TL;DR — Understands How Indonesians Actually Talk About Products
A Dockerized RAG chatbot for a B2B FMCG e-commerce platform that resolves colloquial Indonesian product names keyword search can't ("indomie kuning" → "Indomie Mi Instan Rasa Kaldu Ayam"), classifies 35+ customer intent types, and answers FAQs — all from a single semantic pipeline with no hardcoded keyword or alias tables. Ships as one docker-compose up.
"mie goreng bungkusan merah" ← colloquial query
│ embed + parallel semantic search
┌────────┼─────────────────┐
▼ ▼ ▼
PRODUCTS FAQs INTENTS
(aliases) (ClickHouse) (35+ types)
└────────┴───── best match ──────────▶ correct SKU + Indonesian reply
(no keyword tables)
The Problem
In Indonesian B2B commerce, customers refer to products by colloquial names, brand abbreviations, or regional terms. Standard keyword search fails completely — "indomie kuning" or "mie soto" won't match the official catalog entry. The platform also needed to handle 35+ distinct customer intent types (cart operations to profile inquiries) and answer FAQs without maintaining brittle lookup tables.
The Approach — One Unified RAG Pipeline
A three-collection ChromaDB setup with semantic embeddings (sentence-transformers all-MiniLM-L6-v2) resolves queries across three knowledge domains in parallel:
- Product collection — each product embedded with its official name, colloquial aliases, pack size, and description, enabling fuzzy matching regardless of naming variation.
- FAQ collection — sourced from ClickHouse and indexed into ChromaDB, handling paraphrase and spelling variation.
- Intent collection — 35 intent types embedded as concrete example phrases, classifying the customer's action before routing to the LLM.
A single UnifiedRetriever searches all three and returns the best match by relevance_score = 1.0 - cosine_distance; the UnifiedRAGOrchestrator builds targeted LLM context and generates the Indonesian-language response.
Architecture
User Query (Indonesian / colloquial)
│
┌───────▼───────────────────────────────┐
│ UnifiedRetriever │
│ Embedding → parallel ChromaDB search │
└───┬───────────────┬───────────────┬───┘
▼ ▼ ▼
[Products] [FAQs] [Intents]
colloquial ClickHouse 35 types
name mapping sourced
│ │ │
└───────┬───────┴───────────────┘
│ best match by relevance_score
▼
┌───────────────────────────────────────┐
│ UnifiedRAGOrchestrator │
│ LLM context build + response │
│ Function calling: check_inventory() │
└───────────────────────────────────────┘
│
▼
Response + Order Tracking (JSON)
Key Features
- Colloquial name resolution: Products are indexed with semicolon-separated colloquial alias lists. A query for "mie goreng bungkusan merah" correctly resolves to the official product via semantic similarity, with no explicit alias lookup table.
- LLM function calling for inventory: When a product query includes a quantity, the orchestrator enables a
check_inventory(sku, requested_quantity)function call. The LLM invokes it automatically, and the result is captured byOrderTrackerand persisted to JSON — simulating an order capture flow. - ClickHouse FAQ integration: FAQ data is fetched from a ClickHouse database at index time and stored in ChromaDB, decoupling the vector search from live database queries during runtime.
- Docker-first deployment: The entire system runs inside a single Docker container. Indexing scripts are run as one-time setup commands inside the container, with ChromaDB persisted to a mounted volume. No Python installation required on the host server.
- Windows compatibility: All scripts include UTF-8 reconfiguration for Windows console output, and dependency versions are pinned to avoid onnxruntime DLL issues specific to Windows + torch 2.8.0.
Technical Challenges
The primary compatibility challenge was ChromaDB version locking. ChromaDB 1.3.4 causes segmentation faults on Windows when paired with torch 2.8.0 — the version required for sentence-transformers 2.7.0. The fix was to pin chromadb==0.5.0 and document the constraint explicitly. Similarly, onnxruntime was removed from requirements.txt entirely (Docker uses PyTorch backend; Windows developers install 1.16.3 locally), preventing DLL load failures in the Docker image.
Intent classification required careful data design: the intent collection indexes not just intent names but concrete example customer phrases, so the embedding captures linguistic variation rather than just categorical labels. This keeps the system adaptable — adding a new intent requires only adding example phrases to data/intent.txt and re-indexing.
Impact
- Established the semantic retrieval baseline for the platform's chatbot — covering product search, FAQ, and intent classification in a single unified pipeline.
- 35+ intent types classified with no hardcoded keyword matching, using only semantic similarity against example phrases.
- Dockerized deployment makes the system reproducible across environments with a single
docker-compose up. - Colloquial name resolution removes the need for a manually maintained alias lookup table, reducing ongoing maintenance overhead.