A production-style FastAPI application using Semantic Kernel that routes customer support requests to specialised AI agents, queries a PostgreSQL (Pagila) database through typed tools, applies guardrails, and returns structured JSON.
Built for the Netsol AI CoE take-home assignment.
Request → TriageAgent → Specialist Agent → GuardrailAgent → AgentResponse
| Layer | Contents |
|---|---|
| Presentation | app/api/ – FastAPI router, Pydantic validation |
| Application | app/orchestration/, app/services/, app/agents/ |
| Domain | app/plugins/, app/repositories/, app/schemas/ |
| Infrastructure | app/db/, app/models/, Alembic migrations |
| Cross-cutting | app/logging/, app/middleware/, app/observability/ |
| Agent | Intent | Tool |
|---|---|---|
| TriageAgent | Routes all requests | — |
| CatalogAgent | catalog_search |
search_film_catalog |
| SubscriptionAgent | subscription_question |
get_customer_streaming_subscription |
| RentalHistoryAgent | rental_history |
get_customer_rental_history |
| KnowledgeAgent | knowledge_question |
search_kb |
| HumanHandoffAgent | human_handoff / unsafe_request |
create_handoff_ticket |
| GuardrailAgent | Post-processes every response | — |
git clone <repo-url>
cd multi-agent-ai-support
cd backend
pip install -e ".[dev]"
cd ..cp backend/.env.example backend/.env
# Edit backend/.env – set OPENAI_API_KEY and, if you want traces, Langfuse credentialsThe Pagila SQL files are already included under backend/scripts/. Start PostgreSQL with Docker Compose from the project root:
docker compose up db -dcd backend
alembic upgrade head
cd ..cd backend
python scripts/seed.py
cd ..cd backend
uvicorn app.main:app --reloadAPI is now available at http://localhost:8000.
curl -X POST http://localhost:8000/agent/respond \
-H "Content-Type: application/json" \
-d '{
"customer_id": 1,
"conversation_id": "conv_001",
"message": "Is Alien available for streaming?"
}'Response:
{
"conversation_id": "conv_001",
"intent": "catalog_search",
"selected_agent": "CatalogAgent",
"answer": "ALIEN | Rating: R | Rental Rate: $4.99 | Streaming: Yes",
"confidence": 0.95,
"tools_used": ["CatalogPlugin.search_film_catalog"],
"citations": [],
"next_action": "none",
"guardrail_result": "PASS"
}A local MCP server exposing two DB-backed tools:
search_film_catalogget_customer_streaming_subscription
# Install MCP CLI if needed
pip install mcp
# Run the MCP server
cd backend
python mcp_server/server.pyConnect from any MCP client using stdio transport.
cd backend
pytest tests/ -v
cd ..Tests cover: routing, plugins, repositories, API, guardrails, and migrations.
12 evaluation examples are in backend/evals/eval_cases.json, covering all required prompts from the assignment including safety scenarios (prompt injection, SQL injection, account mutation attempts).
The project uses the Pagila PostgreSQL sample database with three custom migrations:
| Migration | Change |
|---|---|
| 001 | ALTER TABLE film ADD COLUMN streaming_available BOOLEAN NOT NULL DEFAULT FALSE |
| 002 | Creates streaming_subscription table + seeds one active subscription |
| 003 | Creates conversation_message table for persistent conversation memory |
backend/app/
api/ FastAPI router and dependency injection
agents/ 7 specialist agents (TriageAgent, CatalogAgent, …)
orchestration/ SemanticKernelRouter – triage → specialist → guardrail
plugins/ 5 Semantic Kernel plugin classes (DB-backed)
prompts/ System prompt per agent
tools/ MCP metadata + ToolTracker
repositories/ Async Repository Pattern (SQLAlchemy)
schemas/ Pydantic v2 request/response/plugin schemas
models/ SQLAlchemy ORM models
db/ Engine, session factory, DeclarativeBase
services/ SupportService (thin application layer)
middleware/ Request ID, CORS
logging/ Structured logging (structlog)
observability/ Langfuse tracing helpers
backend/mcp_server/ pagila-support-mcp (senior signal)
backend/migrations/ Alembic async migrations
backend/docs/
kb/ Knowledge base markdown files
design.md Architecture decisions
implementation_plan.md Phased plan
ai_usage.md AI tooling disclosure
backend/evals/ 12 evaluation examples
backend/tests/ pytest test suite
backend/scripts/ Seed and utility scripts
frontend/ Streamlit PoC frontend
docs/ Assignment and architecture documentation
The application integrates Langfuse for end-to-end LLM observability.
Tracing is optional — the app runs normally without it (all trace calls fall back to no-ops).
| Span | Type | What you see |
|---|---|---|
support_request |
span | Full request — customer ID, intent, agent, guardrail result, tools used |
triage_agent |
generation | Triage input context + raw JSON classification output |
CatalogAgent / SubscriptionAgent / etc. |
generation | Specialist input message + answer |
guardrail_agent |
generation | Guardrail payload + PASS/FAIL result |
CatalogPlugin.search_film_catalog |
span | Query + result count + latency |
SubscriptionPlugin.get_customer_streaming_subscription |
span | Customer ID + found/not-found |
RentalPlugin.get_customer_rental_history |
span | Customer ID + record count |
KnowledgePlugin.search_kb |
span | Query + result count + latency |
HandoffPlugin.create_handoff_ticket |
span | Ticket ID + reason |
Every span includes latency in milliseconds and is nested under the parent support_request trace, giving you a full waterfall view per request in the Langfuse dashboard.
- Sign up at cloud.langfuse.com (free tier) or self-host using the included
langfuse/Docker Compose. - Create a project and copy the API keys.
- Add to
backend/.env:
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_BASE_URL=https://cloud.langfuse.com- Restart the API — tracing starts automatically. On shutdown, buffered events are flushed via
flush_tracing().
Leave the three env vars unset (or comment them out). The app logs a langfuse_disabled info message and continues with full functionality.
See docs/design.md for architecture decisions, tradeoffs, and MCP readiness strategy.
- Python 3.12+
- PostgreSQL 14+ with Pagila
- OpenAI API key (GPT-4o-mini or later)