Teknis — Chatbot Service
Service inferensi & orkestrasi knowledge RAGA yang dibangun di atas Python + FastAPI. Menerima pertanyaan user beserta daftar sumber pengetahuan yang relevan, mengumpulkan konteks dari Elasticsearch dan service pendukung secara paralel, menyusun system prompt dinamis, lalu memanggil salah satu provider LLM (lokal maupun cloud) untuk menghasilkan jawaban. Dipanggil oleh api-tarantula (env var CHATBOT_URL) untuk setiap chat masuk.
Repository
| Key | Value |
|---|---|
| Git Remote | https://git.tlab.co.id/tarantula/service/chatbot-service.git |
| Branch Aktif | main |
git clone https://git.tlab.co.id/tarantula/service/chatbot-service.git
cd chatbot-serviceTech Stack
| Layer | Teknologi |
|---|---|
| Runtime | Python (FastAPI + uvicorn) |
| Search / Knowledge Index | Elasticsearch |
| LLM Client | openai SDK (OpenAI-compatible untuk semua provider) |
| File Parsing | PyPDF2, python-docx, openpyxl |
| Observability | OpenTelemetry (opentelemetry-instrumentation-fastapi/requests/logging, OTLP exporter) |
| Secret Management | infisical_sdk (Python) |
| Testing | pytest + pytest-mock + pytest-cov |
Struktur Folder
chatbot-service/
├── app/
│ ├── main.py # FastAPI app, endpoint /chatbot & /health, orkestrasi knowledge source
│ ├── run.py # Entry point menjalankan uvicorn
│ ├── queue_manager.py # Queue manager alternatif — TIDAK dipakai oleh main.py (lihat catatan di bawah)
│ ├── config/
│ │ ├── settings.py # Loader konfigurasi via Infisical + konstanta provider LLM
│ │ ├── logging_config.py
│ │ └── telemetry.py # Setup OpenTelemetry (aman-gagal, tidak crash jika OTel bermasalah)
│ ├── models/
│ │ └── request.py # Skema request Pydantic (ChatbotRequest, KMItem)
│ ├── services/
│ │ ├── llm_service.py # Koneksi multi-provider LLM, penyusunan prompt, streaming, analisis gambar
│ │ ├── data_service.py # Query Elasticsearch untuk tiap jenis knowledge source
│ │ ├── sql_service.py # Text-to-SQL: generate query + panggil Database Connect
│ │ └── api_service.py # Text-to-API: generate call + panggil API Connect
│ └── utils/
│ └── helpers.py # Download & ekstraksi isi file dari URL (txt/md/log/pdf/docx/xlsx)
└── tests/ # Unit test per serviceEnvironment Variables
Konfigurasi diambil dari Infisical (INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT, INFISICAL_SECRET_PATH, INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_HOST), dengan fallback ke os.getenv() jika pengambilan dari Infisical gagal.
Elasticsearch
| Variable | Keterangan |
|---|---|
URL_ELASTIC | URL Elasticsearch server |
USER_ELASTIC | Username Elasticsearch |
PASS_ELASTIC | Password Elasticsearch |
VERIFY_ELASTIC | Verifikasi sertifikat TLS (default false) |
Catatan:
app/.env.exampledi repo ini berisi nilai contoh yang terlihat seperti kredensial nyata (host IP dan password spesifik), bukan placeholder generik sepertiyour_xxx_key. Sebaiknya nilai di file contoh ini diganti jadi placeholder murni, dan kredensial yang mungkin sudah pernah ter-commit ini dirotasi.
Index Elasticsearch
| Variable | Default | Keterangan |
|---|---|---|
ES_INDEX_RDBMS_LIST | rdbms_list | Daftar koneksi database eksternal |
ES_INDEX_API_LIST | api_list | Daftar definisi API eksternal |
ES_INDEX_BASIC_KNOWLEDGE | basic_knowledge | Knowledge dasar (topic) |
ES_INDEX_AUDIO_KNOWLEDGE | audio_knowledge | Metadata audio |
ES_INDEX_AUDIO_SUMMARIZE_CHUNK | summarize_audio_chunk | Ringkasan per-chunk transkrip audio |
ES_INDEX_AUDIO_SUMMARIZE | summarize_audio | Ringkasan penuh audio |
ES_INDEX_OCR_KNOWLEDGE | tarantula-ocr | Hasil OCR dokumen |
ES_INDEX_OCR_SUMMARIZE_CHUNK | summarize_document_chunk | Ringkasan per-chunk dokumen |
ES_INDEX_OCR_SUMMARIZE | summarize_document | Ringkasan penuh dokumen |
ES_INDEX_CHAT_HISTORIES | chat_histories | Riwayat percakapan |
Provider LLM
Setiap provider punya tiga variabel: API_KEY_*, BASE_URL_*, MODEL_*.
Provider (llm) | Default Base URL | Default Model |
|---|---|---|
local | http://192.168.0.25:8026/v1 | Qwen/Qwen2.5-14B-Instruct-AWQ |
local_v2 | http://192.168.0.27:18000/v1 | Qwen/Qwen2.5-32B-Instruct-AWQ |
sambanova | https://api.sambanova.ai/v1 | Meta-Llama-3.1-70B-Instruct |
groq | https://api.groq.com/openai/v1 | llama-3.3-70b-versatile |
openai | https://api.openai.com/v1 | gpt-4o-mini |
alibaba | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | qwen-plus |
Jika base_url_llm + api_key_llm + model_llm dikirim langsung di request, ketiganya dipakai apa adanya (override) tanpa melihat llm.
Model Multimodal (Analisis Gambar)
| Variable | Default | Keterangan |
|---|---|---|
MULTIMODAL_SERVER | local | Provider yang dipakai untuk analisis gambar (local atau alibaba) |
API_KEY_LOCAL_MULTIMODAL | EMPTY | API key VLM lokal |
BASE_URL_LOCAL_MULTIMODAL | http://192.168.0.25:8026/v1 | Base URL VLM lokal |
MODEL_LOCAL_MULTIMODAL | Qwen/Qwen2.5-VL-32B-Instruct-AWQ | Model VLM lokal |
API_KEY_ALIBABA_MULTIMODAL | EMPTY | API key VLM Alibaba |
BASE_URL_ALIBABA_MULTIMODAL | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | Base URL VLM Alibaba |
MODEL_ALIBABA_MULTIMODAL | qwen-vl-max | Model VLM Alibaba |
Integrasi & Concurrency
| Variable | Default | Keterangan |
|---|---|---|
API_URL_SQL | http://192.168.0.25:8105/execute | Endpoint Database Connect untuk Text-to-SQL |
API_URL_GATE | http://192.168.0.25:8037/proxy | Endpoint API Connect untuk Text-to-API |
VLLM_BASE_URLS | http://192.168.0.27:18011,http://192.168.0.28:18011 | Daftar base URL VLLM (comma-separated), di-poll lewat /metrics |
MAX_VLLM_RUNNING | 4 | Batas request VLLM yang sedang berjalan sebelum request baru di-queue |
MAX_VLLM_WAITING | 4 | Batas request VLLM yang menunggu sebelum request baru di-queue |
MAX_CONCURRENT_SLOTS | 2 | Jumlah slot pemrosesan /chatbot paralel (threading.Semaphore) |
POLL_INTERVAL | 1 | Interval polling (detik) saat menunggu slot/antrian |
TIMEOUT_VLLM | 30 | Timeout (detik) saat memanggil /metrics VLLM |
USE_QUEUE | true | Dibaca oleh queue_manager.py — lihat catatan di bawah |
Catatan arsitektur:
app/queue_manager.pymendefinisikan queue manager sendiri tapi tidak diimpor atau dipakai olehmain.py. Mekanisme concurrency yang benar-benar aktif adalahthreading.Semaphore(MAX_CONCURRENT_SLOTS)plus polling manual ke/metricsVLLM dimain.py. Modul ini kemungkinan sisa refactor yang belum dibersihkan.
Endpoints
| Method | Path | Keterangan |
|---|---|---|
POST | /chatbot | Endpoint utama: proses pertanyaan, kumpulkan knowledge, panggil LLM |
GET | /health | Health check — memeriksa konektivitas Elasticsearch |
POST /chatbot
Field request utama (lihat ChatbotRequest):
| Field | Tipe | Keterangan |
|---|---|---|
question | string | Wajib. Pertanyaan user |
internal | bool | Jika true, LLM boleh menjawab pakai pengetahuan umum; jika false (default), LLM dibatasi hanya pada data yang tersedia |
chain | bool | Jika true, riwayat chat (room_chat_id) disertakan sebagai konteks |
room_chat_id | string | ID room chat, dipakai untuk mengambil riwayat |
personalization | string | Instruksi gaya bahasa custom; kosong = default menyesuaikan bahasa user |
llm | string | Provider: local, local_v2, sambanova, groq, openai, alibaba |
base_url_llm / model_llm / api_key_llm | string | Override manual provider (opsional) |
faq | string | Konteks tambahan untuk mode FAQ |
km | array | Daftar { type, topic_id[] } — knowledge source yang ingin disertakan (basic, ocr, rdbms, api, audio) |
stream | bool | Jika true, response berupa stream NDJSON |
source | bool | Jika true, sertakan data mentah sumber (source_data) di response |
think | bool | Jika true, tambahkan suffix /think; default /no_think |
image / image_url | bool / array | Aktifkan analisis gambar via model multimodal |
files / files_url | bool / array | Aktifkan ekstraksi & tanya-jawab atas file dari URL |
Response (non-stream) 200:
{
"response": "jawaban dari LLM",
"internal": false,
"chain": false,
"room_chat_id": "uuid",
"token_usage": { "prompt_tokens": 123, "completion_tokens": 45, "total_tokens": 168 },
"source_data": { "basic": [], "ocr": [], "rdbms": [], "api": [], "audio": [] },
"degraded_sources": ["rdbms"]
}degraded_sources hanya muncul kalau ada sumber yang gagal diambil (bukan error fatal — request tetap lanjut dengan sumber yang berhasil).
Response (stream) 200, media_type: application/jsonlines, tiap baris JSON:
{"type": "message", "data": "potongan teks jawaban"}
{"type": "metadata", "data": { "response": "...", "token_usage": {...}, "source_data": {...} }}Alur Concurrency & Antrian VLLM
Pengumpulan Knowledge Source (Paralel)
Lima jenis knowledge source diambil bersamaan lewat ThreadPoolExecutor(max_workers=5); masing-masing dibungkus _safe_get sehingga kegagalan satu sumber tidak menggagalkan permintaan.
| Sumber | Fungsi | Index Elasticsearch | Ukuran Hasil |
|---|---|---|---|
| Basic (topic) | get_data | ES_INDEX_BASIC_KNOWLEDGE | 10 dokumen |
| Dokumen/OCR | get_data_ocr | ES_INDEX_OCR_KNOWLEDGE + index ringkasan | 3 dokumen |
| Audio | get_data_audio | ES_INDEX_AUDIO_KNOWLEDGE + index ringkasan | 10 dokumen |
| Database (RDBMS) | get_data_rdbms | ES_INDEX_RDBMS_LIST (via es.get, 1 dokumen skema) | 1 koneksi |
| API eksternal | get_data_api | ES_INDEX_API_LIST (via es.mget) | sesuai jumlah topic_id |
Riwayat chat (chain=true) | get_history | ES_INDEX_CHAT_HISTORIES | 20 pesan terakhir |
Text-to-SQL & Text-to-API
Untuk sumber database dan API, isinya tidak langsung dipakai — Chatbot Service meminta LLM menyusun query/panggilan yang tepat, baru mengeksekusinya:
Panggilan API eksternal (jika lebih dari satu topic_id) diproses paralel lewat ThreadPoolExecutor. Jika salah satu panggilan API gagal atau mengembalikan data tidak valid, seluruh source_data["api"] untuk request tersebut dikosongkan (bukan hanya endpoint yang gagal).
Penyusunan System Prompt
process_latest() di llm_service.py merakit system prompt dari komponen berikut, persis seperti yang dideskripsikan di Raga Engine:
| Komponen | Sumber |
|---|---|
| Waktu saat ini | datetime.now() |
| Internal prompt | Mode "hanya data yang tersedia" (default) atau "boleh pengetahuan umum" (internal=true) |
| Konten knowledge source | Digabung dari basic + OCR + audio + hasil Text-to-SQL + hasil Text-to-API |
| Riwayat chat | Dari get_history, dibersihkan dari baris "tidak ada informasinya" berulang |
| File dari user | Hasil ekstraksi files_url lewat generate_answer() |
| Deskripsi gambar | Hasil analyze_images() — panggilan terpisah ke model multimodal |
| Personalisasi | Instruksi gaya bahasa custom atau default (menyesuaikan bahasa user) |
User prompt disusun dari aggressive_prefix + pertanyaan + suffix (/think atau /no_think, mengontrol reasoning mode model).
Ekstraksi File (files_url)
| Tipe | Ekstensi |
|---|---|
| Teks | .txt, .md, .log |
| Dokumen | .pdf, .docx, .xlsx |
File diunduh sementara dari URL, diekstrak isinya, lalu dijawab lewat generate_answer() sebelum digabung ke system prompt sebagai files_users.
Integrasi Eksternal
| Service | Env Variable | Keterangan |
|---|---|---|
| Database Connect | API_URL_SQL | Eksekusi query SQL hasil Text-to-SQL |
| API Connect | API_URL_GATE | Eksekusi panggilan API hasil Text-to-API |
| VLLM (lokal) | VLLM_BASE_URLS | Sumber metrik antrian untuk provider local/local_v2 |
| Infisical | INFISICAL_HOST | Sumber seluruh secret runtime |
Health Check
GET /health hanya memeriksa konektivitas Elasticsearch (es.ping()) — mengembalikan 200 ("status": "ok") atau 503 ("status": "degraded") jika Elasticsearch tidak terjangkau. Provider LLM dan service downstream (Database Connect, API Connect, VLLM) tidak termasuk dalam pengecekan ini.
Build & Run
pip install -r requirements.txt
# Jalankan langsung
uvicorn app.run:app --host 0.0.0.0 --port 8080
# Docker
docker build -t chatbot-service .
docker run -p 8080:8080 chatbot-service