ContactOS — Platform Architecture Overview
AI-powered multi-tenant customer service platform. Orchestrates conversations across WhatsApp, web, and voice channels
using LangGraph ReAct agents with deterministic tool execution, real-time analytics, and seamless human handoff.
1. Tech Stack
ContactOS is a full-stack Python + React application with MongoDB persistence and Redis for real-time state.
FrameworkFastAPI 0.115+ with Uvicorn ASGI
AI EngineLangGraph 0.2+ (ReAct pattern)
LLMClaude Haiku 4.5 (default), configurable per tenant
DatabaseMongoDB via Motor 3.3+ (async)
CacheRedis 5.0+ for sessions & real-time state
ValidationPydantic 2.5+ with SQLAlchemy 2.0+
LangChain Core 0.3+
LangChain OpenAI
LangChain Anthropic
Twilio
Google APIs
Boto3
FrameworkReact 19.2 + TypeScript 5.9
BuildVite 7.3
StateTanStack React Query 5.90 + Zustand 5.0
StylingTailwind CSS v4
ChartsECharts 6.0 (via @datastudios/viz)
Real-timeWebSocket with auto-reconnect (3s)
React Router 7.13
Lucide Icons
i18n (ES/EN)
JWT Auth
2. Five-Layer Architecture
Every message flows through five layers: channel ingestion, engine orchestration, LangGraph intelligence, tool execution, and channel delivery.
WhatsApp
Twilio webhook
Web Chat
HTTP POST
Voice AI
Future
NormalizedMessage
Unified contract
Session Mgr
Get or create
Reset Check
Greeting words
Handoff Resume
Inject agent history
Invoke Graph
LangGraph ainvoke()
Post-Process
Buttons + formatters
Chatbot Node
LLM response or tool calls
route_after_chatbot()
Router logic
Tools Node
Execute structured tools
Handoff Node
Route to live agent
validate_document
ID/email format
confirm_operator
Route to queue
confirm_services
Customer services
extract_services
Parse from docs
modify_services
Update details
write_services
Persist to DB
Each tool is a LangChain StructuredTool with Pydantic input schema, deterministic formatter, and button rules.
Tool set is tenant-specific — new tenants define their own tools.
EngineResponse
text + buttons + metadata
WhatsApp
Twilio API
Web HTTP
JSON response
Voice TTS
Future
3. Key Data Contracts
Three core Pydantic models define the boundaries between layers.
{
"phone_number": "+1234567890",
"text": "Hola, necesito ayuda",
"tenant_id": "tsg",
"channel": "whatsapp",
"message_id": "SM...",
"timestamp": "2026-03-18T14:30:00Z"
}
{
"messages": [BaseMessage...],
"tenant_id": "tsg",
"channel": "whatsapp",
"session_id": "uuid",
"phone_number": "+1234567890",
"user_data": {},
"needs_handoff": false,
"conversation_id": "mongo_id"
}
{
"text": "Formatted response...",
"buttons": [{"label": "Confirm", "action": "confirm"}],
"needs_handoff": false,
"session_ended": false
}
1
NormalizedMessage
Unified contract from all channel adapters. Strips channel-specific details (Twilio SIDs, HTTP headers) into a clean format the engine understands.
2
AgentState
TypedDict that flows through every LangGraph node. Contains conversation history (auto-merged), tenant selector, session ID, and handoff flags.
3
EngineResponse
Output contract with response text, UI buttons (attached by ButtonRules), and handoff/session flags. Channel senders use this to deliver via the original channel.
4. Database Model
MongoDB (contactos database) with Motor async driver. All collections scoped by tenant_id.
| Collection |
Purpose |
Key Fields |
lg_sessions |
LangGraph checkpoints — full graph state persistence |
session_id, checkpoint_data, created_at |
conversations |
Conversation records with status tracking |
tenant_id, phone_number, status, messages |
messages |
Full message log with sentiment scores |
conversation_id, role, text, sentiment |
contacts |
Customer directory |
phone_number, name, email, metadata |
agents |
Human agent roster and availability |
agent_id, name, status, assigned_conversations |
tenants |
Tenant configuration and metadata |
tenant_id, config, active_since |
ai_insights |
Auto-generated analytics summaries |
tenant_id, period, content, generated_at |
Accessed via DatabaseManager factory with per-collection DAOs:
ConversationDB, MessageDB, AgentDB, ContactDB, AnalyticsDB, TenantDB.
Connection configured via MONGO_URI or component env vars.
5. Multi-Tenancy
Lazy-loaded tenant registry. Each tenant is a self-contained directory with its own config, tools, prompts, and button rules.
Per-tenant AI behavior configuration.
Modelclaude-haiku-4-5-20251001
Temp0.3 (deterministic)
Expiry30 min idle timeout
Greetingshello, hi, hola, start...
TimezoneSource + display TZ
LangChain StructuredTool with Pydantic schemas. Each tool has a deterministic formatter that replaces LLM interpretation.
validate_document
confirm_operator
confirm_services
cancel_services
extract_services
modify_services
write_services
Declarative rules that attach UI buttons after tool execution. Match on tool results or text patterns.
MatchPattern/text/tool-result
Actionconfirm, edit, cancel
FormatList<ButtonRule>
6. Frontend Dashboard
React 19 SPA with a three-column layout: conversation list, chat thread, and customer detail panel.
Filterable conversation list with tab navigation.
TabsAll, Mine, Unassigned, AI
PollingReact Query (10s)
SearchPhone number, status filter
Real-time message thread with button actions and input.
RolesUser, Bot, System, Agent
Real-timeWebSocket /ws/chat (3s reconnect)
PollingReact Query (5s fallback)
Customer metadata, conversation status, and agent actions.
InfoName, phone, email, history
ActionsAssign, transfer, return to AI
AgentsStatus list with availability
7. Infrastructure & Deployment
Docker Compose orchestration with environment-variable-driven configuration.
Infrastructure Components
▶
FastAPI Backend
Port 3405 · Uvicorn ASGI
⚛
React Frontend
Port 5174 (dev) · Vite
📋
MongoDB
Port 27017 · Motor async
⚡
Redis
Sessions & real-time state
🔌
WebSocket
/ws/chat · same port as backend
| Environment Variable |
Purpose |
Required |
MONGO_URI |
MongoDB connection string (or MONGO_USER + MONGO_PASSWORD + MONGO_ENDPOINT) |
Yes |
ANTHROPIC_API_KEY |
Claude API key for LLM inference |
Yes |
REDIS_URL |
Redis connection for sessions and real-time state |
No (defaults localhost) |
WHATSAPP_TOKEN |
Twilio auth token for WhatsApp channel |
No (WhatsApp only) |
VERIFY_TOKEN |
Webhook verification token |
No (WhatsApp only) |
OPENAI_API_KEY |
Optional OpenAI key for alternative LLM providers |
No |
8. Testing Strategy
Four-level test pyramid with pytest markers. No real LLM calls in L1-L3.
| Level |
Marker |
Scope |
Tools |
| L1 Unit |
unit |
Pure functions, no IO |
ScriptedChatModel (mock LLM) |
| L2 Contract |
contract |
Interface contracts |
RecordingToolNode |
| L3 Flow |
flow |
Multi-turn conversation flows |
Real graph + mocked tools |
| L4 Integration |
integration |
End-to-end with backend + Redis |
Real backend, real tools |
9. Notable Design Decisions
Key architectural choices that shape the platform.
Deterministic Formatters
Tool execution results are formatted deterministically — the LLM never interprets critical outputs like service confirmations. This prevents hallucination on high-stakes data.
Handoff Resume
When a conversation returns from a human agent, the AI receives the full agent conversation history injected into state. Enables seamless context continuation.
Infinite Loop Guardrail
Counts consecutive no-tool AI responses. After 5 iterations without a tool call, forces handoff to a human agent to prevent loops.
MongoDB LangGraph Checkpointer
Full conversation state persisted to MongoDB via a custom checkpointer. Enables session resumption, history replay, and debugging across process restarts.
Spanish-First Localization
System prompts, tool outputs, and UI strings default to Spanish. Centralized locale layer at core/localization/ supports future language expansion.
Lazy-Loaded Tenants
Tenant configurations load on first access via _LazyDict to prevent circular imports. Adding a new tenant only requires a tenants/{id}/ directory.