Multi-Agent RAG Support Bot β Grounded in Real Policy Docs
Answers billing, plan, and troubleshooting questions accurately β and knows exactly when to escalate to a human.
π Built for The Talent Hack Β· Deutsche Telekom Digital Labs Β· AI Engineer Track
SeeSUBMISSION.mdfor the full hackathon write-up and architecture rationale.
The Telecom Support Agent is a production-grade multi-agent AI system that handles real customer support queries β grounded entirely in policy documents, with zero hallucinated answers. Every response is retrieved from actual telecom policy docs, cited at the source, and confidence-gated before delivery. When the system isn't certain enough, or when a request would mutate account state, it escalates automatically β never silently fails.
graph TD
A[π¬ Customer Message] --> B[π― Intent Router]
B --> C[π³ Billing Agent]
B --> D[π Plan Info Agent]
B --> E[π§ Troubleshooting Agent]
C --> F[π RAG Retrieval]
D --> F
E --> F
F --> G[π ChromaDB + Policy Docs]
G --> H[β
Confidence Gate]
H -->|High Confidence| I[π€ Grounded Answer + Citation]
H -->|Low Confidence| J[π¨ Escalate to Human]
H -->|Account-Mutating Request| J
style A fill:#e1f5fe
style G fill:#f3e5f5
style I fill:#e8f5e9
style J fill:#fce4ec
| Feature | Technology | Detail |
|---|---|---|
| π― Intent Classification | Rule-based + LLM | billing / plan_info / troubleshooting / out_of_scope |
| π RAG-Grounded Answers | ChromaDB | Section-aware chunking on ## markdown headers |
| π€ Domain-Specific Agents | Injectable LLM pipeline | 3 specialized agents + orchestrator |
| π‘οΈ Confidence Gate | Deterministic threshold | Numeric score β not an LLM self-report |
| π¨ Escalation Guard | Keyword-rule engine | Refund / cancel / plan-switch β always human |
| π§ͺ Zero-API Testing | Stub injection | Full pipeline testable without an Azure OpenAI key |
| π³ Containerized | Docker | One-command deployment |
ββββββββββββββββββββββββββββββββ
User message βββββββΆβ Intent Router β
β billing / plan_info / β
β troubleshooting / out_of_scopeβ
ββββββββββββββββ¬ββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββ
β π³ Billing β β π Plan Info β β π§ Troubleshooting β
β Agent β β Agent β β Agent β
ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ ββββββββββββ¬ββββββββββββ
β β β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββ
β π RAG Retrieval β
β ChromaDB + Policy Docsβ
β Section-aware chunks β
βββββββββββββ¬βββββββββββββ
βΌ
βββββββββββββββββββββββββ
β β
Confidence Gate β
β threshold: numeric β
β mutating: keyword ruleβ
βββββββββββββ¬βββββββββββββ
βββββββββββ΄βββββββββββ
βΌ βΌ
π€ Grounded Answer π¨ Escalate to Human
+ Source Citation (ticket reference)
telecom-support-agent/
β
βββ π app/
β βββ π agents/
β β βββ intent_agent.py # billing / plan_info / troubleshooting / out_of_scope
β β βββ billing_agent.py # RAG-grounded billing answers
β β βββ plan_agent.py # RAG-grounded plan comparisons
β β βββ troubleshooting_agent.py # RAG-grounded troubleshooting steps
β β βββ escalation_agent.py # deterministic escalation gate
β β βββ orchestrator.py # full pipeline wiring + error handling
β β
β βββ π rag/
β β βββ embeddings.py # offline hashing embedder (zero model download)
β β βββ ingest.py # chunk + embed + load policy docs
β β βββ retriever.py # retrieval wrapper, fails soft to []
β β
β βββ llm.py # Azure OpenAI client (only wired in when key is present)
β βββ main.py # FastAPI app + conditional LLM wiring
β
βββ π data/policies/ # sample billing, plan, troubleshooting docs
βββ π tests/ # 29 tests β unit + API layer
β βββ test_intent_agent.py
β βββ test_billing_agent.py
β βββ test_plan_agent.py
β βββ test_troubleshooting_agent.py
β βββ test_escalation_gate.py
β βββ test_retrieval.py
β βββ test_orchestrator.py
β βββ test_api.py # FastAPI TestClient
β
βββ SUBMISSION.md # Hackathon write-up + rationale
βββ Dockerfile
βββ .env.example
βββ requirements.txt
Role: Entry point classifier
Classifies into:
- billing Bill queries, payment issues
- plan_info Plan comparisons, upgrades
- troubleshooting Network, device, connectivity
- out_of_scope Routes away gracefully
Method:
- Rule-based primary pass
- LLM fallback for ambiguous input
- Stub fallback when no API key set
Output:
- intent_label
- confidence_score
- routed_agentRole: Billing & payment specialist
Handles:
- Unexpected charge queries
- Invoice breakdown requests
- Payment failure investigation
- Roaming charge explanations
Method:
- ChromaDB retrieval on billing docs
- Section-aware policy matching
- Citation extraction from chunks
Output:
- Grounded answer
- Source citation (doc + section)
- Confidence score |
Role: Plan comparison specialist
Handles:
- Plan feature comparisons
- Upgrade / downgrade info
- Add-on and bundle details
- Contract term queries
Method:
- ChromaDB retrieval on plan docs
- Multi-chunk context assembly
- Comparative answer synthesis
Output:
- Grounded comparison
- Source citation
- Confidence scoreRole: Technical support specialist
Handles:
- Network connectivity issues
- Device configuration problems
- Service outage queries
- Signal and speed issues
Method:
- ChromaDB retrieval on tech docs
- Step-by-step answer extraction
- Escalation if steps exceed scope
Output:
- Grounded troubleshooting steps
- Source citation
- Confidence score |
Role: Quality gate + safety enforcer
Triggers escalation on:
Keyword rule (deterministic): Numeric threshold:
- "refund" / "get my money back" - confidence_score < threshold
- "cancel" / "close my account" - retrieval returns []
- "switch plan" / "change plan" - answer flagged low-certainty
Why deterministic (not LLM self-report):
The model proposes answers.
It never decides on its own that it is "confident enough."
Mutating requests are caught by rule β not by asking the LLM if it feels sure.
Output:
- escalate: true/false
- reason: "low_confidence" | "account_mutating" | "out_of_scope"
- ticket_reference: TKT-{uuid} |
|
Chunks split on markdown ## headers β not fixed character windows. Topical coherence per chunk matters more for retrieval quality than raw chunk size.
# ingest.py β section-aware chunking
def chunk_policy_doc(text: str) -> list[str]:
"""Split on ## headers to preserve topical coherence."""
sections = re.split(r'\n##\s+', text)
return [s.strip() for s in sections if s.strip()]
# Each chunk = one coherent policy topic
# e.g. "## Roaming Charges" stays together as one retrieval unitapp/rag/embeddings.py uses a keyword-overlap based hashing embedder β no sentence-transformers, no external model download. The project runs immediately in network-locked environments.
# embeddings.py β swap-ready interface
class BaseEmbedder(ABC):
@abstractmethod
def embed(self, text: str) -> list[float]: ...
class HashingEmbedder(BaseEmbedder):
"""Offline keyword-overlap embedder. Zero external calls."""
def embed(self, text: str) -> list[float]: ...
class AzureOpenAIEmbedder(BaseEmbedder):
"""Drop-in Azure OpenAI embedder for production semantic search."""
def embed(self, text: str) -> list[float]: ...Swap
HashingEmbedderβAzureOpenAIEmbedderinconfig.pyfor production-grade semantic search. The interface is identical.
pip install -r requirements-dev.txt
pytest tests/ --cov=app --cov-report=term-missing29 tests Β· 85% coverage
| Module | Tests | What's Covered |
|---|---|---|
intent_agent.py |
4 | All 4 intent labels, ambiguous input |
billing_agent.py |
3 | Grounded answer, citation, stub fallback |
plan_agent.py |
3 | Plan comparison, multi-chunk context |
troubleshooting_agent.py |
3 | Step extraction, empty retrieval |
escalation_agent.py |
5 | Keyword rule, numeric threshold, both triggers |
retriever.py |
4 | Match, no-match, soft fail to [] |
orchestrator.py |
4 | Full pipeline, error handling |
api.py |
3 | /chat endpoint via FastAPI TestClient |
Every agent accepts an injectable llm_call parameter. The full pipeline is unit-testable without touching a real Azure OpenAI deployment:
# In tests β inject a stub instead of Azure OpenAI
def stub_llm(system_prompt: str, user_message: str) -> str:
return "STUB: This is a test answer."
agent = BillingAgent(llm_call=stub_llm)
result = agent.answer("Why is my bill higher?")
assert result.confidence > 0
assert result.source_citation is not None# In production β real Azure OpenAI client is wired in only when
# all required Azure credentials are configured.
# app/main.py
ACTIVE_LLM_CALL = (
azure_llm_call
if all(
[
os.getenv("AZURE_OPENAI_API_KEY"),
os.getenv("AZURE_OPENAI_ENDPOINT"),
os.getenv("AZURE_OPENAI_API_VERSION"),
os.getenv("AZURE_OPENAI_DEPLOYMENT"),
]
)
else stub_llm
)
orchestrator = Orchestrator(llm_call=ACTIVE_LLM_CALL)AZURE_OPENAI_API_KEY=xxxxxxxxxxxxxxxx
AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com/
AZURE_OPENAI_API_VERSION=2024-12-01-preview
AZURE_OPENAI_DEPLOYMENT=<your-deployment-name>Required:
- Python: 3.11+
- pip: latest
Optional:
- AZURE_OPENAI_API_KEY: for real LLM answers (stubs work without it)
System:
- RAM: 4GB minimum
- Internet: only needed if using real Azure OpenAIgit clone https://github.com/AIstar007/telecom-support-agent.git
cd telecom-support-agent
python -m venv venv
source venv/bin/activate # macOS / Linux
# venv\Scripts\activate # Windows
pip install -r requirements.txt# Run once β or whenever your policy docs change
python -m app.rag.ingestcp .env.example .env# .env β full configuration
OPENAI_API_KEY=sk-your_key_here # optional β stubs work without it
# Retrieval settings
RETRIEVAL_TOP_K=3
CONFIDENCE_THRESHOLD=0.65
# Escalation keywords (pipe-separated)
MUTATING_KEYWORDS=refund|cancel|switch plan|close account|change planuvicorn app.main:app --reloadβ Zero setup required to see it work. Every agent uses deterministic stub logic when no
OPENAI_API_KEYis set. Routing, retrieval, and escalation are all real and testable immediately.
# Health check
GET /health
# Send a support message
POST /chatcurl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"session_id": "demo", "message": "why is my bill higher this month"}'Response β Grounded Answer:
{
"answer": "Your bill may be higher due to international roaming charges applied between the 3rdβ8th. Per our Billing Policy Β§4.2, roaming rates apply when your device connects to a non-home network.",
"source_citation": "billing_policy.md ## Roaming Charges",
"confidence": 0.87,
"escalated": false,
"intent": "billing"
}Response β Escalated:
{
"answer": null,
"escalated": true,
"reason": "account_mutating",
"ticket_reference": "TKT-8a3f91c2",
"message": "This request has been escalated to a human agent. Reference: TKT-8a3f91c2"
}docker build -t telecom-support-agent .
docker run -p 8000:8000 --env-file .env telecom-support-agent| Decision | Rationale |
|---|---|
| Offline hashing embedder | No model download; runs in network-locked environments. Swap-ready interface for semantic embeddings in production. |
Section-aware chunking (## splits) |
Topical coherence per chunk matters more for retrieval quality than raw chunk size. |
| Deterministic escalation gate | The model proposes answers. It never decides on its own that it's confident enough. Keyword rule catches mutating requests; numeric threshold catches low-certainty answers. |
Injectable llm_call |
Full pipeline testable without hitting a real API. Same mechanism conditionally wires in real OpenAI client when key is present. |
| No LangChain | Explicit agent logic, no abstraction overhead β every step is traceable and independently testable. |
| Feature | Status |
|---|---|
| Real DTDL policy corpus | π Planned |
| Auth + account-lookup integration | π Planned |
| Semantic embeddings (OpenAI / sentence-transformers) | π Planned (interface ready) |
| Streaming responses | π Planned |
| Multi-turn conversation memory | π Planned |
| Layer | Technology |
|---|---|
| API Framework | FastAPI |
| Vector Store | ChromaDB |
| LLM | OpenAI β optional, stubs work without it |
| Embeddings | Offline hashing embedder (swap-ready for semantic) |
| Testing | pytest + FastAPI TestClient |
| CI | GitHub Actions |
| Containerization | Docker |
Built with β€οΈ for Deutsche Telekom Digital Labs β The Talent Hack, AI Engineer Track
π Star this repo if you value production-ready AI design! Β· π Report Issues Β· π‘ Request Features
π License: MIT Β· π¨βπ» Author: Alen Thomas (AIstar007)