- 教程
- 人工智能
- 大模型
- RAG
【免费下载链接】all-in-rag
🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/
话梅煮毛豆是一道酸甜可口、烹饪难度仅 ★★ 的简易素菜,它不仅是餐桌上的开胃小食,更是 all-in-rag 项目第八章节「尝尝咸淡 RAG 系统」知识库中的一份典型结构化数据样本。本文以这份菜谱文档为解剖对象,完整保留其原料、用量与操作细节,同时结合仓库中 data_preparation.py、index_construction.py、retrieval_optimization.py、generation_integration.py 等源码,讲清楚一份菜谱从"Markdown 文件"变成"可被检索、可被引用的知识库条目"的完整链路,读完即可在自己的数据上复现这套流程。
一、先看数据源:话梅煮毛豆这份菜谱长什么样
关联文档位于 data/C8/cook/dishes/vegetable_dish/话梅煮毛豆/话梅煮毛豆.md,同目录还存放了成品图1.jpeg。其原文内容如下:
# 话梅煮毛豆的做法 酸甜可口、营养价值高的一种简易美食 预估烹饪难度:★★ ## 必备原料和工具 * 毛豆 * 话梅 * 食用盐 ## 计算 每份: * 毛豆 300 g * 话梅 6 颗 * 食用盐 2 g ## 操作 * 清水加入食用盐,毛豆浸泡 15 分钟 * 加入开水,倒入毛豆、话梅,水煮 20-30 分钟 * 起锅开吃 ## 附加内容从 RAG 数据工程的角度看,这份文档具备三个对后续处理至关重要的结构特征:
- 目录即分类:文件存放在
vegetable_dish/目录下,这一路径信息可以直接推导出菜品分类"素菜"; - 标题即结构:文档使用
#、##两级标题组织"简介 → 原料 → 计算 → 操作 → 附加内容",天然适配按标题切块; - 星级即难度:
预估烹饪难度:★★中的连续星号数量可直接映射为难度等级。
这也是 why 这份菜谱能够成为知识库一员的关键——它不是自由文本,而是遵循了统一模板的结构化数据。仓库中 data/C8/cook 下所有菜谱(荤菜、素菜、汤品、甜品等)都遵循同一套写法,使得后面的元数据增强与分块逻辑可以无差别批量处理。
二、文档加载:话梅煮毛豆如何进入 RAG 系统
数据准备模块 data_preparation.py 的DataPreparationModule是整个链路的入口,其核心方法是load_documents()(data_preparation.py):
for md_file in data_path_obj.rglob("*.md"): with open(md_file, 'r', encoding='utf-8') as f: content = f.read() # 基于数据根目录的相对路径生成确定性 parent_id relative_path = Path(md_file).resolve().relative_to(data_root).as_posix() parent_id = hashlib.md5(relative_path.encode("utf-8")).hexdigest() doc = Document( page_content=content, metadata={"source": str(md_file), "parent_id": parent_id, "doc_type": "parent"} )几个实现细节值得注意:
rglob("*.md")递归扫描:只要数据目录下存在.md文件就会被加载,话梅煮毛豆的路径dishes/vegetable_dish/话梅煮毛豆/话梅煮毛豆.md正在扫描范围内;- 保持原始 Markdown 格式:直接以 UTF-8 读取全文存入
page_content,不做过早的文本清洗,把结构化信息保留给后续分块环节; - 确定性父文档 ID:
parent_id由文件相对路径做 MD5 生成,同一份文件每次构建索引得到的 ID 一致,这为索引缓存复用与父子文档关联提供了稳定锚点; doc_type: "parent":标记完整菜谱为父文档,与后续切出的子块(doc_type: "child")区分。
在 main.py 的build_knowledge_base()中,加载文档后紧接着调用chunk_documents()完成分块,再交给索引构建模块向量化。默认数据路径在 config.py 中定义为data_path: str = "../../data/C8/cook"(相对code/C8目录),也就是说整个菜谱知识库的构建并不需要针对单份文档写任何特殊逻辑。
三、元数据增强:自动认出"素菜 / 简单 / 话梅煮毛豆"
load_documents()加载完每份文档后,会调用_enhance_metadata()(data_preparation.py)做元数据增强,这正是"话梅煮毛豆"被系统理解的三条线索:
1. 分类从路径推断。模块维护了一张目录英文名到中文分类的映射表:
CATEGORY_MAPPING = { 'meat_dish': '荤菜', 'vegetable_dish': '素菜', 'soup': '汤品', 'dessert': '甜品', 'breakfast': '早餐', 'staple': '主食', 'aquatic': '水产', 'condiment': '调料', 'drink': '饮品' }只要路径片段中出现vegetable_dish,metadata['category']即被标记为"素菜"。话梅煮毛豆所在的目录恰好命中这一规则。
2. 难度从星号提取。源码使用正则re.search(r'★+', content)匹配连续的星号,再通过映射表换算:
difficulty_map = {5: '非常困难', 4: '困难', 3: '中等', 2: '简单', 1: '非常简单'}文档中写的是★★,因此metadata['difficulty']会被自动判定为"简单"。这里的星级写法(1~5 星)与菜谱模板是强耦合的约定,仓库内所有菜谱都遵守该约定,难度标签才能批量、稳定地抽取。
3. 名称从文件名提取。doc.metadata['dish_name'] = file_path.stem,即话梅煮毛豆。后续无论是main.py打印检索到的菜品名,还是生成列表式回答,都直接读取这个字段。
增强后的元数据具备实际检索价值:模块提供了filter_documents_by_category()与filter_documents_by_difficulty()(data_preparation.py)两个过滤接口,而 main.py 的_extract_filters_from_query()会从用户问题中匹配"素菜""简单"等关键词,将其转换为元数据过滤条件——这正是"推荐几道简单的素菜"这类查询能精准圈定话梅煮毛豆的原因。
四、Markdown 结构感知分块:二级标题切出可检索的子块
分块是决定检索精度的关键环节。模块采用 LangChain 的MarkdownHeaderTextSplitter实现"结构感知分块"(data_preparation.py):
headers_to_split_on = [ ("#", "主标题"), # 菜品名称 ("##", "二级标题"), # 必备原料、计算、操作等 ("###", "三级标题") # 简易版本、复杂版本等 ] markdown_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=headers_to_split_on, strip_headers=False # 保留标题,便于理解上下文 )strip_headers=False表示切块后保留标题文本,避免子块丢失上下文语义。以话梅煮毛豆为例,父文档会被切成如下子块:
父文档:# 话梅煮毛豆的做法(完整菜谱) ├── 子块1:# 话梅煮毛豆的做法 + 简介 + 难度(★★) ├── 子块2:## 必备原料和工具 + 毛豆/话梅/食用盐 ├── 子块3:## 计算 + 毛豆 300g / 话梅 6 颗 / 食用盐 2g ├── 子块4:## 操作 + 浸泡15分钟 / 水煮20-30分钟 / 起锅 └── 子块5:## 附加内容每个子块会继承父文档的全部元数据,并补充chunk_id(UUID)、doc_type: "child"、chunk_index、chunk_size,同时把child_id → parent_id写入parent_child_map字典。这套设计对应文档 docs/chapter8/02_data_preparation.md 中强调的"小块检索,大块生成"父子文本块架构:用户问"话梅煮毛豆要多少话梅"时,可以精确定位到"计算"子块;而生成回答时再通过get_parent_documents()取回完整父文档,保证 LLM 拥有完整上下文。
五、从"怎么做"到"推荐素菜":检索与生成的完整链路
分块完成后,索引构建模块 index_construction.py 使用默认的 BGE 中文嵌入模型BAAI/bge-small-zh-v1.5将子块向量化,并存入 FAISS 向量库(支持save_local/load_local索引缓存)。随后检索优化模块 retrieval_optimization.py 提供双路检索:
- 向量检索(
as_retriever(search_type="similarity", k=5)):捕捉语义相似,例如"酸甜的开胃小食"能匹配到话梅煮毛豆; - BM25 关键词检索(
BM25Retriever.from_documents(chunks, k=5)):精确匹配"话梅""毛豆"等具体词; - RRF 融合(
_rrf_rerank,参数k=60):按1/(k+rank+1)累计两路排名得分,输出综合排序结果并写入doc.metadata['rrf_score']。
生成集成模块 generation_integration.py 则负责"理解问题并组织回答":
query_router():把问题分为list(推荐类)、detail(做法类)、general(一般类)三种;query_rewrite():对模糊查询做重写优化(如"做菜"→"简单易做的家常菜谱");generate_step_by_step_answer():使用结构化提示词,按"菜品介绍 / 所需食材 / 制作步骤 / 制作技巧"四段式输出详细指导,其中"制作技巧"一节明确要求优先采用原文"附加内容"中的实用技巧,若该部分为空则基于步骤总结或直接省略;_build_context():将父文档按max_length=2000截断并拼接元数据头(菜品名、分类、难度),作为 LLM 上下文。
在 main.py 的ask_question()中,上述环节被串成一条完整流水线:查询路由 → 查询重写 → 元数据过滤/混合检索 → 父子文档去重 → 按路由类型选择生成模式。典型问答场景如下:
用户问题: "推荐几道简单的素菜" 查询类型: list 过滤条件: {'category': '素菜', 'difficulty': '简单'} 生成结果: 为您推荐:话梅煮毛豆、凉拌黄瓜、蒜蓉西兰花……用户问题: "话梅煮毛豆怎么做?" 查询类型: detail 生成结果: ## 🥒 菜品介绍 … ## 🛒 所需食材(毛豆300g、话梅6颗、盐2g)… ## 👨🍳 制作步骤(盐水浸泡15分钟 → 开水下锅煮20-30分钟 → 起锅)…六、动手运行:把这份菜谱跑进一个可问答的系统
从仓库根目录出发,按以下步骤即可将话梅煮毛豆所在的整个知识库跑起来:
- 准备依赖与密钥:
main.py启动时会检查MOONSHOT_API_KEY环境变量(生成模块通过 Moonshot 的MoonshotChat调用 LLM),缺失会抛出ValueError。依赖清单见 code/C8/requirements.txt,从源码 import 看主要包括 langchain、langchain-community、langchain-huggingface、langchain-text-splitters、faiss 相关库及python-dotenv。 - 确认配置:核心参数集中在 config.py 的
RAGConfig中,均可用from_dict覆盖:
| 配置项 | 默认值 | 作用 |
|---|---|---|
data_path | ../../data/C8/cook(相对code/C8) | 菜谱知识库根目录 |
index_save_path | ./vector_index | FAISS 索引持久化路径 |
embedding_model | BAAI/bge-small-zh-v1.5 | 中文嵌入模型 |
llm_model | kimi-k2-0711-preview | 生成模型 |
top_k | 3 | 检索返回的子块数量 |
temperature | 0.1 | 生成温度,值越低越保守 |
max_tokens | 2048 | 最大生成长度 |
- 启动交互问答:在
code/C8目录下执行python main.py,系统会先构建或加载向量索引(首次构建需几分钟,之后因索引缓存只需几秒),随后进入命令行问答循环,支持流式输出。
值得注意的是,上述整条流水线对话梅煮毛豆并未做任何特判——它是被统一的模板约定(目录分类、星级难度、标题结构)"自动理解"的。这也正是本案例最值得复用的经验:在搭建 RAG 知识库时,先定义好文档模板规范,往往比堆砌更多的切块参数更能提升检索效果。
七、留白的"附加内容"与数据规范的价值
话梅煮毛豆文档末尾的"附加内容"目前为空,这在数据层面并非缺陷。一方面,生成模块的提示词已对此做了容错——没有技巧就不强行输出;另一方面,它恰好演示了模板的"可生长性":后续若补充"话梅品种选择""浸泡时间对口感的影响"等补充说明,系统无需改动任何代码即可自动将其纳入知识库。
结合 docs/chapter8/04_generation_sys.md 中提出的优化方向,这份菜谱未来还可以沿着两条路线深化:一是接入图数据库,把"毛豆、话梅、食用盐"建模为食材节点,支撑"和话梅搭配的食材"这类关系查询;二是融合同目录1.jpeg成品图等视觉数据,用多模态模型实现"看图找菜"。无论走向哪种方案,当前这套"目录即分类、标题即结构、星级即难度"的数据规范,都是所有上层能力得以成立的地基。
- 教程
- 人工智能
- 大模型
- RAG
【免费下载链接】all-in-rag
🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/
相关推荐
Roo Code v2.2.29 发布解析:为自动写入增加可配置延迟,让诊断与 Linter 从容跟上
Roo Code v2.2.29 发布解析:为自动写入增加可配置延迟,让诊断与 Linter 从容跟上 Roo Code 2.2.29 引入了一项直接影响开发体
教程人工智能大模型RAGall-in-rag 实战:以"煮泡面加蛋"为例拆解菜谱知识库的数据准备与检索生成链路
all in rag 实战:以"煮泡面加蛋"为例拆解菜谱知识库的数据准备与检索生成链路 本篇技术指南以 Datawhale「all in rag」仓库中 煮泡面
教程人工智能大模型RAGall-in-rag 尝尝咸淡 RAG 系统实战:西红柿豆腐汤羹菜谱数据与检索问答全解析
all in rag 尝尝咸淡 RAG 系统实战:西红柿豆腐汤羹菜谱数据与检索问答全解析 西红柿豆腐汤羹是一道清淡鲜美、营养均衡的经典家常汤羹,在 all in
教程人工智能大模型RAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考