---
title: CustomerCore API
emoji: π
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
---
π§ CustomerCore
Enterprise AI Customer Support Platform β Multi-Agent Triage, Churn Prediction & Real-Time Observability
π₯οΈ Live Demo β’
π MLflow Experiments β’
π¦ Model Registry β’
βοΈ CI/CD
---
## π What is CustomerCore?
CustomerCore is a **production-grade, multi-tenant B2B AI platform** that automatically triages customer support tickets using a network of 6 specialized AI agents. It classifies tickets, predicts customer churn risk, detects infrastructure outages, retrieves similar past cases, and routes everything to the right team β all in under 5 seconds.
**Not just a prototype** β it has CI/CD pipelines, containerized deployment on Hugging Face Spaces, MLOps model tracking via DagsHub, LLM observability via Langfuse, a privacy vault for PII masking, Constitutional AI safety guardrails, and a Human-in-the-Loop review system for high-risk decisions.
---
## π Live Infrastructure
| Service | Link | What it does |
|:--------|:-----|:-------------|
| π₯οΈ **Operations Console** | [HF Space](https://huggingface.co/spaces/saibalajiomg/customercore) | Live dashboard β submit tickets, view AI triage results |
| π **MLflow Experiments** | [DagsHub](https://dagshub.com/saibalajinamburi/CustomerCore.mlflow) | Training runs, metrics, model comparison charts |
| π¦ **Model Registry** | [DagsHub Models](https://dagshub.com/saibalajinamburi/CustomerCore/models) | Versioned churn model with Production/Staging labels |
| π **Dataset Versioning** | [DagsHub DVC](https://dagshub.com/saibalajinamburi/CustomerCore) | Raw + processed datasets tracked via DVC |
| βοΈ **CI/CD Pipelines** | [GitHub Actions](https://github.com/saibalajinamburi/CustomerCore/actions) | Lint, test, train, deploy β on every push |
---
## ποΈ How It All Connects β End to End
This is how a single support ticket flows through the entire system:
```mermaid
sequenceDiagram
autonumber
actor User as Client / Dashboard
participant API as FastAPI Server
participant Vault as Privacy Vault
participant Graph as LangGraph 6-Agent Pipeline
participant LLM as LLM Router (Local/Cloud)
participant ML as Churn Model (Random Forest)
participant DB as Supabase PostgreSQL
participant VDB as ChromaDB / pgvector
participant Trace as Langfuse + Prometheus
User->>API: Submit support ticket (JWT auth)
API->>API: Verify JWT β extract tenant_id + role
API->>Vault: Mask PII (emails, phones, names, credit cards)
Vault-->>API: Sanitized text
API->>DB: Save ticket (status: pending)
API-->>User: HTTP 202 Accepted
Note over API,Graph: Agent pipeline runs in background
API->>Graph: Dispatch ticket to 6-agent supervisor
Graph->>LLM: Classify Agent β categorize + assign priority
LLM-->>Graph: category=billing, priority=high
Graph->>DB: Memory Agent β recall past customer interactions
Graph->>VDB: RAG Agent β BM25 + vector hybrid search
VDB-->>Graph: Top 5 similar past resolutions
Graph->>ML: Churn Agent β predict churn probability
ML-->>Graph: churn_risk=0.73
Graph->>Graph: Incident Agent β check for outage patterns
Graph->>Graph: HITL Agent β should human review?
Graph->>LLM: Generate resolution using retrieved context
Graph->>Vault: Constitutional Policy check on AI response
Graph->>DB: Save final triage result + audit log
Graph->>Trace: Log full trace (latency, tokens, cost, decisions)
```
---
## π§ The 6-Agent Triage Pipeline
CustomerCore uses **LangGraph** to orchestrate 6 specialized agents in a **parallel + sequential hybrid pipeline** with conditional Human-in-the-Loop gating. The graph topology uses a **fan-out / fan-in pattern** where the Memory, RAG, Churn, and Incident agents execute concurrently after classification, then converge at the HITL gate:
```mermaid
graph LR
A[π© Ticket In] --> B[π·οΈ Classify]
B --> C[π§ Memory]
B --> D[π RAG]
B --> E[π Churn]
B --> F[π¨ Incident]
C & D & E & F --> G{π HITL Check}
G -->|Safe| H[β
Finalize]
G -->|Risky| I[βΈοΈ Human Review]
I --> H
```
| # | Agent | What it does | Key detail |
|---|-------|-------------|------------|
| 1 | **Classify Agent** | Categorizes ticket (Billing, Technical, Security, Account) and assigns priority (Low β Critical) | Uses SLA-Aware LLM Router β local Gemma for routine, cloud frontier for critical |
| 2 | **Memory Agent** | Recalls past interactions for this customer | Uses **Mem0** with tenant-scoped identifiers |
| 3 | **RAG Agent** | Hybrid search β BM25 keyword + ChromaDB vector retrieval with **Reciprocal Rank Fusion** (RRF) | Optional cross-encoder reranking via `ms-marco-MiniLM-L-6-v2` |
| 4 | **Churn Agent** | Runs customer features through our trained **Random Forest** model | Features: monthly spend, tenure, ticket count, contract months |
| 5 | **Incident Agent** | Analyzes recent ticket frequency to detect systemic outages | Spike detection across the same tenant |
| 6 | **HITL Agent** | Flags tickets for human review | Triggers when: confidence < 0.65, priority is critical, or safety policy violated |
When HITL flags a ticket, LangGraph's **`interrupt_before` checkpoint** pauses execution. The state is saved in memory. A human operator reviews from the dashboard and calls `resume_triage()` to continue. The LangGraph `MemorySaver` checkpointer makes this possible β it stores the full agent state at the interrupt point so execution can resume exactly where it stopped.
> **Performance gain**: Running Memory, RAG, Churn, and Incident agents concurrently instead of sequentially reduced pipeline latency by **~45%** (from ~7 seconds to ~3β4 seconds for complex tickets).
---
## π RAG β How Knowledge Retrieval Works
CustomerCore doesn't use plain vector search. It uses a **3-layer Graph-RAG** system:
```mermaid
graph TD
Q[User Query] --> V[Layer 1: Vector + BM25 Hybrid Search]
Q --> G[Layer 2: B2B Knowledge Graph - NetworkX]
Q --> S[Layer 3: SQL Analytics - DuckDB Gold Marts]
V --> |Top 5 similar tickets| M[Combined Context Builder]
G --> |Tenant profile + category trends| M
S --> |Ticket funnel + customer health| M
M --> LLM[LLM Prompt Injection]
```
| Layer | What it does | Why it matters |
|:------|:-------------|:---------------|
| **Vector + BM25** | ChromaDB dense search + BM25 sparse keyword search, merged via **Reciprocal Rank Fusion** | Dense catches semantics ("frustrated" β "angry"), BM25 catches exact terms ("error code 5012") |
| **Knowledge Graph** | NetworkX DiGraph connecting `Ticket β Tenant β Category` nodes | Answers "Why has acme-corp been escalating?" β pure vector search can't do this |
| **DuckDB Gold** | SQL queries on dbt-transformed Parquet marts | Returns structured analytics: ticket funnel, customer health daily, agent performance |
**Tenant Isolation**: Every ChromaDB query has a mandatory `where={"tenant_id": current_tenant}` filter. BM25 index is partitioned per tenant. Cross-tenant leakage is architecturally impossible.
---
## π€ Models β What We Built vs What We Call
### Models We Trained (Custom ML)
| Model | Algorithm | Task | Tracked on |
|:------|:----------|:-----|:-----------|
| **Churn Classifier** | Random Forest, Logistic Regression, Gradient Boosting | Predict customer churn probability from account features | [DagsHub MLflow](https://dagshub.com/saibalajinamburi/CustomerCore.mlflow) |
| **Support LLM (QLoRA)** | Llama 3 8B Instruct (PEFT / QLoRA) | Predict professional, tenant-specific ticket resolutions | Hugging Face Adapter Registry |
* **Churn Classifier**: The training pipeline (`src/ml/train_churn.py`) trains all 3 models, compares F1 scores, and **auto-promotes the best one to "Production"** in the DagsHub Model Registry. Logged artifacts include ROC curves, confusion matrices, and feature importance plots.
* **Support LLM (QLoRA)**: The serverless training pipeline (`src/ml/modal_train.py`) executes on-demand on an Nvidia A10G GPU using **Modal**, pulls historical resolved support tickets (`raw_text` and `suggested_resolution`) from Supabase, performs QLoRA fine-tuning in 4-bit NF4 double-quantization, and publishes the resulting LoRA adapter (`customercore-llama3-adapter`) to the Hugging Face Hub.
### Models We Call via API (LLM)
| Model | Provider | When it's used |
|:------|:---------|:---------------|
| **Gemma 3 4B** | Ollama (runs locally) | Classification, extraction, routine reasoning |
| **Gemini 2.5 Flash** | OpenRouter (cloud API) | Fast-tier cloud fallback β when Ollama unavailable |
| **Claude 3.5 Sonnet** | OpenRouter (cloud API) | Frontier-tier reasoning for critical/complex tickets |
### SLA-Aware LLM Router β Smart Model Selection
The router (`src/rag/router.py`) automatically picks the right model based on task type, priority, and runtime environment:
| Task | Low/Medium Priority | High/Critical Priority |
|:-----|:-------------------|:----------------------|
| Classify | π’ Local Gemma (~150ms, $0) | π’ Local Gemma |
| Extract | π’ Local Gemma | π’ Local Gemma |
| Reason | π’ Local Gemma | π΅ Cloud Frontier (Claude 3.5 Sonnet) |
| Action | π΅ Cloud Fast (Gemini 2.5 Flash) | π΅ Cloud Frontier |
> **Result**: ~80% of tickets are handled locally at **$0 cost** and **<200ms latency**. Only complex/high-risk tickets go to the frontier cloud model.
**Dynamic Local β Cloud Fallback**: The `LLMClient` detects the runtime environment at startup. If `SPACE_ID` (Hugging Face) or `APP_ENV=production` is set, local Ollama calls are transparently redirected to `openrouter/google/gemini-2.5-flash` with zero timeout delay. If local Ollama fails on a development machine (e.g., not running), the same fallback activates automatically.
SLA latency targets per priority: Critical β€200ms, High β€500ms, Medium β€1000ms, Low β€2000ms. Violations are tracked in Prometheus metrics.
### L2 Semantic Cache β Avoid Redundant LLM Calls
On top of model routing, a **two-layer semantic cache** (`src/rag/router.py`) intercepts calls before they reach the LLM:
| Layer | Storage | Hit condition | Benefit |
|:------|:--------|:--------------|:--------|
| **L1 Exact** | Redis in-memory dict | Same prompt hash | Sub-millisecond, $0 cost |
| **L2 Semantic** | Redis + embedding similarity | Cosine similarity > 0.92 | ~85% of near-duplicate tickets served from cache |
Cache hit ratio is tracked as a Grafana metric. Each cache hit saves ~$0.002β$0.04 in API costs and 150β800ms of latency.
---
## π Data Pipeline β How Data Flows
### Data Ingestion Strategy
We use the **stream-to-database-then-download** approach (industry standard):
```
HuggingFace Datasets (API)
β Stream to Redpanda (Kafka-compatible broker)
β Bronze Consumer (raw events)
β PII Masking (Presidio + spaCy)
β Silver Layer (cleaned)
β dbt transforms (DuckDB)
β Gold Layer (analytics-ready marts)
```
**Why not download directly?** In production, data comes from APIs, webhooks, and real-time events β not static files. Streaming through a message broker (Redpanda) decouples producers from consumers, handles backpressure, and enables replay. Supabase acts as the persistent storage layer (like S3 in AWS architectures).
### Datasets Used
| Dataset | Rows | License | Language | Purpose |
|:--------|:-----|:--------|:---------|:--------|
| Bitext Customer Support | 26,872 | CDLA-Sharing-1.0 | English | SaaS support Q&A (billing, technical, account) |
| Bitext Retail Banking | 25,545 | CDLA-Sharing-1.0 | English | Financial support Q&A (card, loan, compliance) |
| Amazon MASSIVE Intent | 34,542 | Apache 2.0 | DE/FR/ES | Real multilingual customer utterances |
| **Total** | **~87,000** | | **4 languages** | |
### Medallion Architecture (Bronze β Silver β Gold)
```
Bronze (raw events from Redpanda)
β
βββ PII masking applied (Presidio)
β
Silver (cleaned, PII-scrubbed events)
β
βββ dbt transformations (DuckDB adapter)
β
Gold (analytics-ready marts):
βββ customer_health_daily β daily tenant health scores
βββ ticket_funnel_daily β intake/processing/resolved funnel
βββ support_agent_performance β agent resolution metrics
βββ billing_failure_summary β payment failure patterns
βββ incident_severity_hourly β outage severity tracking
βββ retention_cohort_metrics β cohort-based churn analysis
βββ product_adoption_features β feature usage tracking
```
---
## π₯οΈ The Dashboard (Hugging Face Space)
The live dashboard at [huggingface.co/spaces/saibalajiomg/customercore](https://huggingface.co/spaces/saibalajiomg/customercore) is a **single-page app** built with vanilla HTML/CSS/JS, served directly from FastAPI.
### What you see:
- **Session Context Bar** β Tenant, Role, and Auth status at the top. Changing either instantly re-generates a JWT and enforces access rules
- **AI Triage Pipeline** β Submit tickets or use 1-click demo presets (billing issue, outage report, PII leak, etc.)
- **Prediction Cards** β Priority, Routing Team, Churn Risk %, Outage Detection
- **Suggested Resolution** β AI-generated response with KB citations from RAG
- **HITL Workspace** β Role-gated: `manager`+ sees the flagged ticket review queue; `support_agent` sees an explicit "Access Denied" panel. Switching roles instantly clears any previously loaded data β no stale reviews persist
- **System Health Panel** β Service status (Supabase β
, Redis, ChromaDB)
### Understanding the Session Context:
| Field | What it means |
|:------|:-------------|
| **Tenant** | The company/organization (e.g. "acme-corp", "globex", "initech"). All data is isolated per tenant β one tenant can never see another's tickets or triage history |
| **Role** | `support_agent` can submit tickets and view results. `manager` can additionally access the HITL Workspace to review and approve/override flagged AI decisions |
| **Session / Token** | Active JWT token status. The token carries `tenant_id` + `role` claims, signed with HS256. Auto-generated on the dashboard for demo purposes β in production this would come from Supabase Auth or Auth0 |
### How Authentication Works:
1. Dashboard lets you pick a tenant + role β auto-generates a JWT (dev mode)
2. JWT payload: `{"tenant_id": "acme-corp", "role": "support_agent", "exp": ...}`
3. Every API call includes `Authorization: Bearer `
4. Server verifies signature + expiry β extracts `tenant_id` β uses it for ALL data isolation
5. In production, tokens would come from Supabase Auth, Auth0, or similar
---
## π Privacy & Security
### PII Masking β Privacy Vault
Every ticket goes through PII masking **before** it hits the database or any LLM:
```
Input: "My email is john@acme.com and card 4111-1111-1111-1111, call me at +49 170 1234567"
Output: "My email is [EMAIL_REDACTED] and card [CREDIT_CARD_REDACTED], call me at [PHONE_REDACTED]"
```
- Uses **Microsoft Presidio** + **spaCy** NER running locally in-process
- Detects: emails, phone numbers, credit cards, names, SSNs, IBANs
- Supports reversible tokenization via **AES-256-GCM** encryption (authorized users can decrypt)
- Even Langfuse traces get PII-masked before being sent to cloud
### Constitutional AI Safety Engine
An 8-rule safety engine that checks every AI response **before** it reaches the customer:
| Rule | Severity | What it catches |
|:-----|:---------|:----------------|
| PII Protection | π΄ CRITICAL | Response leaks raw PII back |
| No Commitments | π‘ VIOLATION | "We will refund $200 by Friday" β creates legal liability |
| Language Consistency | β οΈ WARNING | Response in English when ticket was in German |
| Toxicity Guard | π΄ CRITICAL | Harmful, discriminatory, or abusive language |
| AI Identity | π΄ CRITICAL | AI denies being an AI when asked directly |
| Scope Limitation | π‘ VIOLATION | Gives legal, medical, or financial advice |
| No Hallucination | π‘ VIOLATION | Cites KB articles that don't exist |
| Competitor Neutral | β οΈ WARNING | Disparages competitors by name |
Two execution paths: **Fast path** (regex, <5ms) catches obvious violations. **Slow path** (LLM-based, ~500ms) handles nuanced cases like coded language or context-dependent toxicity.
### Multi-Tenant Data Isolation
- **JWT tokens** carry `tenant_id` β every API request is cryptographically bound to a tenant
- **ChromaDB**: Metadata filter `where={"tenant_id": X}` on every query
- **BM25 Index**: Physically partitioned per tenant (separate corpora)
- **Supabase**: Row-Level Security (RLS) policies enforce isolation at the database layer
- **Role-Based Access Control**: `support_agent` < `manager` < `admin` β HITL resume requires `manager`+
- **Frontend RBAC enforcement**: The HITL Workspace instantly clears previously loaded reviews and displays an "Access Denied" message the moment the active role is switched to `support_agent` β no stale data persists across role changes
---
## π MLOps Pipeline
### Training Flow
```
src/ml/train_churn.py
βββ Load dataset (DVC-tracked CSV from DagsHub)
βββ Feature engineering (monthly_spend, tenure_months, ticket_count, contract_months)
βββ Train 3 models:
β βββ Logistic Regression (baseline)
β βββ Random Forest (best performer)
β βββ Gradient Boosting
βββ Log params + metrics + artifact plots to MLflow @ DagsHub
βββ Compare F1 scores across all models
βββ Register best model β DagsHub Model Registry
βββ Auto-promote to "Production" stage
```
### What gets logged to DagsHub MLflow:
- **Parameters**: n_estimators, max_depth, criterion, test_size, random_state
- **Metrics**: Accuracy, F1-Score, Recall, Precision, AUC-ROC
- **Artifacts**: ROC Curve, Confusion Matrix Heatmap, Feature Importance Chart
- **Dataset**: Registered as an MLflow dataset input for full reproducibility
### Dataset Versioning (DVC)
Datasets are tracked with **DVC** and stored on DagsHub's S3-compatible remote. `dvc push` / `dvc pull` to sync. The `.dvc` files in the repo are pointers to the actual data files.
---
## ποΈ Architecture β Local vs Cloud
CustomerCore runs in two modes **without changing any application code** β the same business logic works in both:
| Component | π» Local Dev Mode | βοΈ Cloud Mode (HF Spaces) |
|:----------|:------------------|:--------------------------|
| **API Server** | `uvicorn` on `localhost:8080` | Docker container on HF Space (port 7860) |
| **Database** | SQLite file (`customercore.db`) | **Supabase PostgreSQL** (cloud-managed) |
| **Vector Store** | In-process ChromaDB (Docker) | **Supabase pgvector** |
| **LLM (routine)** | Ollama β Gemma 3 4B (local GPU/CPU) | Ollama β Gemma 3 4B |
| **LLM (complex)** | OpenRouter β Llama 3.1 8B | OpenRouter β Llama 3.1 8B |
| **PII Masking** | Local Presidio + spaCy | Local Presidio + spaCy (in-container) |
| **Cache** | Redis (Docker container) | **Upstash Redis** (serverless) |
| **Event Streaming** | **Redpanda** broker (Docker) | Async background tasks |
| **Object Storage** | **MinIO** (S3-compatible, Docker) | DagsHub DVC remote |
| **Metrics** | Prometheus + Grafana (Docker) | OpenTelemetry β Grafana Cloud |
| **LLM Tracing** | Console debug logs | **Langfuse Cloud** (full trace UI) |
| **ML Tracking** | Local MLflow server | **DagsHub MLflow** (cloud) |
| **Secrets** | `.env` file | **Doppler** (secrets management) + HF Space Secrets |
| **Deployment** | `uvicorn --reload` | GitHub Actions β HF Space (auto-deploy) |
### How Cloud Mode Works (HF Spaces):
1. Push to `main` branch triggers GitHub Actions
2. `hf_deploy.yml` workflow syncs code to HF Space repo
3. HF builds the Docker image from `Dockerfile.hf`
4. Container starts: runs database migrations β starts uvicorn β serves dashboard
5. Secrets (Supabase URL, API keys, etc.) are injected as HF Space secrets
6. Supabase handles persistent data β the HF container is stateless
7. Readiness check: `/ready` endpoint validates Supabase connectivity
### Why Some Services Are "Degraded" in Cloud:
In the HF Space, Redis and ChromaDB show as "offline" in the health check β this is expected. The cloud mode uses Supabase pgvector instead of ChromaDB, and Upstash Redis instead of local Redis. The `/ready` endpoint returns 200 as long as Supabase (the critical service) is connected.
---
## βοΈ CI/CD β GitHub Actions
Three workflows run automatically:
| Workflow | Trigger | What it does |
|:---------|:--------|:------------|
| **CI Pipeline** (`ci.yml`) | Push to `main` or PR | Install deps β Lint with **Ruff** β Run **pytest** (307 tests) β Upload failure logs |
| **ML Training** (`train.yml`) | Push to `main` | Train churn models β Log to DagsHub MLflow β Post **CML report** as PR comment with metrics |
| **HF Deploy** (`hf_deploy.yml`) | Push to `main` | Sync entire repo to Hugging Face Space β Triggers Docker rebuild β Live in ~2 min |
> **Note**: All 3 workflows trigger on every push to `main`, including README changes. The ML training re-run ensures model registry stays in sync. If you only want to update docs without triggering training, you can push to a non-main branch first.
---
## π Observability Stack
### Langfuse β LLM Observability
Every LLM call is traced with a hierarchical structure:
```
Trace: "ticket-triage" (one per request)
βββ Span: "classify_agent"
βββ Generation: "gemma3-4b" (model, prompt, completion, tokens, cost, latency)
βββ Span: "rag_agent"
βββ Event: "rag_retrieval" (query, num_results, cache_hit)
βββ Generation: "llama-3.1-8b"
βββ Score: constitutional_compliance = 0.95
βββ Score: resolution_quality = 0.82
βββ Score: rag_grounding = 1.0
```
- PII is stripped from all trace data before sending to Langfuse
- **LiteLLM integration**: `litellm.success_callback = ["langfuse"]` auto-traces every LLM call β zero code changes needed
- Graceful degradation: when Langfuse keys are absent, NoOp stubs are used β tests pass offline
### Prometheus + Grafana β System Metrics
```bash
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d
# Grafana: http://localhost:3001 | Prometheus: http://localhost:9090
```
Pre-built dashboard panels:
- π **Triage ingestion rate** β tickets/second (success vs pending vs error)
- β±οΈ **p95 latency profiles** β duration distribution by priority
- π€ **LLM call reliability** β volume by model type and status
- π° **LLM cost tracker** β cumulative API spend in USD
- π― **Semantic cache hit ratio** β L1/L2 cache performance
- π **HITL reason distribution** β why tickets are being flagged
### OpenTelemetry Collector
The OTel collector (`infra/otel-collector.yaml`) scrapes Prometheus metrics from the FastAPI `/metrics` endpoint and forwards them to Grafana Cloud for cloud-mode observability.
---
## π Multilingual Support
CustomerCore supports **4 languages** out of the box: English, German, French, Spanish.
- **Language detection**: `langdetect` library identifies the ticket language
- **Translation**: Multilingual model translates non-English tickets before classification
- **Response language**: Constitutional AI policy ensures the response matches the ticket language
- **Training data**: Amazon MASSIVE dataset provides real customer utterances in DE/FR/ES (not translations β real native utterances)
---
## π§ͺ Testing
### Test Categories
| Type | Files | What it covers |
|:-----|:------|:--------------|
| **Unit Tests** | 13 files in `tests/unit/` | Every module: API routes, agents, RAG, vault, policy, router, multilingual, supervisor, streaming |
| **Integration Tests** | `tests/integration/` | End-to-end API flows with real HTTP calls |
| **Adversarial Red Team** | `tests/adversarial_red_team.py` | Prompt injection, jailbreak attempts, PII extraction attacks |
### Running Tests
```bash
pytest tests/ -v # All tests
pytest tests/unit/ -v # Unit only
pytest tests/adversarial_red_team.py -v # Security tests
```
---
## ποΈ Infrastructure
### Docker Compose Stack (Local)
```bash
docker compose up -d # Start everything
```
| Service | Port | Purpose |
|:--------|:-----|:--------|
| **Redpanda** | 9092 | Kafka-compatible message broker |
| **Redpanda Console** | 8080 | Visual dashboard for topics/messages |
| **MinIO** | 9000/9001 | S3-compatible object storage |
| **ChromaDB** | 8000 | Vector database for RAG |
| **Redis** | 6379 | Semantic cache + rate limiter |
| **OTel Collector** | 4317/4318 | Telemetry pipeline |
### Kubernetes (Production-Ready)
K8s manifests in `infra/k8s/`:
- `namespace.yaml` β `customercore` namespace
- `fastapi-deployment.yaml` β 2-replica deployment with health probes
- `secrets.yaml` β Doppler-injected secrets
- `ingress.yaml` β NGINX ingress for external access
- `pdb.yaml` β Pod Disruption Budget (min 1 available)
Can be deployed to a local Kind cluster (`infra/kind-config.yaml`) or any cloud K8s.
---
## π Secrets Management
All secrets are managed via **Doppler** (cloud secrets manager):
```bash
doppler run -- uvicorn src.api.main:app # Injects all secrets as env vars
doppler run -- python src/ml/train_churn.py # Same for training
```
Required secrets (set in Doppler or as env vars):
- `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`
- `OPENROUTER_API_KEY` (for cloud LLM calls)
- `LITELLM_MASTER_KEY` (also used as JWT signing key)
- `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`
- `MLFLOW_TRACKING_USERNAME`, `MLFLOW_TRACKING_PASSWORD` (DagsHub)
- `HF_TOKEN` (for Hugging Face deployment)
- `UPSTASH_REDIS_URL` (cloud Redis)
---
## π Project Structure
```
CustomerCore/
βββ src/
β βββ agent/ # LangGraph multi-agent triage
β β βββ supervisor.py # StateGraph: 6 nodes + HITL interrupt + MemorySaver
β β βββ state.py # AgentState TypedDict (shared state across all agents)
β β βββ schemas.py # TriageOutput Pydantic model (validated output)
β β βββ nodes/
β β βββ classify_agent.py # Ticket categorization + priority assignment
β β βββ memory_agent.py # Mem0 tenant-scoped customer memory recall
β β βββ rag_agent.py # Hybrid retrieval + resolution generation
β β βββ churn_agent.py # ML model inference for churn prediction
β β βββ incident_agent.py # Outage spike detection
β β βββ hitl_agent.py # Human-in-the-loop gating logic
β βββ api/ # FastAPI application layer
β β βββ main.py # App factory, CORS, lifespan events
β β βββ auth.py # JWT verification + RBAC + tenant extraction
β β βββ ui.py # SPA dashboard (HTML/CSS/JS served from Python)
β β βββ routers/
β β βββ triage.py # POST /triage, GET /triage/{id}, POST /triage/{id}/resume
β β βββ health.py # /health (liveness), /ready (readiness)
β β βββ metrics.py # Prometheus /metrics endpoint
β β βββ stream.py # SSE streaming for real-time triage status
β βββ rag/ # Retrieval-Augmented Generation engine
β β βββ hybrid_retriever.py # BM25 + ChromaDB + Reciprocal Rank Fusion + cross-encoder reranking
β β βββ graph_rag.py # 3-layer Graph-RAG: Vector + NetworkX Graph + DuckDB SQL
β β βββ router.py # SLA-aware multi-model LLM router with cost tracking
β β βββ llm_client.py # LiteLLM wrapper: Ollama (local) + OpenRouter (cloud)
β β βββ multilingual.py # Language detection + cross-lingual translation
β βββ ml/
β β βββ train_churn.py # Multi-model training + MLflow + auto-promote to Production
β β βββ deploy_to_hf.py # Automated Hugging Face Space code sync
β βββ responsible_ai/
β β βββ privacy_vault.py # AES-256-GCM PII masking (Presidio + spaCy NER)
β β βββ constitutional_policy.py # 8-rule Constitutional AI safety engine
β β βββ audit_log.py # Durable audit trail (every decision logged)
β β βββ key_manager.py # Encryption key lifecycle management
β β βββ model_cards/ # ML model documentation cards
β βββ db/
β β βββ repository.py # Supabase CRUD (tickets, customers, audit events)
β β βββ migrations.py # Auto-DDL: creates tables on startup if missing
β βββ monitoring/
β β βββ langfuse_tracer.py # Full LLM observability: Trace β Span β Generation β Score
β βββ streaming/
β β βββ data_loader.py # Downloads 87K records from HuggingFace β Redpanda
β β βββ producers/ # Synthetic event generators for testing
β β βββ producer_helper.py # Kafka producer wrapper
β β βββ bronze_consumer.py # Raw event consumer from Redpanda
β β βββ bronze_to_silver.py # PII masking + data cleaning pipeline
β β βββ triage_consumer.py # Triggers agent pipeline from stream events
β β βββ minio_setup.py # S3 bucket initialization
β βββ dbt/ # Data transformations (DuckDB adapter)
β βββ dbt_project.yml
β βββ profiles.yml
β βββ models/gold/ # 8 Gold mart models (health, funnel, agents, billing, etc.)
βββ tests/
β βββ unit/ # 13 test files, 300+ tests
β βββ integration/ # E2E API tests
β βββ adversarial_red_team.py # Prompt injection + jailbreak test suite
β βββ conftest.py # Shared fixtures
βββ infra/
β βββ k8s/ # Kubernetes: deployment, ingress, PDB, secrets
β βββ monitoring/ # Grafana dashboard JSON + Prometheus config
β βββ kind-config.yaml # Local multi-node K8s cluster
β βββ otel-collector.yaml # OpenTelemetry pipeline config
βββ .github/workflows/
β βββ ci.yml # Lint (Ruff) + Test (pytest) pipeline
β βββ train.yml # ML training + CML report
β βββ hf_deploy.yml # Auto-deploy to Hugging Face Spaces
βββ docker-compose.yml # Full local stack (Redpanda, MinIO, ChromaDB, Redis, OTel)
βββ docker-compose.monitoring.yml # Prometheus + Grafana
βββ Dockerfile # Full production image
βββ Dockerfile.hf # Slim image for Hugging Face Spaces
βββ requirements.txt # Production dependencies
βββ requirements-ci.txt # CI/test dependencies (CPU-only PyTorch)
βββ data/ # DVC-tracked datasets (.dvc pointer files)
```
---
## π οΈ Full Tech Stack
| Category | Tools |
|:---------|:------|
| **Language** | Python 3.12 |
| **API Framework** | FastAPI, Uvicorn, Pydantic v2, SlowAPI (rate limiting) |
| **AI Agents** | LangGraph (StateGraph, MemorySaver, interrupt checkpoints) |
| **LLM Orchestration** | LangChain, LiteLLM (unified interface for local + cloud) |
| **LLMs** | Ollama (Gemma 3 4B β local), OpenRouter (Llama 3.1 8B β cloud) |
| **ML Training** | scikit-learn, MLflow, DagsHub, DVC |
| **RAG** | ChromaDB (dense), rank-bm25 (sparse), Reciprocal Rank Fusion, sentence-transformers (reranker) |
| **Graph RAG** | NetworkX (knowledge graph), DuckDB (SQL analytics) |
| **NLP / PII** | spaCy (NER), Microsoft Presidio (PII detection), langdetect |
| **Database** | Supabase (PostgreSQL + pgvector + RLS), SQLite (local fallback), DuckDB (analytics) |
| **Auth** | PyJWT (HS256), RBAC, Supabase Row-Level Security |
| **Event Streaming** | Redpanda (Kafka-compatible, no JVM, no ZooKeeper) |
| **Object Storage** | MinIO (local S3), DagsHub DVC remote (cloud) |
| **Caching** | Redis / Upstash Redis β L1 exact cache + L2 semantic cache (cosine similarity > 0.92) |
| **Observability** | Prometheus, Grafana, OpenTelemetry Collector, Langfuse |
| **Infrastructure** | Docker, Docker Compose, Kubernetes (Kind), Hugging Face Spaces |
| **CI/CD** | GitHub Actions, CML (Continuous Machine Learning), Ruff (linter) |
| **Data Transforms** | dbt (DuckDB adapter) β 8 Gold mart models |
| **Security** | AES-256-GCM encryption, Constitutional AI (8-rule safety engine), frontend RBAC enforcement |
| **Secrets** | Doppler (cloud secrets manager), Hugging Face Space Secrets |
| **Logging** | structlog (structured JSON logs) |
---
## π Quick Start
### 1. Clone & Install
```bash
git clone https://github.com/saibalajinamburi/CustomerCore.git
cd CustomerCore
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
```
### 2. Set Environment Variables
Create a `.env` file or use Doppler:
```bash
# Required for cloud features (optional for local-only mode)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
LITELLM_MASTER_KEY=your-key # Also used as JWT signing secret
OPENROUTER_API_KEY=your-key # For cloud LLM calls
```
### 3. Start Local Infrastructure
```bash
docker compose up -d # Redpanda, ChromaDB, Redis, MinIO, OTel
```
### 4. Run the API
```bash
uvicorn src.api.main:app --host 0.0.0.0 --port 8080 --reload
```
### 5. Open Dashboard
- **Dashboard**: `http://localhost:8080`
- **Swagger API Docs**: `http://localhost:8080/docs`
### 6. (Optional) Load Training Data
```bash
python -m src.streaming.data_loader --sources all --limit 500
```
### 7. (Optional) Train ML Models
```bash
doppler run -- python -X utf8 src/ml/train_churn.py
```
### 8. Run Tests
```bash
pytest tests/ -v
```
### 9. (Optional) Run Serverless GPU LLM Fine-Tuning
```bash
doppler run -- modal run src/ml/modal_train.py
```
---
## β οΈ Challenges Faced & How We Solved Them
| Challenge | Root Cause | Solution |
|:----------|:-----------|:---------|
| **Langfuse SDK v4 broke LiteLLM** | LiteLLM v1.85 calls `langfuse.version` and `Langfuse.trace()` which don't exist in SDK v4 | Wrote monkeypatches in `langfuse_tracer.py` that shim the old API onto the new SDK β `mock_trace()`, `mock_span_method()`, `mock_generation_method()` |
| **CI failing on PyTorch** | Full PyTorch is 2GB+ and slows CI to 15 min | Created `requirements-ci.txt` with CPU-only PyTorch from `download.pytorch.org/whl/cpu` |
| **HF Space shows "degraded"** | Health check expects Redis + ChromaDB locally, but cloud uses Upstash + pgvector | Modified `/ready` endpoint to return 200 as long as Supabase (critical path) is connected |
| **Cross-tenant data leakage risk** | Naive vector search returns results from all tenants | Enforced `where={"tenant_id": X}` on every ChromaDB query + physically partitioned BM25 index per tenant |
| **PII in LLM traces** | Langfuse receives full prompt text which could contain customer PII | Added `_mask_pii()` regex pipeline that strips emails, phones, SSNs, IBANs, credit cards before any data leaves the process |
| **UTF-8 encoding on Windows** | Python on Windows defaults to CP1252 β MLflow logging fails with emoji/special chars | Set `PYTHONIOENCODING=utf-8` and use `python -X utf8` flag |
| **CML reports on every push** | ML training workflow triggers even on README changes | Accepted trade-off: ensures model registry is always in sync. Can be scoped to `src/ml/**` paths if needed |
| **Database schema drift** | New columns added in code but HF Space container has old schema | Added `migrations.py` that runs `ALTER TABLE ADD COLUMN IF NOT EXISTS` on every startup |
| **Supabase direct port 5432 blocked in cloud** | IPv6-only resolution on direct PostgreSQL connections times out on HF Spaces (IPv4 only) | Switched migrations to Supabase **Connection Pooler** on port `6543` β IPv4-compatible, stable in all cloud environments |
| **Ollama unavailable in cloud containers** | HF Spaces don't ship with Ollama β local model calls time out after several seconds | `LLMClient` detects `SPACE_ID` / `APP_ENV=production` at startup and transparently redirects local tier calls to `openrouter/google/gemini-2.5-flash` |
| **Stale HITL data persists after role switch** | Switching from `manager` to `support_agent` in the demo left previously fetched review rows visible | Added an immediate role guard in `generateToken()` that clears the HITL table and shows an "Access Denied" banner the instant the role changes to `support_agent` |
| **Misleading DB timeout error messages** | When Supabase was unreachable, the UI catch block printed "Ensure role is manager" β wrong root cause | Updated catch blocks to distinguish `NetworkError` / `ENOTFOUND` from auth errors and display an accurate "Database unreachable" message with guidance |
| **Gated base model downloads on HF** | Hugging Face gated models (like `meta-llama/Meta-Llama-3-8B-Instruct`) returned a `403 Forbidden` error on serverless containers without user authorization | Switched base model to `NousResearch/Meta-Llama-3-8B-Instruct`, a public non-gated clone with identical architecture and weights |
| **Missing dependency in Python 3.12 container** | The remote training process crashed with `ModuleNotFoundError: No module named 'setuptools'` | Added `setuptools` to the container `pip_install` settings in `modal_train.py` for Python 3.12 compatibility |
| **Schema mapping & status issues in training script** | Direct queries failed due to non-existent columns (`text`) and incorrect status mapping (`completed` vs check constraints) | Replaced the column selection with `raw_text` and changed the status filter criteria to `'complete'` |
---
## π EU AI Act Compliance
| Article | Requirement | Implementation |
|:--------|:-----------|:---------------|
| **Art. 10** | Data governance & PII masking | Privacy Vault: Presidio + spaCy masks all PII before storage/LLM calls. AES-256-GCM reversible tokenization |
| **Art. 12** | Audit traceability | Every decision logged to `audit_events` table with timestamp, tenant, input, output, model used |
| **Art. 14** | Human oversight | LangGraph interrupt checkpoint pauses high-risk decisions for human review before execution |
| **Art. 15** | Fairness & security | Adversarial red-team tests, model cards, 8-rule Constitutional AI safety engine, RBAC |
---
## π License
MIT License β see [LICENSE](LICENSE) for details.