Nutrition Research Assistant
Enables natural-language nutrition research for Indian foods, combining a curated knowledge base with hybrid RAG and specialized agents. Provides MCP tools for knowledge search, safe calculations, document retrieval, and optional live web search.
README
Nutrition Research Assistant
Production-grade multi-agent Indian nutrition research assistant: answers questions about Indian food and nutrition by combining a curated internal knowledge base, hybrid RAG, specialized agents, MCP tools, conversational memory, optional live web search, graceful failure handling, and end-to-end OpenTelemetry-compatible tracing.
graph TD
Client[CLI / Swagger / Demo script] --> API[FastAPI :8000]
API --> Sup[Agno Supervisor]
Sup --> KA[Knowledge Agent]
Sup --> CA[Calculator Agent]
Sup --> DA[Document Agent]
Sup --> WA[Health/Web Agent]
KA --> MCP[FastMCP Server :8001/mcp]
CA --> MCP
DA --> MCP
WA --> MCP
MCP --> Chroma[ChromaDB + BM25 + RRF + Reranker]
MCP --> Calc[Safe calculator]
MCP --> Web[Agno DDGSTools]
MCP --> Docs[documents.json]
Sup --> Mem[(SQLite memory)]
API --> OTel[OpenTelemetry + structlog]
Core principle (PRD §89): the LLM decides what should happen; MCP tools perform what must happen deterministically. Agents never touch Chroma directly — everything flows through MCP.
Tech Stack
| Layer | Choice |
|---|---|
| Python / packages | 3.12+ · uv (pyproject.toml + lock) |
| LLM | DeepSeek deepseek-v4-flash (OpenAI-compatible) |
| Agents | Agno (Supervisor + 4 specialized agents) |
| MCP | FastMCP server, streamable-http on :8001/mcp |
| Vector DB | ChromaDB (local, cosine) |
| Embeddings | BAAI/bge-small-en-v1.5 (local) |
| Reranker | BAAI/bge-reranker-base (local cross-encoder) |
| BM25 | rank_bm25 |
| Memory | Agno sessions + SQLite (data/app.db) |
| API | FastAPI + uvicorn |
| Observability | OpenTelemetry (console exporter; OTLP-swappable) + structlog |
| Web search | Agno built-in DDGSTools (optional) |
Repository Layout
corpus/ 8 curated nutrition documents (frontmatter + per-food tables)
scripts/ ingest.py · rebuild_index.py · test_retrieval.py · eval_questions.json
test_mcp_wiring.py · test_agents.py · test_memory.py · demo.py
src/
api/ FastAPI app, routes, schemas (chat / health / ready)
agents/ models.py (DeepSeek) · mcp_client.py · agents.py (specialists + supervisor)
mcp_server/ server.py + tools/{knowledge,calculator,documents,web}.py
rag/ embeddings · chroma · bm25 · fusion (RRF) · reranker · pipeline
memory/ SQLite storage
services/ circuit breaker · retry
observability tracing (OTel) · logging (structlog)
config/ settings.py core/ errors.py · models.py
cli.py REPL client
tests/ unit + API tests
Quick Start
# 1. Environment
uv venv --python 3.12
uv sync
# 2. Secrets
cp .env.example .env # set DEEPSEEK_API_KEY
# 3. Ingest the corpus (downloads bge-small-en-v1.5 on first run)
uv run python scripts/ingest.py
# 4. Start the MCP tool server (terminal 1)
uv run python -m src.mcp_server.server
# 5. Start the API (terminal 2)
uv run uvicorn src.api.app:app --reload --port 8000
Swagger UI: http://127.0.0.1:8000/docs (API port is configurable via API_PORT in .env — this workspace uses 8002 because 8000 is taken)
Try it:
uv run python -m src.cli # interactive REPL
uv run python scripts/demo.py # scripted 5-question demo
curl -X POST http://127.0.0.1:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"session_id":"demo","message":"How much protein is in 100g cooked chickpeas?"}'
API
| Endpoint | Purpose |
|---|---|
POST /api/v1/chat |
{session_id, message} → {status, session_id, response, sources[], trace_id} |
GET /health |
{status, mcp, chroma, llm} — liveness, no secrets |
GET /ready |
{status, checks} — alive vs ready distinction |
Degraded responses carry status: "degraded", a human-readable message, and the trace_id — never a 500 traceback.
MCP Tools (port 8001)
| Tool | Backend | Notes |
|---|---|---|
search_knowledge(query, top_k, filters) |
hybrid RAG (dense + BM25 + RRF + rerank) | returns chunks with document_id / score / method |
calculate(expression) |
AST-whitelisted safe evaluator | no eval, no code execution |
get_document(document_id) |
ingested documents.json |
never fabricates ids |
search_web(query) |
Agno DDGSTools |
optional; WEB_SEARCH_ENABLED |
search_health_information(query) |
DDGSTools + domain ranking | prioritizes WHO/ICMR/NIH/CDC |
Agents
| Agent | Tools | Handles |
|---|---|---|
| Supervisor | team of 4 | intent, routing, composition, memory |
| Knowledge | search_knowledge |
nutrition facts, comparisons, raw vs cooked |
| Calculator | calculate |
serving scaling, totals |
| Document | get_document |
document retrieval |
| Health/Web | web + health search | current info, general health (educational only) |
Failure Handling
- MCP down → controlled
degradedresponse with trace_id (no 500); circuit breaker fails fast, probes after cooldown (PRD §70/§85) - MCP timeout — 10s transport read timeout; agent run bounded by
AGENT_TIMEOUT_SECONDS - Chroma down → "Knowledge retrieval temporarily unavailable" (optionally falls back to web)
- LLM down / bad key →
LLM_UNAVAILABLE-style degraded message - Web disabled → "Live search is currently unavailable."
- Retry policy: 2 attempts with backoff, then degrade — never endless
Observability
Every request produces one trace: api.request → supervisor.run → mcp.<tool> → rag.dense_search / rag.bm25 / rag.rrf / rag.reranker, with trace_id echoed in the API response, logs, and spans. All logs are JSON (structlog) with timestamp, level, logger, trace_id, span_id. Set OTEL_EXPORTER_OTLP_ENDPOINT to export to Jaeger/Tempo/Collector (uv sync --extra otel).
Tests & Retrieval Eval
uv run pytest -q
uv run python scripts/test_retrieval.py --rerank # Recall@5/10, MRR, Hit@1/3 over 24 questions
The eval dataset (scripts/eval_questions.json, 24 questions across 7 categories) measures retrieval quality; the reranker lifts Hit@1/3 over raw fusion. Results land in data/eval/results.json.
Definition of Done (PRD §87)
- Core — FastAPI runs; DeepSeek via OpenAI-compatible client; Agno agents operational; FastMCP operational; Chroma populated; 8 documents; memory works
- MCP — all 5 tools work and agents use MCP as the real execution path
- RAG — dense + BM25 + RRF + cross-encoder rerank; metadata preserved; eval exists
- Agents — Supervisor + 4 specialists; multi-agent composition works
- Memory — session ids; follow-ups resolve; persisted in SQLite
- Observability — trace_id per request; nested spans; MCP/RAG/LLM/errors traced
- Failure handling — MCP unavailable/timeout, Chroma, LLM, web all controlled; no unhandled exception reaches the user
Troubleshooting
- Hugging Face symlink warning — Windows-only; set
HF_HUB_DISABLE_SYMLINKS_WARNING=1(cosmetic) - Retrieval returns nothing — Chroma empty: run
python scripts/ingest.py - "tool service unavailable" — MCP server not running (start it) or breaker cooling down (wait ~30s, or restart API)
- Health says llm not_configured —
DEEPSEEK_API_KEYmissing in.env
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。