all-in-rag 实战解析:从"太阳蛋"食谱看 RAG 知识库的构建与智能问答全流程
【免费下载链接】all-in-rag🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/项目地址: https://gitcode.com/datawhalechina/all-in-rag
本篇文章以 all-in-rag 仓库 C8 食谱 RAG 系统知识库中的一份真实数据源——太阳蛋.md——为主线,完整复现其食谱内容,并深入剖析这份 Markdown 文档在 code/C8 完整 RAG 流水线中"如何被加载、如何被元数据增强、如何被结构分块、如何被检索、如何被生成回答"的全过程。读完本文,你既能照做一份零失败的微波炉太阳蛋,也能掌握一套可直接复用的"Markdown 结构化知识库 → 向量索引 → 混合检索 → 多模式生成"的 RAG 工程实现。
一、太阳蛋食谱:一份典型的 Markdown 结构化数据源
在 all-in-rag 的 C8 食谱 RAG 系统中,data/C8/cook 是整个知识库的原始数据目录,按分类/菜品/的目录层级组织(如breakfast/太阳蛋.md、meat_dish/红烧肉.md)。太阳蛋.md 就是其中一份标准的"早餐"分类食谱,其内容结构完整、难度标识规范,非常适合作为理解 RAG 数据准备的样例。原文内容如下:
1.1 必备原料和工具
- 鸡蛋
- 盐
- 油
- 分可控火候微波炉或不可控火候微波炉(定义和分辨方式请见附加内容)
- 筷子或牙签
1.2 用量计算
- 鸡蛋的用量为 1 个。
- 盐的用量为 1 g 每个鸡蛋。
- 油的用量为 5 mL 每个鸡蛋。
使用上述条件,计算出计划使用的原材料比例(即按份数线性缩放即可:做 N 份,则鸡蛋 N 个、盐 N g、油 5N mL)。
1.3 操作步骤
可控火候微波炉:
- 准备一个小碗,倒入在上一步计算好的油,撒盐,搅拌均匀。倾斜碗使油沾在碗表面。
- 取出一个鸡蛋,打入小碗。
- 蛋黄表面戳孔。牙签戳 5 个或筷子戳 1 个。
- 放入微波炉,中火 3 分钟。
不可控火候微波炉:
- 准备一个小碗,倒入在上一步计算好的油,撒盐,搅拌均匀。倾斜碗使油沾在碗表面。
- 取出一个鸡蛋,打入小碗。
- 蛋黄表面戳孔。牙签戳 5 个或筷子戳 1 个。
- 放入微波炉,1 分钟。
- 循环执行:只要太阳蛋还未大面积呈固体状,就继续用微波炉加热 30 秒。
1.4 附加内容
循环执行:只要太阳蛋的熟度不符合个人口味,就继续用微波炉加热 1 分钟。
- 不可控火候微波炉:
- 定义:即无法控制火候、仅能控制时长的微波炉。
- 辨别方法:若在微波炉操作面板上无法找到小火、中火、大火等字样,即为不可控火候微波炉。
- 可控火候微波炉:
- 定义:即既能控制火候又能控制时长的微波炉。
- 辨别方法:若在微波炉操作面板上能找到小火、中火、大火等字样,即为可控火候微波炉。
关键技巧提示:蛋黄表面戳孔是防爆核心——蛋黄在微波加热时内部水分汽化会产生高压,若不戳孔极易炸开;同时先让油盐在碗壁形成薄油层,能有效避免鸡蛋粘碗。这两条经验正是食谱中可被检索模块精确命中的"制作技巧"型内容。
二、食谱文档如何进入 RAG 知识库:数据准备模块
在 all-in-rag 的 code/C8/rag_modules/data_preparation.py 中,DataPreparationModule负责将data/C8/cook下数百份食谱文档(含太阳蛋.md)转化为带丰富元数据的父子文档结构。
2.1 批量加载 Markdown 文件
load_documents()通过Path(data_path).rglob("*.md")递归扫描整个知识库目录,直接以 UTF-8 读取原文并保持 Markdown 格式,为每份父文档生成确定性的parent_id(基于相对路径的 MD5),同时记录source与doc_type: "parent"标记。
2.2 元数据增强:太阳蛋的"身份证"
_enhance_metadata()会为太阳蛋文档自动补全三类核心元数据:
| 元数据字段 | 提取逻辑 | 太阳蛋文档的取值 |
|---|---|---|
category | 从文件路径匹配CATEGORY_MAPPING(如breakfast→ 早餐) | 早餐 |
dish_name | 直接取文件名 stem | 太阳蛋 |
difficulty | 用正则★+统计连续星号数量,映射 1~5 星难度 | 简单(文档标记为 ★★) |
其中难度映射表定义在源码中:{5: '非常困难', 4: '困难', 3: '中等', 2: '简单', 1: '非常简单'}。太阳蛋文档头部"预估烹饪难度:★★"正是这条规则的数据来源,也就是说——即使不改动任何代码,只要在食谱里按规范标注星号,系统就能自动识别难度。
2.3 Markdown 结构感知分块
_markdown_header_split()使用 LangChain 的MarkdownHeaderTextSplitter,按#(主标题)、##(二级标题)、###(三级标题)三级结构对文档分块,并设置strip_headers=False保留标题上下文。太阳蛋文档经此流程会被拆分为典型的父子结构:
太阳蛋.md(父文档,parent_id 唯一标识) ├── 子块1:# 太阳蛋的做法 + 难度评级 ├── 子块2:## 必备原料和工具 ├── 子块3:## 计算(用量配比) ├── 子块4:## 操作(可控火候/不可控火候分支) └── 子块5:## 附加内容(微波炉类型定义)每个子块都会继承父文档的category、difficulty、dish_name,并新增chunk_id、parent_id、doc_type: "child"、chunk_index等字段,同时写入parent_child_map维护父子映射——这正是"小块检索、大块生成"架构的基础。
分块的价值在于:用户问"做太阳蛋需要什么食材"时,可精确命中"子块2";而生成回答时再通过
get_parent_documents()将命中子块对应的完整父文档(含全部操作步骤)交给 LLM,保证上下文不残缺。
三、索引构建与混合检索:让"太阳蛋"被精确召回
3.1 向量化与 FAISS 索引
code/C8/rag_modules/index_construction.py 中的IndexConstructionModule使用HuggingFaceEmbeddings(默认模型BAAI/bge-small-zh-v1.5,normalize_embeddings=True)将每个子块编码为向量,并构建 FAISS 索引;save_index()/load_index()实现了索引缓存机制,首次构建后后续启动可直接秒级加载。
3.2 双路混合检索与 RRF 重排
code/C8/rag_modules/retrieval_optimization.py 的RetrievalOptimizationModule同时设置了两路检索器:
- 向量检索(
vectorstore.as_retriever,k=5):基于语义相似度,能理解"微波炉做早餐""怎么煎个蛋"等同义表达; - BM25 检索(
BM25Retriever.from_documents,k=5):基于关键词精确匹配,能稳定命中"太阳蛋""戳孔"等专名。
hybrid_search()调用_rrf_rerank()用 RRF(Reciprocal Rank Fusion)融合两路结果,每个文档得分 =Σ 1 / (k + rank + 1)(默认k=60),综合排名后取top_k(配置文件默认 3)。此外,metadata_filtered_search()支持按category、difficulty元数据过滤——例如查询"推荐几道简单的早餐"时,系统会自动从问题中提取过滤条件(见 main.py 的_extract_filters_from_query),只检索早餐+简单的文档块,太阳蛋正是此类场景的理想命中对象。
四、生成集成:从检索结果到结构化回答
code/C8/rag_modules/generation_integration.py 的GenerationIntegrationModule负责"最后一公里"。
- 查询路由:
query_router()用 LLM 将问题分类为list(要菜名推荐)、detail(要具体做法)、general(一般性问题)三类; - 查询重写:对非
list类查询,query_rewrite()会智能改写模糊表达(如"想做个蛋"→更利于检索的表达),明确查询则保持原样; - 父子文档聚合:
get_parent_documents()按子块命中次数统计父文档相关性并智能去重; - 多模式生成:
detail类问题走generate_step_by_step_answer(),按"菜品介绍/所需食材/制作步骤/制作技巧"的结构化提示词输出分步指导;general类问题走generate_basic_answer()输出常规回答;list类问题由generate_list_answer()直接返回去重后的菜名列表;
- 流式输出:
generate_step_by_step_answer_stream()/generate_basic_answer_stream()基于 LCEL 的chain.stream()实现逐字输出,配合 main.py 中print(chunk, end="", flush=True)呈现打字机效果。
若用户问"太阳蛋怎么做",链路将呈现为:查询路由判定detail→ 重写后混合检索命中太阳蛋相关子块 → 聚合完整父文档 → 分步指导模式生成包含原料配比与双火候操作分支的详细回答。
五、一键运行:把太阳蛋"喂"给 RAG 系统
完整系统由 code/C8/main.py 的RecipeRAGSystem协调:initialize_system()按依赖顺序初始化数据准备、索引构建、生成集成模块;build_knowledge_base()优先加载已保存索引;run_interactive()提供命令行交互问答。
运行前提:
- 按 code/C8/requirements.txt 安装依赖(langchain==0.3.26、faiss-cpu、sentence-transformers、rank_bm25 等);
- 设置环境变量
MOONSHOT_API_KEY(默认 LLM 为kimi-k2-0711-preview,见 code/C8/config.py); - 默认数据路径
../../data/C8/cook与嵌入模型BAAI/bge-small-zh-v1.5均已在 code/C8/config.py 中配置好,top_k=3、temperature=0.1、max_tokens=2048可按需调整。
# 在 code/C8 目录下 export MOONSHOT_API_KEY="your_api_key" python main.py启动后即可交互提问,例如:"太阳蛋怎么做"(detail 分步指导)、"推荐几道简单的早餐"(list + 元数据过滤)。整体架构与运行说明还可参考 docs/chapter8/01_env_architecture.md,各模块实现细节见 docs/chapter8/02_data_preparation.md、docs/chapter8/03_index_retrieval.md、docs/chapter8/04_generation_sys.md。
六、小结
一份看似简单的"太阳蛋"食谱,在 all-in-rag 的 C8 系统中完整经历了 RAG 的四大环节:数据准备(目录路径推断分类、星号正则识别难度、三级标题结构分块)→索引构建(BGE 向量化 + FAISS 缓存)→混合检索(向量语义 + BM25 关键词 + RRF 融合 + 元数据过滤)→生成集成(查询路由、智能重写、父子文档聚合、多模式与流式输出)。掌握这条链路,你就能将任何规范化的 Markdown 文档集快速改造为可问答、可推荐的领域知识库——这也正是 all-in-rag 项目"从文档到生产级 RAG"的核心工程价值所在。
【免费下载链接】all-in-rag🔍大模型应用开发实战一:RAG 技术全栈指南,在线阅读地址:https://datawhalechina.github.io/all-in-rag/项目地址: https://gitcode.com/datawhalechina/all-in-rag
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考