Skip to content

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

KeyValue
Git Remotehttps://git.tlab.co.id/tarantula/tarantula-v2/service/summarizer.git
Branch Aktifmain
bash
git clone https://git.tlab.co.id/tarantula/tarantula-v2/service/summarizer.git
cd summarizer

Tech Stack

LayerTeknologi
RuntimePython 3.11
FrameworkFastAPI + uvicorn
LLM Clientopenai Python SDK (OpenAI-compatible API)
HTTP Clienthttpx (async), requests (sync health probe)
Secret Managementinfisical_sdk (Python)
Asyncasyncio (semaphore-controlled concurrency)
JSON Repairjson-repair (perbaikan JSON rusak dari output LLM)
ContainerizationDocker (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.stag

Environment Variables

File .env / env container

bash
# 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
VariableDefaultKeterangan
INFISICAL_PROJECT_IDID project Infisical
INFISICAL_ENVIRONMENTstagingEnvironment target
INFISICAL_SECRET_PATH/ocrproxy (default di kode) / /summarizer (di env.example)Path secrets di Infisical — lihat catatan di bawah
INFISICAL_CLIENT_IDClient ID Universal Auth
INFISICAL_CLIENT_SECRETClient Secret Universal Auth
INFISICAL_HOSThttp://10.1.102.15:8002URL Infisical server
LLM_TIMEOUT600Timeout per LLM call (detik) — chunk besar bisa makan waktu lama
LLM_MAX_RETRIES2Maksimum retry jika LLM call gagal
LLM_MAX_CONCURRENCY2Maksimum concurrent chunk call ke LLM (semaphore)
DISABLE_INFISICAL0Set 1 untuk skip Infisical dan pakai env var langsung

Catatan: default INFISICAL_SECRET_PATH di kode (app/infisical_config.py) adalah /ocrproxy — kemungkinan sisa dari service lain yang dijadikan basis awal summarizer. env.example sudah 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

VariableDefaultKeterangan
API_KEY_LOCALEMPTYAPI key untuk server LLM (OpenAI-compatible)
BASE_URL_LOCALhttp://192.168.0.27/qwen/v1Base URL LLM server (endpoint /v1)
MODEL_LOCALTLab/LLM-VL-ModelsNama model yang digunakan untuk inferensi

InfisicalConfig memiliki fallback berlapis: jika Infisical tidak dapat dijangkau (network down, auth gagal, atau DISABLE_INFISICAL=1), service tetap berjalan dan membaca nilai dari os.getenv() + default.

Endpoints

MethodPathKeterangan
GET/healthHealth check + probe upstream /models LLM server
POST/summarizeHierarchical summarization + NER; mengembalikan file JSON
POST/extract_entitiesNER 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 NER

Response: 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 entitas

Response: file entities_YYYYMMDD_HHMMSS.json dengan struktur:

json
{
  "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

TahapDeskripsi
ChunkingTeks dipecah per kalimat (split [.!?]) dengan batas chunk_size kata. Kalimat tidak dipotong di tengah.
Stage 1Setiap 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 mergeSatu call LLM pada teks gabungan semua chunk summary; menghasilkan summary final + NER final.
NER aggregationEntitas 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_RETRIES kali di level transport/rate-limit dengan backoff bawaan openai SDK
  • 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

json
{
  "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

KategoriKeteranganContoh
PERNama orang"Joko Widodo"
ORGOrganisasi / perusahaan"PT Telkom"
GPEEntitas geo-politik (negara, kota, wilayah)"Jakarta", "Indonesia"
LOCLokasi / tempat umum"Gedung Sate"
DATETanggal"12 Maret 2024"
TIMEWaktu"pukul 09.00 WIB"
EVENTNama peristiwa"Pemilu 2024"
FACFasilitas (gedung, bandara, jembatan)"Bandara Soekarno-Hatta"

Health Check

GET /health memeriksa:

  1. Status service ("ok")
  2. Konfigurasi aktif (model_base_url, configured_model)
  3. Koneksi ke upstream LLM (/models endpoint) — menampilkan daftar model tersedia
json
{
  "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_len upstream, log level warning disertai saran menurunkan chunk_size.
  • Jika probe gagal (upstream tidak terjangkau saat startup), service tetap jalan normal — hanya log warning.

Build & Run

bash
# 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/