洋葱炒猪肉菜谱的 RAG 全流程解析:从 Markdown 数据加工到 all-in-rag 智能问答实战
2026/9/23 12:37:17 网站建设 项目流程
  • 教程
  • 人工智能
  • 大模型
  • RAG

【免费下载链接】all-in-rag

🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/

项目地址:https://gitcode.com/datawhalechina/all-in-rag
点击查看免费下载

本篇以 all-in-rag 仓库「尝尝咸淡 RAG 系统」知识库中的真实菜谱 洋葱炒猪肉的做法 为贯穿全文的案例,完整讲解一道结构化菜谱从"原料数据"到"可问答知识"的加工链路:元数据增强、Markdown 结构分块、父子文本块检索去重、以及 LLM 分步指导生成。读完你将掌握 RAG 系统中"小块检索、大块生成"策略在一份真实文档上的落地细节,并可直接对照源码复现整个问答流程。

一、数据源本体:洋葱炒猪肉这道菜到底写了什么

先回到一切数据的起点——仓库中的原始文档 洋葱炒猪肉的做法。它位于data/C8/cook/dishes/meat_dish/目录下,属于「荤菜」分类的菜谱之一,全文结构高度规整,是典型的可用于 RAG 构建的结构化 Markdown 数据。其完整内容如下:

洋葱炒猪肉的做法

咸中带甜,简单上手,一不小心可能让人多吃一碗饭。一般只需 15 分钟即可完成。

预估烹饪难度:★★★

文档正文由「必备原料和工具」「计算」「操作」「附加内容」四个二级标题章节构成,每个章节职责单一、篇幅精炼,这为后续按标题分块提供了天然的切分依据。

1.1 必备原料和工具

  • 洋葱
  • 猪肉片
  • 蕃茄酱
  • 麻油

1.2 计算(每份用量配比)

食材

食材用量
洋葱一颗(是主角,喜欢吃洋葱可以多半颗~一颗)
猪肉250g
蒜头3 瓣

调味料

调味料用量
食用油15ml
黑胡椒1.25g
酱油30ml
15g
麻油5ml
番茄酱15ml
料酒15ml

备注:配方中的小匙、大匙属于体积计量单位,若家中没有标准量勺,可参照常见"茶匙(约 5ml)、大匙(约 15ml)"的换算标准来精准确定用料。

1.3 操作步骤

  1. 洋葱切片,猪肉切片(或切丝),蒜头拍碎,并将上述调味料混合备用。
  2. 炒锅内倒入 1 大匙食用油,等待约 10 秒让油温升高后倒入猪肉。
  3. 炒至猪肉变色后,下蒜头炒香,盛起备用。
  4. 原锅下洋葱翻炒 3~4 分钟,加入调味料炒匀。
  5. 下刚盛起备用的猪肉,翻炒至猪肉熟透。
  6. 猪肉熟后再翻炒 1~2 分钟即可起锅。

1.4 附加内容

  • 猪肉可选猪肩肉片或切好的肉丝,按个人喜好决定。

这段原文是后续所有 RAG 环节的事实基础:检索必须能命中它,生成必须忠于它。接下来我们看这份菜谱是如何被「尝尝咸淡 RAG 系统」加工成可检索、可回答的知识单元的。

二、数据准备:一份菜谱如何被加载并打上元数据标签

在 docs/chapter8/02_data_preparation.md 中明确了数据准备模块的设计目标:把原始 Markdown 菜谱文件转化为带丰富元数据的父文档(完整菜谱)与子文档(按标题分割的小块),为"小块检索、大块生成"打基础。对应实现位于 code/C8/rag_modules/data_preparation.py。

2.1 加载:递归发现与父文档 ID

load_documents()通过Path(self.data_path).rglob("*.md")递归扫描数据目录下的所有 Markdown 文件,逐文件读取内容,并为每个父文档分配一个确定性的唯一 ID——基于文件相对数据根目录的路径做 MD5 哈希(而非随机 UUID),这样同一文件在不同构建之间拥有稳定 ID,便于索引复用与父子关系重建:

parent_id = hashlib.md5(relative_path.encode("utf-8")).hexdigest()

洋葱炒猪肉这份文档被读取后,即成为一个doc_type="parent"的父文档,source字段指向其文件路径。

2.2 元数据增强:从路径与内容中自动提取

加载完成后,_enhance_metadata()会对每个父文档做三项关键增强,以洋葱炒猪肉为例:

元数据字段提取方式洋葱炒猪肉的实际取值
dish_name取文件名 stem洋葱炒猪肉
category在路径中匹配分类目录名荤菜(命中meat_dish
difficulty用正则★+匹配内容中的连续星号,按数量映射中等(原文为 ★★★)

分类映射与难度映射在源码中集中定义,便于检索模块复用(见 data_preparation.py 的CATEGORY_MAPPINGDIFFICULTY_LABELS):

CATEGORY_MAPPING = { 'meat_dish': '荤菜', 'vegetable_dish': '素菜', 'soup': '汤品', 'dessert': '甜品', 'breakfast': '早餐', 'staple': '主食', 'aquatic': '水产', 'condiment': '调料', 'drink': '饮品' } difficulty_map = {5: '非常困难', 4: '困难', 3: '中等', 2: '简单', 1: '非常简单'}

这份元数据的价值在于:用户提问"推荐一道荤菜""有没有中等难度的下饭菜"时,系统可以从查询中提取过滤条件(main.py中的_extract_filters_from_query()),直接做category/difficulty的元数据过滤检索,而不是把过滤负担全部压给向量相似度。

三、结构分块:洋葱炒猪肉被切成哪五个子块

结构规整的菜谱与第 2 章学过的 Markdown 结构分块思路天然契合(参见 docs/chapter2/05_text_chunking.md)。DataPreparationModule._markdown_header_split()使用 LangChain 的MarkdownHeaderTextSplitter,按三级标题层级切分:

headers_to_split_on = [ ("#", "主标题"), # 菜品名称 ("##", "二级标题"), # 必备原料、计算、操作等 ("###", "三级标题") # 简易版本、复杂版本等 ] markdown_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=headers_to_split_on, strip_headers=False # 保留标题,便于理解上下文 )

以洋葱炒猪肉为例,分块结果恰好对应文档的五个语义单元:

洋葱炒猪肉的做法.md(父文档,parent_id=xxx) ├── 子块1:# 洋葱炒猪肉的做法 + 简介 + 预估烹饪难度 ★★★ ├── 子块2:## 必备原料和工具 + 原料清单 ├── 子块3:## 计算 + 食材/调味料用量配比表 ├── 子块4:## 操作 + 六步制作步骤 └── 子块5:## 附加内容 + 猪肉选材建议

每个子块都会继承父文档的全部元数据,并额外标注doc_type="child"chunk_id(随机 UUID)、chunk_index(在父文档中的位置)、parent_id,同时在parent_child_map中登记child_id -> parent_id的映射关系,为后续"由子块回溯父文档"做准备。

分块的效果是检索粒度变细:用户问"洋葱炒猪肉需要什么调味料",向量检索可以精确命中"计算"子块,而不是在整个文档中大海捞针;而strip_headers=False保留了标题,使每个子块都自带语义上下文。

四、小块检索、大块生成:检索去重与父子文档回溯

4.1 为什么要"小块检索,大块生成"

如果直接把整份菜谱作为一个检索单元,用户提问"洋葱炒猪肉要放多少酱油"时,这个具体问题只占全文极小比例,向量检索很可能排不到前面;反之若只检索小块,命中的"计算"子块又缺少"操作"步骤的上下文。因此系统采用混合策略:检索阶段用小子块精确匹配,生成阶段回溯完整父文档保证上下文完整,这正是 docs/chapter8/01_env_architecture.md 中强调的设计核心。

4.2 混合检索与 RRF 重排

code/C8/rag_modules/retrieval_optimization.py 中的RetrievalOptimizationModule同时启动两个检索器:

  • 向量检索:基于 BGE 嵌入模型(默认BAAI/bge-small-zh-v1.5)+ FAISS 向量库做语义相似度召回;
  • BM25 检索:基于BM25Retriever做关键词精确召回。

两者结果通过_rrf_rerank()按 Reciprocal Rank Fusion 公式1 / (k + rank + 1)(k=60)融合排序,兼顾语义与词面匹配。例如"洋葱炒猪肉"这类菜名查询,BM25 对菜名关键词高度敏感,而"咸中带甜的下饭菜"这类语义查询则依赖向量召回,RRF 将两者的排名优势合并。

4.3 智能去重:多个子块合并为一份完整菜谱

由于洋葱炒猪肉被切成 5 个子块,用户提问时可能同时命中"计算"与"操作"两个子块。此时DataPreparationModule.get_parent_documents()会:

  1. 统计每个parent_id被命中的子块数量作为相关性指标;
  2. 按命中次数降序排列父文档;
  3. 输出去重后的完整父文档列表,避免把同一道菜重复传给 LLM。

源码实现见 data_preparation.py,其日志输出形如从 2 个子块中找到 1 个去重父文档: 洋葱炒猪肉(2块),直观展示了"多块命中、一份输出"的效果。

五、生成阶段:菜谱如何变成分步骤烹饪指导

检索回溯得到完整父文档后,由 code/C8/rag_modules/generation_integration.py 的GenerationIntegrationModule负责生成回答。main.py中的ask_question()会先通过query_router()把问题路由为三类:

  • list:推荐/列表类,如"推荐几道荤菜",直接输出菜名列表(generate_list_answer);
  • detail:做法细节类,如"洋葱炒猪肉怎么做",走分步指导模式generate_step_by_step_answer);
  • general:一般知识类,走基础回答模式(generate_basic_answer)。

以 detail 路由为例,提示词要求模型围绕"菜品介绍 / 所需食材 / 制作步骤 / 制作技巧"灵活组织输出,且明确约束:优先使用原文中的实用技巧,不强行填充无关内容。这意味着 LLM 的回答会严格锚定上文那份真实菜谱——洋葱、250g 猪肉、3 瓣蒜头、调味料配比与六步操作都会原样呈现,而不是模型自由发挥。_build_context()会把父文档连同dish_namecategorydifficulty等元数据拼装进上下文(默认最大 2000 字符),保证模型"看到"的是带标签的完整菜谱。

六、端到端运行:配置、依赖与一次真实问答

6.1 关键配置项

系统配置集中在 code/C8/config.py 的RAGConfig数据类中,核心参数如下:

配置项默认值作用
data_path../../data/C8/cook菜谱数据目录,即洋葱炒猪肉所在的知识库根目录
index_save_path./vector_indexFAISS 索引持久化路径,二次启动秒级加载
embedding_modelBAAI/bge-small-zh-v1.5向量嵌入模型
llm_modelkimi-k2-0711-preview生成阶段的大模型
top_k3检索返回子块数量
temperature0.1生成温度,低值保证回答更贴近原文
max_tokens2048单次生成最大 token 数

6.2 环境与运行

按 docs/chapter8/01_env_architecture.md 的指引,在code/C8目录安装依赖并配置 Kimi API Key(通过环境变量MOONSHOT_API_KEY,源码在main.py初始化时会校验其是否存在):

conda create -n cook-rag-1 python=3.12.7 conda activate cook-rag-1 cd code/C8 pip install -r requirements.txt # 配置 MOONSHOT_API_KEY 后运行 python main.py

依赖清单见 code/C8/requirements.txt,包括langchainlangchain-huggingfacelangchain-text-splittersfaiss-cpurank_bm25sentence-transformers等。首次运行会经历"加载文档 → 元数据增强 → 结构分块 → 构建 FAISS 索引 → 保存索引"的完整链路,并打印知识库统计(文档总数、文本块数、菜品分类、难度分布);索引已存在时则直接加载缓存,实现秒级启动。

6.3 用这份菜谱验证一次完整问答

进入交互式问答后,提问"洋葱炒猪肉怎么做"会依次触发:

  1. 查询路由判定为detail
  2. 混合检索命中洋葱炒猪肉的"计算""操作"等子块;
  3. get_parent_documents去重回溯出完整父文档;
  4. 分步指导模式生成包含食材清单、六步操作与制作技巧的回答。

如果追问"洋葱炒猪肉要放多少糖",_extract_filters_from_query虽无法提取分类/难度过滤条件,但精确的子块检索仍会命中"计算"章节,直接给出"糖 15g"的用量答案——这正是结构分块 + 小块检索带来的实战收益。

七、小结

以「洋葱炒猪肉」这一份真实菜谱为线索,我们完整走通了 all-in-rag「尝尝咸淡 RAG 系统」从数据到答案的整条链路:结构化 Markdown 文档经过元数据增强(菜名、荤菜分类、中等难度)与 Markdown 三级标题分块(1 份文档 → 5 个子块),在混合检索(向量 + BM25 + RRF)阶段以小粒度精确命中用户意图,再经父子文档映射去重回溯完整菜谱,最后由 LLM 在 detail 路由下生成忠于原文的分步指导。这份菜谱既是厨房里一道 15 分钟的快手菜,也是理解 RAG 数据加工流水线一份难得的"麻雀虽小、五脏俱全"的样本数据。若想深入阅读模块实现,推荐对照 data_preparation.py、retrieval_optimization.py 与 generation_integration.py 三份源码逐行研读。

  • 教程
  • 人工智能
  • 大模型
  • RAG

【免费下载链接】all-in-rag

🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/

项目地址:https://gitcode.com/datawhalechina/all-in-rag
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询