第 11 章 · RAG 与知识库
模型不知道你的私有资料
那个跨境卖家有一份厚厚的《选品与合规手册》——哪些品类进哪个国家要认证、哪些材质在欧盟受限、退货政策怎么写。他问 Agent:“这款含锂电池的玩具能发德国吗?”
Agent 答得头头是道,却是编的——因为这份手册是他公司内部的资料,模型训练时根本没见过。模型只在“它训练时的公开知识”范围内博学,对你的私有文档、最新政策、内部 SOP 一无所知。而这些,恰恰是企业场景里最需要 Agent 掌握的。
怎么让 Agent 基于你的资料回答,而不是凭记忆瞎编?答案是当前最主流的一套方法:RAG。
RAG 是什么:先检索,再回答
RAG 全称 Retrieval-Augmented Generation(检索增强生成)。名字听着唬人,思路特别朴素——回答之前,先去资料库里查一查:
- 用户提问。
- 系统从你的资料库里,检索出与问题最相关的几段内容。
- 把这几段内容连同问题一起交给模型。
- 模型基于检索到的资料生成回答。
这就像开卷考试:模型不靠“背下来的记忆”答题,而是先翻到相关那几页,照着页面内容作答。RAG 的最大价值就是大幅减少胡编乱造——让 Agent 的回答有据可依,而且这个“据”是你自己的、可更新的资料。
支撑 RAG 的几个概念
要让“检索”这一步真正好用,得理解几个关键术语。它们环环相扣:
- Embedding(文本向量):把一段文字转换成一串数字(向量),这串数字能表示文字的语义。神奇之处在于——“椰奶鸡汤怎么做”和英文的 “chicken in coconut milk soup” 字面完全不同,但它们的向量会很接近,因为意思相近。
- Vector DB(向量数据库):专门存储这些向量、并支持“找语义最接近的内容”的数据库。普通数据库搜的是关键词精确匹配,向量数据库搜的是“意思相近”。
- 切片(Chunking):文档往往很长,不能整篇塞给模型。先把它切成一小段一小段(chunk),每段单独生成 Embedding 存进向量库,检索时按段命中。
- 混合检索(Hybrid Search):结合关键词搜索(精确匹配术语、型号)和语义搜索(意思相近),命中率比单用一种更高。查“CE 认证”这种精确术语时关键词更准,查“能不能安全出口”这种模糊问法时语义更准。
把它们串起来,就是 RAG 的完整数据流:
建库阶段: 文档 → 切片 → 每片生成 Embedding → 存入 Vector DB
检索阶段: 用户问题 → 生成 Embedding → 在 Vector DB 里找最相近的几片 → 交给模型回答
用 pi 实现:知识库是“接进来”的能力
这里要点明 pi 的定位——pi 是一个 harness,它不内建知识库。这不是缺点,而是回到全书的主线:知识库是一种你在 harness 之上接入的能力,而 pi 给了你三种自然的接入方式。
方式一:把检索做成一个工具(第 4 章)。 最直接——你自己维护向量库,封装一个 search_knowledge 工具,Agent 需要时调用它:
pi.registerTool({
name: "search_knowledge",
description: "在公司《选品与合规手册》里检索相关内容。回答合规/选品问题前必须先调用它。",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "要检索的问题或关键词" },
topK: { type: "number", description: "返回最相关的几段,默认 5" },
},
required: ["query"],
},
async execute({ query, topK = 5 }) {
const queryVec = await embed(query); // 问题转向量
const hits = await vectorDb.search(queryVec, topK); // 向量库检索最相近的片段
return { chunks: hits.map((h) => ({ text: h.text, source: h.source })) };
},
});
模型问“含锂电池玩具能发德国吗”时,会先调 search_knowledge,拿到手册里相关的几段(Observation,回顾第 3 章),再基于这些真实条款回答,并能标出来源。这就是把 RAG 变成了 ReAct 循环里的一次工具调用。
方式二:接一个 RAG 类 MCP Server(第 5 章)。 如果已有现成的知识库服务(很多向量库/文档平台提供 MCP 接口),直接在配置里连上,它的检索工具就自动注册进 Agent,一行业务代码都不用写。
方式三:做成技能(第 7 章)。 把“如何用这个知识库、什么时候该查、怎么引用来源”打包成一个技能,让 Agent 按需加载这套检索工作流。
三种方式共享同一个内核:Agent 本身不存知识,它只是在需要时‘去查’——检索这一步,是一次工具调用。
pi 里的标准 RAG 配方
如果把这件事落成工程,pi 里最稳的一条路径不是“把知识库塞进 Agent”,而是把 RAG 拆成四个边界清楚的部件:
资料入库层 文档/PDF/网页 → 清洗 → 切片 → embedding → 向量库/全文索引
检索服务层 query → hybrid search → rerank → 返回带来源的片段
pi 工具层 search_knowledge 工具 / MCP server / Skill 调用检索服务
上下文层 把少量高质量片段注入本轮 context,让模型基于资料回答
这四层各司其职:
| 层 | 负责什么 | 不负责什么 |
|---|---|---|
| 资料入库层 | 文档解析、切片、生成向量、更新索引 | 不参与对话,不决定怎么回答 |
| 检索服务层 | 根据问题找最相关片段,返回 text/source/score | 不把整库塞给模型 |
| pi 工具层 | 把检索包装成 Agent 可调用的能力 | 不持有长期知识本身 |
| 上下文层 | 只把本轮需要的片段放进模型窗口 | 不替代 memory,也不永久保存知识 |
一个实用的返回结构可以长这样:
return {
chunks: hits.map((hit) => ({
text: hit.text,
source: hit.source, // 文档名 / URL / 页码 / 段落 ID
score: hit.score,
updatedAt: hit.updatedAt,
})),
query,
};
这比只返回一段纯文本更可靠,因为后续章节要用到这些元数据:
- 引用来源:回答里能标出“依据哪份手册哪一节”。
- 评测回归:附录 D 会用
source和score检查 RAG 是否命中了正确资料。 - 可观测性:第 25 章可以按
query/source/tool/sessionId追踪哪些知识被用过。 - 知识更新:
updatedAt能帮助你发现 Agent 是否引用了过期片段。
也就是说,pi 的 RAG 落地重点不是把向量库选得多花哨,而是把“检索”包装成一个可审计、可测试、可观测的工具调用。
RAG、Context、Memory 的边界
RAG 最容易和上下文、记忆混在一起。可以按这个问题来分:
| 概念 | 它回答的问题 | 在 RAG 链路里的位置 |
|---|---|---|
| RAG / Knowledge | “外部资料里有什么证据?” | 通过工具或 MCP 临时检索出来 |
| Context | “这一次模型调用实际看见什么?” | 检索命中的少量片段被注入当前窗口 |
| Memory | “下次还值得记住什么用户偏好/长期事实?” | 可能决定检索哪个库,但不替代知识库 |
| State | “本轮任务跑到哪一步?” | 记录已经查过哪些 query、哪些来源已用过 |
一个常见误区是把“知识库命中的片段”写进长期 memory。这样会让 memory 变成噪声仓库:今天命中的某段政策,明天可能已经过期;某次任务的检索结果,也不一定值得跨会话保留。更好的做法是:
- 原始资料和向量索引放在 Storage。
- 可复用的用户偏好放进 Memory。
- 当前任务已查过什么放进 State。
- 本轮需要模型看的片段才放进 Context。
这也是附录 B 那张 Storage / Memory / State / Context 对比表的实际用法。
动手看看
挑一份你自己的文档(一份 PDF 手册、一批 Markdown 笔记都行),想清楚三件事:怎么把它切片、用什么生成 Embedding、存进哪个向量库。然后按“方式一”封装一个 search_knowledge 工具接进 pi。你会直观体会到:RAG 的“魔法”其实就是把一次向量检索的结果,塞进 Agent 的上下文——难点不在接入,而在“切片切得好不好、检索准不准”。
实战中的几个坑
坑一:切片粒度不当。
- 现象:检索回来的片段要么太碎(缺上下文)、要么太大(塞爆上下文还不精准)。
- 原因:chunk 大小没调好,或按字数硬切、切断了语义。
- 对策:按语义单元(段落、小节)切,控制在几百 token,并让相邻片段有少量重叠。
坑二:只用语义搜索,精确术语反而搜不准。
- 现象:查“CE-EN71 认证”这种精确型号,语义检索给回一堆“相关但不对”的内容。
- 原因:纯语义搜索对专有名词、编号不敏感。
- 对策:用 hybrid search,关键词 + 语义结合。
坑三:检索到的内容不加约束就让模型自由发挥。
- 现象:模型把检索资料和自己的“记忆”混着答,又开始编。
- 原因:没在提示里明确“只依据检索内容回答”。
- 对策:指令里写清“仅基于检索到的资料回答,资料里没有就明说没有”,并要求标注来源。
坑四:知识库不更新,答的是过期政策。
- 现象:手册改了,Agent 还在按老条款回答。
- 原因:向量库建好后就没再同步。
- 对策:文档更新时重新切片、重建对应片段的向量;把“建库”做成可重跑的流程。
对比:其他框架
知识库/RAG 是各框架成熟度差异很大的一块:
| 框架 | RAG/知识库支持 | 特点 |
|---|---|---|
| LangGraph | 依托 LangChain 的丰富 retriever/向量库生态 | 组件最全,可深度定制 |
| CrewAI | 可给角色配知识源,也可接 LangChain 检索 | 以角色为中心挂知识 |
| PydanticAI | 无内建知识库,检索需自己实现为工具/依赖 | 保持轻量,把 RAG 留给你拼 |
| Agno | 一等能力:内置 Knowledge + 向量库 + Embedder | 开箱即用,配置即得 RAG |
| pi | 不内建,作为工具/MCP/技能接入 | harness 视角,检索即一次工具调用 |
分野很清楚:Agno 这类应用框架把 RAG 做成了内置组件,搭起来最快;pi 这类 harness 则把它当作“接入的能力”,更灵活、也更贴合“用哪个向量库、怎么切片都由你定”的真实工程需求。底层方法都是同一套 RAG:检索增强生成。
小结
- 模型不知道你的私有资料,RAG(检索增强生成) 让 Agent“先查资料再回答”,大幅减少胡编。
- 支撑 RAG 的关键概念:Embedding(语义向量)、Vector DB(向量库)、切片、Hybrid Search(混合检索),串成“建库 + 检索”两个阶段。
- pi 不内建知识库,而是把它作为工具 / MCP Server / 技能接入——检索本质上是 ReAct 循环里的一次工具调用。
- pi 的标准 RAG 配方是:资料入库层、检索服务层、pi 工具层、上下文层四层分开;检索返回要带
source/score/updatedAt,方便引用、评测与观测。 - RAG 不是 Memory:知识放在 Storage,需要时检索成 Context;长期偏好才进 Memory,任务进度属于 State。
- 实战要点:切片按语义、检索用混合、回答只依据检索内容并标来源、知识库要能更新。
下一章回到会话本身——多轮对话是怎么组织和持久化的。