基于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)公式:
-
通过排名而非原始分数进行融合,解决向量空间与字面得分量纲不一的问题。
-
-
元数据过滤 (Metadata Filter) :在检索结果中根据菜系、难度等标签进行硬性筛选。
三.生成集成
核心:意图路由与流式表达
-
查询路由 (Query Router) :利用 LLM 将用户问题分类为
list(找菜名)、detail(看步骤)或general(基础问答)。 -
查询重写 (Query Rewrite) :将模糊的口语(如“好吃的”)重写为专业的搜索词(如“简单易做的家常菜谱”)。
-
结构化 Prompt:针对烹饪场景设计模板,强制要求 LLM 输出“菜品介绍、所需食材、制作步骤、制作技巧”。
-
上下文构建:自动拼接多个文档,并标注来源食谱、分类及难度。
-
流式输出:支持
stream模式,提升交互流畅感。
四.main整合
核心:业务逻辑闭环
-
模块解耦:将数据、索引、检索、生成封装为独立模块,由
RecipeRAGSystem类统一指挥。 -
检索链路闭环:
-
用户提问 意图路由。
-
调用混合检索获取子块 (Child Chunks) 。
-
通过
get_parent_documents还原对应的父文档 (Parent Docs) 。 -
LLM 基于完整父文档生成分步回答。
-
-
交互界面:提供命令行交互模式,支持环境变量检查和自动化的索引构建/加载流程。
参考文章
DataWhale All-in-RAG | 大模型应用开发实战一:RAG技术全栈指南