- 教程
- 人工智能
- 大模型
- RAG
【免费下载链接】all-in-rag
🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/
在 Datawhale 的 all-in-rag 项目中,第 8 章基于 HowToCook 风格的菜谱数据构建了一个名为"尝尝咸淡"的食谱问答 RAG 系统,而data/C8/cook/dishes/meat_dish/红烧肉/简易红烧肉.md正是该系统知识库中的一份典型数据样本。本篇文章以这份菜谱为绝对主体,先完整讲解其菜谱内容,再结合仓库源码(data_preparation.py 等)深入剖析它从一份普通 Markdown 文件,一步步转化为可精确检索、可完整生成的知识单元的全过程。读完你将掌握:结构化菜谱文档如何被加载与元数据增强、如何按 Markdown 标题层级分块、父子文本块架构如何保证"小块检索、大块生成",以及该系统如何运行验证。
一、这份菜谱文档的完整内容
简易红烧肉.md是知识库中"荤菜"分类下的一份菜谱,全文采用规范的 Markdown 结构:一级标题为菜品名,正文包含简介与难度评级,随后是"必备原料和工具"、"计算"、"操作"、"附加内容"四个二级章节。这种高度规整的结构,正是第 8 章数据准备模块能够直接按标题分块的前提(详见 02_data_preparation.md)。
1.1 菜品简介与难度
这份红烧肉教程是一道新手不败的菜谱。配着米饭好吃的停不下来,香糯无敌棒色泽诱人肥而不腻。建议搭配米饭食用。
文档中标注"预估烹饪难度:★★★"。这一星级标记并非普通文本,它会被数据准备模块的正则逻辑自动解析为结构化的难度元数据(详见下文 2.2 节)。
1.2 必备原料和工具
| 类别 | 原料/工具 |
|---|---|
| 主料 | 大肉、鸡蛋(可选)、豆皮(可选) |
| 辅料 | 生姜、冰糖、生抽、老抽、料酒、香叶、八角、盐、水、葱(记得要开水) |
| 工具 | 刀(原文档特别提示:如果有可能,请尽量把刀磨得锋利一些) |
1.3 计算(用量配比)
原文档要求"每次制作前需要确定计划做几份",一份正好够 2~3 人吃;如果只有 1 人食用,可以考虑食材减半。具体用量如下:
- 猪五花肉:约 3~4 斤
- 姜:6 片
- 冰糖:15 克(约 7 块)
- 生抽:10ml
- 老抽:15ml
- 料酒:5ml
- 开水:没过食材的量,需要 600ml~900ml
- 香叶:3 片
- 八角:2 个
- 鹌鹑蛋(可选,没有鹌鹑蛋可以用同等重量的鸡蛋代替):0~2 个
- 豆皮(可选):0~80g
- 盐:2~3g
1.4 操作步骤
原材料准备:
- 猪五花肉切大块(约 4.5cm,冷冻半小时至一小时更好切)
- 豆皮切 2cm 的宽度
- 生姜切片(每片厚度约 3mm)
- 水烧开
- 鹌鹑蛋煮熟并用叉子/牙签扎孔(尽量多些好入味)
- 大葱取白色的部分(葱白)
开始制作:
- 冷水锅中放入切好的猪五花肉,加入料酒与葱姜,煮 15 分钟去掉血腥;
- 锅中放入两片生姜提味;
- 开中小火后直接加入五花肉,不需要放入食用油,每块五花肉六个面都煎一下,煎至出油即可;
- 将煎出的油倒出备用,并将五花肉推至一边,加入 15g 冰糖,翻炒至冰糖融化;
- 融化后将五花肉与冰糖炒至融合上色,加入生抽 10ml、老抽 15ml、料酒 5ml,翻炒至上色;
- 加入烧好的开水炖煮 40 分钟(刀工差的同学切的过大请自觉延长炖煮时间),并放入生姜 2 片、香叶 3 片、八角 2 个;
- 盖上锅盖煮至沸腾后,加入煮好扎好孔的鹌鹑蛋和豆皮,开中小火,等待 40 分钟(中途可适当翻搅防止粘锅);
- 打开锅盖,待汤汁快没有的时候开大火收汁(切记不可收干);
- 加入 2~3g 盐,翻炒一下,就可以出锅了。
原文档末尾的"附加内容"章节为空,这并不影响其作为知识库样本的价值——生成模块的提示词设计(见 generation_integration.py)明确要求"如果原文的附加内容与烹饪无关或为空,可以基于制作步骤总结关键要点,或者完全省略此部分",保证回答不会强行填充无关内容。
二、这份菜谱在 RAG 系统中的定位
2.1 数据源路径与目录约定
系统的数据根路径在 config.py 中配置为../../data/C8/cook(相对于code/C8目录),而这份红烧肉菜谱恰好位于该路径下的dishes/meat_dish/红烧肉/子目录。meat_dish这个目录名不是随意取的,它是元数据增强时推断菜品分类的关键依据。
2.2 加载与元数据增强:一份菜谱如何获得"标签"
在 data_preparation.py 中,load_documents()使用Path.rglob("*.md")递归扫描数据目录下的所有 Markdown 文件,读取原始内容并创建Document对象。每个父文档的parent_id由相对路径经 MD5 哈希得到(确定性 ID,重复构建不产生新 ID),doc_type标记为"parent"。
随后_enhance_metadata()为每份菜谱补充三类核心元数据(data_preparation.py):
- 菜品分类(category):通过
CATEGORY_MAPPING字典在文件路径中匹配目录名,meat_dish → 荤菜、vegetable_dish → 素菜、soup → 汤品等九类。因此简易红烧肉.md会被自动打上荤菜标签; - 菜品名称(dish_name):直接取文件名(不含扩展名)
file_path.stem,即"简易红烧肉"; - 难度等级(difficulty):使用正则
re.search(r'★+', content)精确匹配连续星号数量,再经映射表{5: '非常困难', 4: '困难', 3: '中等', 2: '简单', 1: '非常简单'}转换。本菜谱的三颗星会被解析为中等。
这些元数据不仅用于展示,还直接服务于检索阶段的过滤能力:main.py的_extract_filters_from_query()会从用户问题中抽取分类与难度关键词(如"简单的荤菜"),命中后调用metadata_filtered_search()缩小检索范围(见 main.py)。
2.3 Markdown 结构感知分块
分块逻辑位于chunk_documents()与_markdown_header_split()(data_preparation.py),它使用 LangChain 的MarkdownHeaderTextSplitter,按三级标题进行分割:
headers_to_split_on = [ ("#", "主标题"), # 菜品名称 ("##", "二级标题"), # 必备原料、计算、操作等 ("###", "三级标题") # 简易版本、复杂版本等 ] markdown_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=headers_to_split_on, strip_headers=False # 保留标题,便于理解上下文 )对于本菜谱,分块结果如下(对应 02_data_preparation.md 中"分块效果示例"描述的父子文本块映射):
父文档:简易红烧肉.md(完整菜谱) ├── 子块1:# 简易红烧肉的做法 + 简介 + 难度评级 ├── 子块2:## 必备原料和工具 + 食材清单 ├── 子块3:## 计算 + 用量配比 ├── 子块4:## 操作 + 详细制作步骤 └── 子块5:## 附加内容(空章节)每个子块都会继承父文档的全部元数据,并额外获得chunk_id(UUID)、doc_type="child"、chunk_index(在父文档中的位置)、batch_index与chunk_size;同时self.parent_child_map[child_id] = parent_id建立起子块到父文档的映射关系。strip_headers=False意味着标题文本被保留在子块内容中,便于 LLM 理解上下文。
2.4 父子文本块:为什么"小块检索、大块生成"
这是本项目数据准备设计的核心思想。如果只问"红烧肉需要什么食材",整份菜谱文档中只有"必备原料和工具"和"计算"两个章节与该问题高度相关,直接向量化整篇文档会导致该部分被稀释、排名靠后;而按标题切成小块后,子块 2/3 能精确命中。
但反过来,如果只把命中的小块喂给 LLM,又会丢失完整上下文(如缺少操作步骤)。因此检索阶段使用小块精确匹配,生成阶段则通过get_parent_documents()(data_preparation.py)把命中的多个子块聚合回完整父文档再传给 LLM:
- 统计每个父文档被匹配的子块数量(相关性指标);
- 按命中次数降序排序,命中子块越多的菜谱排名越靠前;
- 每个父文档只输出一次,实现智能去重,避免同一道菜被重复拼接。
这也正是"用户问宫保鸡丁怎么做时,可能同时检索到操作、原料等多个子块,最终合并为一个完整菜谱"的去重机制。
三、从分块到检索与生成:整条调用链
这份菜谱生成的子块会继续流入后续模块,形成完整的 RAG 链路:
- 索引构建:
IndexConstructionModule使用BAAI/bge-small-zh-v1.5嵌入模型(CPU 推理、向量归一化)将子块向量化,构建 FAISS 向量索引并持久化到./vector_index(见 index_construction.py)。第二次启动时可直接加载已保存索引,实现秒级启动。 - 混合检索与 RRF 重排:
RetrievalOptimizationModule同时使用向量检索(语义)与 BM25 检索(关键词),再用 RRF(Reciprocal Rank Fusion)公式1/(k+rank+1)(k=60)融合两个榜单的排名(见 retrieval_optimization.py),最终按config.top_k=3截取结果。 - 查询路由与重写:
GenerationIntegrationModule先将问题路由为list(推荐列表)/detail(详细做法)/general(一般问题)三类;detail类走分步指导提示词,general类走基础回答提示词,均支持流式输出(见 generation_integration.py)。 - 生成:默认使用
kimi-k2-0711-preview模型(通过 MoonshotChat 接入,需设置MOONSHOT_API_KEY环境变量),temperature=0.1、max_tokens=2048(见 config.py)。上下文构建器_build_context()会把每个父文档的dish_name、category、difficulty元数据连同全文一起格式化后送入提示词。
例如用户问"红烧肉怎么做",系统会命中该菜谱的多个子块,去重聚合回完整父文档,最终以"菜品介绍 → 所需食材 → 制作步骤 → 制作技巧"的分步结构输出回答。
四、运行与验证
按第 8 章环境配置(01_env_architecture.md)操作:
conda create -n cook-rag-1 python=3.12.7 conda activate cook-rag-1 cd code/C8 pip install -r requirements.txt export MOONSHOT_API_KEY=你的API密钥 # 或在 .env 中配置 python main.py依赖清单见 requirements.txt,核心包括langchain==0.3.26、langchain-community==0.3.27、langchain-huggingface==0.3.1、faiss-cpu、rank_bm25等。程序启动后会先构建/加载知识库并打印统计信息(文档总数、文本块数、分类与难度分布),随后进入交互式问答,输入"红烧肉需要什么食材""推荐几个简单的荤菜"等即可验证本菜谱在系统中的检索与生成效果。
小结
一份看似普通的简易红烧肉.md,在 all-in-rag 第 8 章的数据准备模块中经历了"递归加载 → 元数据增强 → 三级标题分块 → 父子关系建立 → 智能去重"的完整流水线:目录名meat_dish变成荤菜标签、三颗星变成中等难度、四个章节变成五个可精确检索的子块,最终在混合检索与 RRF 重排的配合下,为"小块检索、大块生成"的问答体验提供了坚实的数据底座。理解这一过程,你就掌握了如何把任何高度结构化的 Markdown 语料,转化为高质量 RAG 知识库的通用方法论。
- 教程
- 人工智能
- 大模型
- RAG
【免费下载链接】all-in-rag
🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/
相关推荐
红烧猪蹄的完整做法:all-in-rag 菜谱知识库中的标准化荤菜食谱实战
红烧猪蹄的完整做法:all in rag 菜谱知识库中的标准化荤菜食谱实战 红烧猪蹄是一道汤汁浓郁、软糯下饭的经典家常硬菜,其标准做法文档完整收录在 all i
教程人工智能大模型RAG冷吃兔食谱实战:all-in-rag 中一份 Markdown 菜谱如何成为 RAG 知识库的数据源
冷吃兔食谱实战:all in rag 中一份 Markdown 菜谱如何成为 RAG 知识库的数据源 冷吃兔是川渝地区经典的自贡菜,也是 Datawhale a
教程人工智能大模型RAGall-in-rag 食谱知识库实战:徽派红烧肉的完整做法与结构化数据解析
all in rag 食谱知识库实战:徽派红烧肉的完整做法与结构化数据解析 导读 本文以开源仓库 datawhalechina/all in rag 中 C8
教程人工智能大模型RAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考