Teknis — Summarizer
Service hierarchical summarization & Named Entity Recognition (NER) RAGA yang dibangun di atas Python + FastAPI. Menerima file teks, memecahnya menjadi chunk, memanggil LLM secara paralel untuk setiap chunk, lalu menggabungkan hasilnya menjadi ringkasan final beserta entitas bernama. Digunakan oleh api-tarantula untuk memproses dokumen dan audio transcript.
Repository
| Key | Value |
|---|---|
| Git Remote | https://git.tlab.co.id/tarantula/tarantula-v2/service/summarizer.git |
| Branch Aktif | main |
git clone https://git.tlab.co.id/tarantula/tarantula-v2/service/summarizer.git
cd summarizerTech Stack
| Layer | Teknologi |
|---|---|
| Runtime | Python 3.11 |
| Framework | FastAPI + uvicorn |
| LLM Client | openai Python SDK (OpenAI-compatible API) |
| HTTP Client | httpx (async), requests (sync health probe) |
| Secret Management | infisical_sdk (Python) |
| Async | asyncio (semaphore-controlled concurrency) |
| JSON Repair | json-repair (perbaikan JSON rusak dari output LLM) |
| Containerization | Docker (python:3.11-slim) |
Struktur Folder
summarizer/
├── app/
│ ├── main.py # FastAPI app, pipeline, endpoints
│ └── infisical_config.py # Infisical client + env fallback
├── tests/
│ ├── conftest.py
│ ├── test_call_json.py
│ ├── test_endpoints.py
│ ├── test_infisical_config.py
│ ├── test_summarize_and_ner.py
│ └── test_utils.py
├── requirements.txt
├── env.example
├── Dockerfile
└── Dockerfile.stagEnvironment Variables
File .env / env container
# Infisical (wajib)
INFISICAL_PROJECT_ID=
INFISICAL_ENVIRONMENT=
INFISICAL_SECRET_PATH=/summarizer
INFISICAL_CLIENT_ID=
INFISICAL_CLIENT_SECRET=
INFISICAL_HOST=
# Tuning LLM (opsional, tidak via Infisical)
LLM_TIMEOUT=600
LLM_MAX_RETRIES=2
LLM_MAX_CONCURRENCY=2
# Menonaktifkan Infisical (fallback ke env var langsung)
DISABLE_INFISICAL=0| Variable | Default | Keterangan |
|---|---|---|
INFISICAL_PROJECT_ID | — | ID project Infisical |
INFISICAL_ENVIRONMENT | staging | Environment target |
INFISICAL_SECRET_PATH | /ocrproxy (default di kode) / /summarizer (di env.example) | Path secrets di Infisical — lihat catatan di bawah |
INFISICAL_CLIENT_ID | — | Client ID Universal Auth |
INFISICAL_CLIENT_SECRET | — | Client Secret Universal Auth |
INFISICAL_HOST | http://10.1.102.15:8002 | URL Infisical server |
LLM_TIMEOUT | 600 | Timeout per LLM call (detik) — chunk besar bisa makan waktu lama |
LLM_MAX_RETRIES | 2 | Maksimum retry jika LLM call gagal |
LLM_MAX_CONCURRENCY | 2 | Maksimum concurrent chunk call ke LLM (semaphore) |
DISABLE_INFISICAL | 0 | Set 1 untuk skip Infisical dan pakai env var langsung |
Catatan: default
INFISICAL_SECRET_PATHdi kode (app/infisical_config.py) adalah/ocrproxy— kemungkinan sisa dari service lain yang dijadikan basis awal summarizer.env.examplesudah benar menunjuk ke/summarizer, tapi nilai default di kode sebaiknya diselaraskan supaya tidak salah path kalau env var tidak di-set eksplisit.
Secrets via Infisical
| Variable | Default | Keterangan |
|---|---|---|
API_KEY_LOCAL | EMPTY | API key untuk server LLM (OpenAI-compatible) |
BASE_URL_LOCAL | http://192.168.0.27/qwen/v1 | Base URL LLM server (endpoint /v1) |
MODEL_LOCAL | TLab/LLM-VL-Models | Nama model yang digunakan untuk inferensi |
InfisicalConfigmemiliki fallback berlapis: jika Infisical tidak dapat dijangkau (network down, auth gagal, atauDISABLE_INFISICAL=1), service tetap berjalan dan membaca nilai darios.getenv()+ default.
Endpoints
| Method | Path | Keterangan |
|---|---|---|
GET | /health | Health check + probe upstream /models LLM server |
POST | /summarize | Hierarchical summarization + NER; mengembalikan file JSON |
POST | /extract_entities | NER saja (tanpa summarize); mengembalikan file JSON |
POST /summarize
Content-Type: multipart/form-data
file (UploadFile) — file teks yang akan dirangkum
chunk_size (int, default 10000) — ukuran chunk dalam kata
target_context (int, default 128000) — context window target (kata); jika merged summary masih melebihi ini, pipeline melakukan merge pass tambahan
enable_ner (bool, default true) — aktifkan NERResponse: file summary_YYYYMMDD_HHMMSS.json (di-download langsung, temp file dihapus otomatis via BackgroundTask).
POST /extract_entities
Content-Type: multipart/form-data
file (UploadFile) — file teks untuk ekstraksi entitasResponse: file entities_YYYYMMDD_HHMMSS.json dengan struktur:
{
"named_entities": {
"PER": ["Budi Santoso"],
"ORG": ["Kementerian Keuangan"],
"LOC": [], "GPE": ["Jakarta"],
"DATE": ["12 Maret 2024"],
"TIME": [], "EVENT": [], "FAC": []
},
"input_tokens": 542,
"timestamp": "2026-08-02T10:15:00.123456"
}Pipeline Hierarchical Summarization
Tahapan Pipeline
| Tahap | Deskripsi |
|---|---|
| Chunking | Teks dipecah per kalimat (split [.!?]) dengan batas chunk_size kata. Kalimat tidak dipotong di tengah. |
| Stage 1 | Setiap chunk diproses paralel via asyncio.gather. Semaphore membatasi LLM_MAX_CONCURRENCY call bersamaan. Satu call LLM menghasilkan summary + key_points + topics + named_entities (merge-on-raw). |
| Stage 2+ | Jika gabungan semua summary masih melebihi target_context kata, pipeline rechunk dan re-summarize secara iteratif. NER dinonaktifkan di intermediate pass untuk menghemat token. |
| Final merge | Satu call LLM pada teks gabungan semua chunk summary; menghasilkan summary final + NER final. |
| NER aggregation | Entitas digabung dari 3 sumber: chunk-level (highest recall), final summary, dan key_points. |
Strategi "Merge-on-Raw"
NER dijalankan pada teks chunk mentah, bukan pada summary. Ini memberikan recall entitas yang lebih tinggi karena summary cenderung menghilangkan sebutan spesifik (nama orang, tanggal, lokasi). Dengan pendekatan ini, satu LLM call per chunk menghasilkan summary sekaligus entitas — menghemat setengah round-trip dibanding pendekatan lama.
Ketahanan Terhadap Kegagalan
asyncio.gather(return_exceptions=True)— kegagalan satu chunk tidak membatalkan chunk lain- Chunk yang gagal digantikan placeholder kosong (summary
"", entities[]) - LLM call di-retry
LLM_MAX_RETRIESkali di level transport/rate-limit dengan backoff bawaanopenaiSDK - Terpisah dari itu,
_call_json()(parsing hasil LLM ke JSON) punya retry loop sendiri — default 3 percobaan per call; tiap percobaan gagal di-log dengan potongan raw response untuk debugging - Block
<think>...</think>dari reasoning model di-strip sebelum JSON parse - Response JSON dengan fence code block (
```json) dibersihkan sebelum parse - Jika
json.loads()tetap gagal (mis. upstream vLLM mengembalikan JSON rusak — double brace, unescaped quote),json_repair.repair_json()mencoba memperbaikinya secara heuristik sebelum percobaan dianggap gagal
Struktur Output /summarize
{
"total_initial_chunks": 5,
"summarize_chunk": [
{
"chunk_index": 1,
"original_tokens": 987,
"summary_tokens": 145,
"original_text": "...",
"summary": "...",
"key_points": ["...", "..."],
"topics": ["...", "..."],
"named_entities": {
"PER": ["Budi Santoso"],
"ORG": ["Kementerian Keuangan"],
"LOC": [], "GPE": ["Jakarta"],
"DATE": ["12 Maret 2024"],
"TIME": [], "EVENT": [], "FAC": []
}
}
],
"final_summary": {
"summary": "Ringkasan panjang dan komprehensif...",
"key_points_model": ["..."],
"key_points_aggregated": ["..."],
"topics_aggregated": ["..."],
"named_entities": {
"final_summary_entities": { "PER": [], "ORG": [] },
"aggregated_key_points_entities": { "PER": [] },
"chunk_entities_merged": { "PER": [] },
"all_entities_merged": { "PER": [] }
}
},
"final_summary_tokens": 312
}NER Entity Types
| Kategori | Keterangan | Contoh |
|---|---|---|
PER | Nama orang | "Joko Widodo" |
ORG | Organisasi / perusahaan | "PT Telkom" |
GPE | Entitas geo-politik (negara, kota, wilayah) | "Jakarta", "Indonesia" |
LOC | Lokasi / tempat umum | "Gedung Sate" |
DATE | Tanggal | "12 Maret 2024" |
TIME | Waktu | "pukul 09.00 WIB" |
EVENT | Nama peristiwa | "Pemilu 2024" |
FAC | Fasilitas (gedung, bandara, jembatan) | "Bandara Soekarno-Hatta" |
Health Check
GET /health memeriksa:
- Status service (
"ok") - Konfigurasi aktif (
model_base_url,configured_model) - Koneksi ke upstream LLM (
/modelsendpoint) — menampilkan daftar model tersedia
{
"status": "ok",
"model_base_url": "http://192.168.0.27/qwen/v1",
"configured_model": "TLab/LLM-VL-Models",
"upstream": {
"reachable": true,
"models": ["TLab/LLM-VL-Models"],
"error": null
}
}Startup: Chunk Budget Probe
Saat aplikasi start (lifespan handler), service memanggil sekali endpoint /models milik upstream LLM dan membandingkan max_model_len model dengan estimasi token dari chunk_size default (10000 kata, dikonversi ke token dengan rasio kata→token 1.4 untuk teks Bahasa Indonesia). Hasilnya hanya di-log (tidak menghalangi startup):
- Jika estimasi token chunk default sudah > 60% dari
max_model_lenupstream, log levelwarningdisertai saran menurunkanchunk_size. - Jika probe gagal (upstream tidak terjangkau saat startup), service tetap jalan normal — hanya log
warning.
Build & Run
# Install dependencies
pip install -r requirements.txt
# Jalankan development
uvicorn app.main:app --reload --port 8000
# Docker build & run
docker build -t summarizer .
docker run -p 8000:8000 \
-e INFISICAL_PROJECT_ID=... \
-e INFISICAL_ENVIRONMENT=dev \
-e INFISICAL_SECRET_PATH=/summarizer \
-e INFISICAL_CLIENT_ID=... \
-e INFISICAL_CLIENT_SECRET=... \
-e INFISICAL_HOST=... \
summarizer
# Jalankan tests
pytest tests/