RAG-项目实战一(基础篇)

5 阅读4分钟

基于HowToCook项目的菜谱数据,构建一个智能的食谱问答系统。用户可以:

  • 询问具体菜品的制作方法:"宫保鸡丁怎么做?"
  • 寻求菜品推荐:"推荐几个简单的素菜"
  • 获取食材信息:"红烧肉需要什么食材?"

一.数据处理

1.markdown文件父子分块策略

markdown文件结构很清晰,但实际使用上可能会遇到一个问题:

  • 如果按一级标题,二级标题,小标题...这样的结构细分,会导致上下文信息缺失,比如当用户问到“锅包肉怎么做?”,在检索时可能比较片面,只关注“操作”部分,而忽略了“原料和工具”,这样LLM就无法给出完整的回答。

  • 那么将整个md文档分块呢?这样的上下文信息肯定是完整的,但如果当用户问到某些细节,比如“锅包肉的原料和制作工具是哪些?”,如果这个问题的答案在文档中的占比很小,就会导致检索时排名靠后

针对上面的问题,可以采用父子分块策略:具体就是用小的子块进行检索,在生成时传递完整的文档(父块)给LLM。这样既保证了检索的精确性,又提高了生成时上下文的完整性。

2.流程

核心:结构化解析与“父子关系”确立

  • 路径标准化:使用 pathlib 处理跨平台路径,并通过相对路径的 MD5 哈希生成确定性的 parent_id

  • 元数据增强:从文件路径提取“菜系”,从正文提取“难度等级”,并自动识别“菜名”存入 metadata

  • 父子分块策略

    • 父文档:完整的 Markdown 食谱,保留全局上下文。
    • 子块 (Chunks) :利用 MarkdownHeaderTextSplitter 按标题(#、##)切分,确保每个分块语义完整。
    • 建立链接:每个子块均携带 parent_id,实现“小块检索,大块输出”。

二.索引构建及检索优化

1.索引构建

核心:高性能向量化存储

  • 嵌入模型:采用 BAAI/bge-small-zh-v1.5 模型,并开启 normalize_embeddings 以优化余弦相似度计算。

  • 向量库选择:使用 FAISS 进行高效的相似度检索。

  • 持久化管理:支持索引的本地保存 (save_local) 与加载 (load_local),避免重复调用 Embedding 接口,降低成本。

2.检索优化

核心:混合检索与 RRF 重排

  • 混合检索 (Hybrid Search)

    • 向量检索:负责“语义理解”,处理近义词提问。

    • BM25 检索:负责“关键词匹配”,处理专有名词或生僻菜名。

  • RRF 算法融合

    • 使用倒数排名融合(Reciprocal Rank Fusion)公式:

      Score=rR1k+rank(r)Score = \sum_{r \in R} \frac{1}{k + rank(r)}

    • 通过排名而非原始分数进行融合,解决向量空间与字面得分量纲不一的问题。

  • 元数据过滤 (Metadata Filter) :在检索结果中根据菜系、难度等标签进行硬性筛选。

三.生成集成

核心:意图路由与流式表达

  • 查询路由 (Query Router) :利用 LLM 将用户问题分类为 list(找菜名)、detail(看步骤)或 general(基础问答)。

  • 查询重写 (Query Rewrite) :将模糊的口语(如“好吃的”)重写为专业的搜索词(如“简单易做的家常菜谱”)。

  • 结构化 Prompt:针对烹饪场景设计模板,强制要求 LLM 输出“菜品介绍、所需食材、制作步骤、制作技巧”。

  • 上下文构建:自动拼接多个文档,并标注来源食谱、分类及难度。

  • 流式输出:支持 stream 模式,提升交互流畅感。

四.main整合

核心:业务逻辑闭环

  • 模块解耦:将数据、索引、检索、生成封装为独立模块,由 RecipeRAGSystem 类统一指挥。

  • 检索链路闭环

    • 用户提问 \rightarrow 意图路由。

    • 调用混合检索获取子块 (Child Chunks)

    • 通过 get_parent_documents 还原对应的父文档 (Parent Docs)

    • LLM 基于完整父文档生成分步回答。

  • 交互界面:提供命令行交互模式,支持环境变量检查和自动化的索引构建/加载流程。

参考文章 DataWhale All-in-RAG | 大模型应用开发实战一:RAG技术全栈指南

引用代码 github.com/datawhalech…