ChefMate RAG — 智能食谱 RAG 问答系统

RAGFastAPIDeepSeekFAISSPythonAstro

从 CLI 到 Web,一个完整 RAG 系统的工程化实践。基于 362 个真实菜谱,涵盖数据准备、向量索引、混合检索、查询路由、LLM 生成、指标可视化与生产部署的全链路实现。已上线运行于 vincentbuilds.fun

系统架构

用户输入 (vincentbuilds.fun/chefmate)

  ├──► 多轮对话解析 (指代消解 + 上下文注入)

  ├──► 查询约束分析 (份量/分类/难度提取)

  ├──► 查询路由 (LLM 分类: list/detail/general)

  ├──► 混合检索
  │     ├── FAISS 向量检索 (bge-small-zh-v1.5, 384d)
  │     ├── BM25 关键词检索
  │     ├── 元数据筛选 (份量/分类/难度)
  │     └── RRF 融合排序 (k=60)

  ├──► 父文档还原 (子块 → 完整菜谱)

  ├──► 上下文预算管理 (10,000 tokens)

  ├──► LLM 生成 (DeepSeek V4, 三种 Prompt 模板)
  │     ├── list → JSON 约束输出 (防幻觉)
  │     ├── detail → 结构化步骤教学
  │     └── general → 知识推理

  └──► 答案验证 → 指标计算 → 流式返回

部署架构

GitHub (allinweb)

  ├── git push → GitHub Actions CI
  │     ├── npm build → dist/
  │     ├── rsync dist/ → Alibaba Cloud
  │     └── rsync chefmate/ + data/ → Alibaba Cloud
  │     └── docker compose up -d --build

  └── Alibaba Cloud ECS
        ├── Nginx (443, SSL)
        │   ├── /          → /var/www/vincentbuilds (static)
        │   └── /api/*     → proxy_pass localhost:8000
        └── Docker
            └── FastAPI :8000 (127.0.0.1 only)

核心技术

技术栈

层级技术用途
向量检索FAISS + bge-small-zh-v1.5 (384d)语义相似度搜索
关键词检索BM25 (rank-bm25)精确关键词匹配
融合排序RRF (Reciprocal Rank Fusion, k=60)消除分数尺度差异
LLMDeepSeek V4 (deepseek-chat)路由 + 查询改写 + 生成
后端FastAPI + slowapi + PydanticAPI 服务 + 限流 + 校验
前端Astro v6 + Tailwind CSS v4静态站 + ChefMate SPA
部署Docker + Nginx + GitHub Actions容器化 + HTTPS + CI/CD

混合检索策略

# retriever.py — 核心检索逻辑
def hybrid_search(query, top_k=20, filters=None):
    fetch_k = max(top_k * 3, 15)  # 放大检索池

    vec_docs = FAISS.similarity_search(query, k=fetch_k)    # 语义
    bm25_docs = BM25.retrieve(query, k=fetch_k)             # 关键词

    # 元数据筛选(份量/分类/难度)
    if filters:
        vec_docs = apply_filters(vec_docs, filters)
        bm25_docs = apply_filters(bm25_docs, filters)

    # RRF 融合排序
    return rrf_rerank(vec_docs, bm25_docs)[:top_k]

为什么用 RRF? 向量检索 (0~1 相似度) 和 BM25 (非归一化分数) 的分数尺度不同,无法直接相加。RRF 基于排名而非原始分数,天然消除了尺度差异。

查询约束分析

# query_analyzer.py — 从自然语言提取结构化约束
"推荐3个简单的素菜,适合5个人吃"
  → servings=5, category="素菜", difficulty="简单"

"推荐适合十个人吃的量的菜"
  → servings=10  # 中文数字自动识别

"适合两个人吃的菜"
  → servings=2   # 小份量场景

约束同时用于:

  • 检索阶段(元数据筛选,优先返回份量匹配的菜谱)
  • 生成阶段(Prompt 注入约束提示)

关键功能

1. 幻觉防御

问题:LLM 在列表推荐时经常编造库中不存在的菜名。

方案:三重防线

防线实现效果
Prompt 约束JSON 结构化输出 + 白名单菜品列表LLM 只能从给定列表中选择
候选过滤从 valid_names 中物理剔除,LLM 看不到纯技术手段杜绝
后校验AnswerValidator 模糊匹配 (get_close_matches)容错 “宫宝鸡丁” → “宫保鸡丁”

2. 多轮对话

用户:推荐两个素菜
系统:素炒豆角 + 凉拌豆腐
用户:有没有不一样的   ← 上下文理解
系统:奶油蘑菇汤 + 上汤娃娃菜  ← 自动排除已推荐

指代消解 + 已推荐菜品追踪 + 上下文注入到 Prompt。

3. 份量感知

菜谱数据自动提取份量信息(“一份正好够 2 人吃”、“4-6 人份”),检索和推荐时作为硬约束。

4. RAG 指标可视化

每次回答展示:

  • 路由类型 — LLM 分类结果(步骤教学/列表推荐/通用问答)
  • 检索来源 — 匹配菜谱、语义得分、命中子块数
  • 耗时 — 端到端响应时间
  • 置信度 — 基于检索命中数计算

安全防护

防线措施配置
频率限制slowapi IP 限流10/min(chat)
日配额SQLite 计数DAILY_QUOTA=200
输入校验Pydantic + XSS 检测max_length=500
端口安全Docker 绑定 127.0.0.1公网不可达
CORS仅允许 vincentbuilds.fun同源策略
HTTPSNginx + Let’s EncryptTLS 1.2/1.3
API Key服务端 .env chmod 600前端不可见

本地开发

git clone https://github.com/8BitcloudBot/allinweb.git
cd allinweb

# 后端
cp .env.example .env   # 填入 DEEPSEEK_API_KEY
uv sync
uv run uvicorn chefmate.server:app --reload --port 8000

# 前端(另一个终端)
npm install && npm run dev    # http://localhost:4321

V2 演进

ChefMate GraphRAG 版本已上线,基于 Neo4j 知识图谱支持多跳推理、食材搭配发现和相似菜品推荐。

查看 ChefMate GraphRAG →

注意:向量索引各环境独立,首次启动会自动构建(约 20 秒)。不要跨环境复制 vector_index/