kelvinator4leo/meridian-chatbot
Meridian Support — AI Customer Support Chatbot
A production-grade prototype that lets Meridian Electronics customers check product availability, place orders, look up order history, and authenticate themselves — all through a chat interface backed by an MCP server.
Architecture
┌──────────────┐ ┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Chainlit │ │ Agent loop │ │ MCP client │ │ Meridian MCP │
│ chat UI │ ───▶ │ (openai-agents │ ───▶ │ (Streamable HTTP │ ───▶ │ server │
│ │ │ SDK) │ │ transport) │ │ │
│ - streaming │ │ - tool calls │ │ - dynamic discovery │ │ - products │
│ - cl.Step │ │ - structured │ │ - tool dispatch │ │ - orders │
│ - sessions │ │ output │ │ │ │ - auth │
└──────────────┘ └─────────────────┘ └──────────────────────┘ └─────────────────┘
│ │
│ ├── Guardrails (input scrubbing, prompt-injection defense)
│ ├── Auth state in cl.user_session (verified email only)
│ └── System prompt scoped to Meridian + safety rules
└── LangFuse traces (every turn, every tool call, every token)Three layers, one job each:
- UI layer (Chainlit): chat surface, streaming, step visualization for tool calls — what the customer sees.
- Agent layer (openai-agents SDK): the LLM loop, tool selection, structured output. Guardrails sit at this boundary so injection attempts and unauthenticated account requests are stopped before the model decides to call a tool.
- Tool layer (MCP, Streamable HTTP): the chatbot has zero hard-coded business logic. It discovers tools dynamically from the MCP server at startup. Adding a new capability is a server-side change — no chatbot redeploy.
Stack
Screenshots
The bot in action and behind the scenes. Click any thumbnail to view full-size.
Welcome screen
Product browse
LangFuse traces
Per-turn observability — model, prompt, response, token counts, latency, and cost for every LLM call.
OpenAI usage
Cost and request volume against the OpenAI API.
Deployment
The chatbot deploys to Hugging Face Spaces via GitHub Actions. Every push to main runs the test matrix; on success, the workflow force-pushes the repo to the Space's git remote, which triggers HF to rebuild the Docker image.
One-time setup
- Create the Space on huggingface.co (Spaces → Create new Space → SDK: Docker). Note the Space name.
- Generate a write token at https://huggingface.co/settings/tokens (scope:
Write).
- Add three GitHub Actions secrets (repo Settings → Secrets and variables → Actions → New repository secret):
- Add runtime secrets to the HF Space (Space → Settings → Variables and secrets):
Load testing
A make loadtest target wraps Apache Bench against /healthz:
chainlit run app.py # in one shell
make loadtest # in another (tweak via N=, C=, PORT=)The default 1000 requests at concurrency 50 saturates the single-container HF Space free tier well below its rated capacity; horizontal scaling is in future.md Strategic.
Curated log tailer
Pipe Chainlit's stdout through scripts/tail_logs.py for a colour-coded view of structured events (mcpcall, circuitopen, agent_decision, etc.):
chainlit run app.py 2>&1 | python scripts/tail_logs.py
# or filter to one event type:
chainlit run app.py 2>&1 | python scripts/tail_logs.py --filter agent_decisionStdlib only — no extra deps.
Health
GET /healthz returns a JSON payload with the status of the two upstreams the chat depends on (the MCP server and OpenAI). HTTP 200 when both reachable, 503 when either is degraded:
{
"status": "ok",
"checks": {
"mcp": {"status": "ok", "latency_ms": 234.1},
"openai": {"status": "ok", "latency_ms": 156.3}
}
}Suitable as the target for HF Spaces' health probe, UptimeRobot, or any external monitor. Public, no auth — payload reveals nothing sensitive.
Persistent sessions
A SQLite file at ./chainlit.db is auto-created on startup; no env var or external service is needed. Browser refresh resumes the conversation, the verified email, and the running cost-cap counter. HF Spaces caveat: the container filesystem is ephemeral, so the DB is wiped on every Space rebuild (i.e. every push to main). For durable production storage, attach an HF Persistent Storage volume and point _DB_PATH at the mount.
Deploy
Push to main. The Actions workflow runs lint + tests across Python 3.11/3.12, then deploys. The Space rebuilds in ~2-3 minutes; live URL is https://huggingface.co/spaces/<HF_USERNAME>/<HF_SPACE>.
Local Docker
docker build -t meridian-support .
docker run --rm -p 7860:7860 --env-file .env meridian-support
# open http://localhost:7860