Skip to content

Technical — Chatbot Service

RAGA's inference & knowledge orchestration service, built on Python + FastAPI. Accepts a user question along with a list of relevant knowledge sources, gathers context from Elasticsearch and supporting services in parallel, assembles a dynamic system prompt, then calls one of several LLM providers (local or cloud) to generate an answer. Called by api-tarantula (env var CHATBOT_URL) for every incoming chat.

Repository

KeyValue
Git Remotehttps://git.tlab.co.id/tarantula/service/chatbot-service.git
Active Branchmain
bash
git clone https://git.tlab.co.id/tarantula/service/chatbot-service.git
cd chatbot-service

Tech Stack

LayerTechnology
RuntimePython (FastAPI + uvicorn)
Search / Knowledge IndexElasticsearch
LLM Clientopenai SDK (OpenAI-compatible for every provider)
File ParsingPyPDF2, python-docx, openpyxl
ObservabilityOpenTelemetry (opentelemetry-instrumentation-fastapi/requests/logging, OTLP exporter)
Secret Managementinfisical_sdk (Python)
Testingpytest + pytest-mock + pytest-cov

Folder Structure

chatbot-service/
├── app/
│   ├── main.py                 # FastAPI app, /chatbot & /health endpoints, knowledge source orchestration
│   ├── run.py                  # Entry point that runs uvicorn
│   ├── queue_manager.py        # Alternative queue manager — NOT used by main.py (see note below)
│   ├── config/
│   │   ├── settings.py         # Config loader via Infisical + LLM provider constants
│   │   ├── logging_config.py
│   │   └── telemetry.py        # OpenTelemetry setup (fail-safe, never crashes if OTel misbehaves)
│   ├── models/
│   │   └── request.py          # Pydantic request schema (ChatbotRequest, KMItem)
│   ├── services/
│   │   ├── llm_service.py      # Multi-provider LLM connection, prompt assembly, streaming, image analysis
│   │   ├── data_service.py     # Elasticsearch queries per knowledge source type
│   │   ├── sql_service.py      # Text-to-SQL: generates the query + calls Database Connect
│   │   └── api_service.py      # Text-to-API: generates the call + calls API Connect
│   └── utils/
│       └── helpers.py          # Download & extract file content from a URL (txt/md/log/pdf/docx/xlsx)
└── tests/                      # Unit tests per service

Environment Variables

Configuration is pulled from Infisical (INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT, INFISICAL_SECRET_PATH, INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_HOST), falling back to os.getenv() if the Infisical fetch fails.

Elasticsearch

VariableDescription
URL_ELASTICElasticsearch server URL
USER_ELASTICElasticsearch username
PASS_ELASTICElasticsearch password
VERIFY_ELASTICVerify TLS certificates (default false)

Note: this repo's app/.env.example contains example values that look like real credentials (a specific host IP and password), not generic placeholders like your_xxx_key. These example values should be replaced with plain placeholders, and any credential that may already have been committed here should be rotated.

Elasticsearch Indices

VariableDefaultDescription
ES_INDEX_RDBMS_LISTrdbms_listList of external database connections
ES_INDEX_API_LISTapi_listList of external API definitions
ES_INDEX_BASIC_KNOWLEDGEbasic_knowledgeBaseline knowledge (topics)
ES_INDEX_AUDIO_KNOWLEDGEaudio_knowledgeAudio metadata
ES_INDEX_AUDIO_SUMMARIZE_CHUNKsummarize_audio_chunkPer-chunk audio transcript summaries
ES_INDEX_AUDIO_SUMMARIZEsummarize_audioFull audio summaries
ES_INDEX_OCR_KNOWLEDGEtarantula-ocrDocument OCR results
ES_INDEX_OCR_SUMMARIZE_CHUNKsummarize_document_chunkPer-chunk document summaries
ES_INDEX_OCR_SUMMARIZEsummarize_documentFull document summaries
ES_INDEX_CHAT_HISTORIESchat_historiesConversation history

LLM Providers

Every provider has three variables: API_KEY_*, BASE_URL_*, MODEL_*.

Provider (llm)Default Base URLDefault Model
localhttp://192.168.0.25:8026/v1Qwen/Qwen2.5-14B-Instruct-AWQ
local_v2http://192.168.0.27:18000/v1Qwen/Qwen2.5-32B-Instruct-AWQ
sambanovahttps://api.sambanova.ai/v1Meta-Llama-3.1-70B-Instruct
groqhttps://api.groq.com/openai/v1llama-3.3-70b-versatile
openaihttps://api.openai.com/v1gpt-4o-mini
alibabahttps://dashscope-intl.aliyuncs.com/compatible-mode/v1qwen-plus

If base_url_llm + api_key_llm + model_llm are sent directly in the request, all three are used as-is (overriding llm).

Multimodal Model (Image Analysis)

VariableDefaultDescription
MULTIMODAL_SERVERlocalProvider used for image analysis (local or alibaba)
API_KEY_LOCAL_MULTIMODALEMPTYLocal VLM API key
BASE_URL_LOCAL_MULTIMODALhttp://192.168.0.25:8026/v1Local VLM base URL
MODEL_LOCAL_MULTIMODALQwen/Qwen2.5-VL-32B-Instruct-AWQLocal VLM model
API_KEY_ALIBABA_MULTIMODALEMPTYAlibaba VLM API key
BASE_URL_ALIBABA_MULTIMODALhttps://dashscope-intl.aliyuncs.com/compatible-mode/v1Alibaba VLM base URL
MODEL_ALIBABA_MULTIMODALqwen-vl-maxAlibaba VLM model

Integration & Concurrency

VariableDefaultDescription
API_URL_SQLhttp://192.168.0.25:8105/executeDatabase Connect endpoint for Text-to-SQL
API_URL_GATEhttp://192.168.0.25:8037/proxyAPI Connect endpoint for Text-to-API
VLLM_BASE_URLShttp://192.168.0.27:18011,http://192.168.0.28:18011Comma-separated VLLM base URLs, polled via /metrics
MAX_VLLM_RUNNING4Cap on running VLLM requests before new requests are queued
MAX_VLLM_WAITING4Cap on waiting VLLM requests before new requests are queued
MAX_CONCURRENT_SLOTS2Number of parallel /chatbot processing slots (threading.Semaphore)
POLL_INTERVAL1Polling interval (seconds) while waiting for a slot/queue
TIMEOUT_VLLM30Timeout (seconds) when calling VLLM's /metrics
USE_QUEUEtrueRead by queue_manager.py — see the note below

Architecture note: app/queue_manager.py defines its own queue manager but is never imported or used by main.py. The concurrency mechanism actually in effect is threading.Semaphore(MAX_CONCURRENT_SLOTS) plus manual polling of VLLM's /metrics in main.py. This module is likely leftover from a refactor that was never cleaned up.

Endpoints

MethodPathDescription
POST/chatbotMain endpoint: processes the question, gathers knowledge, calls the LLM
GET/healthHealth check — verifies Elasticsearch connectivity

POST /chatbot

Main request fields (see ChatbotRequest):

FieldTypeDescription
questionstringRequired. The user's question
internalboolIf true, the LLM may answer using general knowledge; if false (default), it's restricted to available data
chainboolIf true, chat history (room_chat_id) is included as context
room_chat_idstringRoom chat ID, used to fetch history
personalizationstringCustom tone/style instruction; empty = default language-matching behavior
llmstringProvider: local, local_v2, sambanova, groq, openai, alibaba
base_url_llm / model_llm / api_key_llmstringManual provider override (optional)
faqstringAdditional context for FAQ mode
kmarrayList of { type, topic_id[] } — knowledge sources to include (basic, ocr, rdbms, api, audio)
streamboolIf true, the response is an NDJSON stream
sourceboolIf true, include raw source data (source_data) in the response
thinkboolIf true, appends the /think suffix; default is /no_think
image / image_urlbool / arrayEnables image analysis via the multimodal model
files / files_urlbool / arrayEnables extraction & Q&A over files from a URL

200 Response (non-stream):

json
{
  "response": "answer from the 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 only appears if a source failed to fetch (not a fatal error — the request still proceeds with whichever sources succeeded).

200 Response (stream), media_type: application/jsonlines, one JSON object per line:

json
{"type": "message", "data": "chunk of the answer text"}
{"type": "metadata", "data": { "response": "...", "token_usage": {...}, "source_data": {...} }}

Concurrency & VLLM Queue Flow

Knowledge Source Gathering (Parallel)

The five knowledge source types are fetched concurrently via ThreadPoolExecutor(max_workers=5); each is wrapped in _safe_get so that one source failing doesn't fail the whole request.

SourceFunctionElasticsearch IndexResult Size
Basic (topic)get_dataES_INDEX_BASIC_KNOWLEDGE10 documents
Document/OCRget_data_ocrES_INDEX_OCR_KNOWLEDGE + summary indices3 documents
Audioget_data_audioES_INDEX_AUDIO_KNOWLEDGE + summary indices10 documents
Database (RDBMS)get_data_rdbmsES_INDEX_RDBMS_LIST (via es.get, 1 schema document)1 connection
External APIget_data_apiES_INDEX_API_LIST (via es.mget)matches number of topic_id
Chat history (chain=true)get_historyES_INDEX_CHAT_HISTORIESlast 20 messages

Text-to-SQL & Text-to-API

For database and API sources, the raw content is not used directly — Chatbot Service asks the LLM to compose the right query/call first, then executes it:

External API calls (when there is more than one topic_id) are processed in parallel via ThreadPoolExecutor. If any API call fails or returns invalid data, the entire source_data["api"] for that request is cleared (not just the failing endpoint).

System Prompt Assembly

process_latest() in llm_service.py assembles the system prompt from the following components, matching what's described on the Raga Engine page:

ComponentSource
Current timedatetime.now()
Internal prompt"Data-only" mode (default) or "general knowledge allowed" mode (internal=true)
Knowledge source contentMerged from basic + OCR + audio + Text-to-SQL result + Text-to-API result
Chat historyFrom get_history, cleaned of repeated "no information available" lines
User filesExtraction result of files_url via generate_answer()
Image descriptionResult of analyze_images() — a separate call to the multimodal model
PersonalizationCustom tone instruction or the default (matches the user's language)

The user prompt is built as aggressive_prefix + question + suffix (/think or /no_think, controlling the model's reasoning mode).

File Extraction (files_url)

TypeExtensions
Text.txt, .md, .log
Document.pdf, .docx, .xlsx

Files are downloaded temporarily from the URL, their content extracted, then answered via generate_answer() before being merged into the system prompt as files_users.

External Integrations

ServiceEnv VariableDescription
Database ConnectAPI_URL_SQLExecutes the SQL query produced by Text-to-SQL
API ConnectAPI_URL_GATEExecutes the API call produced by Text-to-API
VLLM (local)VLLM_BASE_URLSSource of queue metrics for the local/local_v2 providers
InfisicalINFISICAL_HOSTSource of all runtime secrets

Health Check

GET /health only checks Elasticsearch connectivity (es.ping()) — returns 200 ("status": "ok") or 503 ("status": "degraded") if Elasticsearch is unreachable. LLM providers and downstream services (Database Connect, API Connect, VLLM) are not covered by this check.

Build & Run

bash
pip install -r requirements.txt

# Run directly
uvicorn app.run:app --host 0.0.0.0 --port 8080

# Docker
docker build -t chatbot-service .
docker run -p 8080:8080 chatbot-service