第 8 / 15 章 约 8 分钟
RAG 与知识库
理解检索、引用、知识库与信息隔离。
内容同步:2026年10月25日 · 文档版本 6
这个模块在 Agent 里的位置
这个模块决定 Agent 从哪里获得可信事实。它影响:
- 模型是否引用真实来源。
- 知识是否过期。
- 用户偏好是否被错误当成事实。
- 多租户数据是否隔离。
- 检索结果是否会撑爆上下文。
很多人把 RAG 理解成“向量库 + topK”。工程上远远不够。
一个生产级检索链路至少要处理:
- 租户过滤。
- 结构化过滤。
- FTS / 向量混合召回。
- rerank。
- 引用校验。
- 知识版本和新鲜度。
- 记忆删除。
先记住几个概念
| 概念 | 含义 | 为什么重要 |
|---|---|---|
| Working context | 当前 turn 可见消息 | 短期记忆 |
| Session history | 当前任务历史 | 任务期记忆 |
| Long-term memory | 用户偏好、历史结论 | 跨任务记忆 |
| Knowledge base | 外部事实、文档、指标 | 独立事实源 |
| sourceId | 稳定来源 ID | 引用可校验 |
| version | 文档版本 | 支持审计和过期判断 |
| tenant scope | 租户隔离边界 | 防止跨租户泄漏 |
从一个真实问题开始:模型引用了一篇过期文档
用户问:
根据最新供应商资料,判断这家供应商是否有交付风险。
模型回答:
供应商 A 已具备稳定交付能力。
用户反馈这是错的。排查后发现:
- 知识库里有两份同名文档。
- 旧文档是 2024 年版本。
- 新文档是 2026 年版本。
- 向量检索按相似度召回了旧文档。
- 系统没有把 updatedAt 和 version 传给模型。
- 输出校验只检查引用存在,不检查是否最新。
这不是“模型幻觉”,而是知识契约缺失。
记忆和知识必须分开
很多人会把“记忆”和“知识库”混在一个表里。这会带来两个问题:
- 用户偏好被当成事实。
- 事实更新后,用户记忆还保留旧结论。
更合理的边界是:
| 类型 | 内容 | 生命周期 | 举例 |
|---|---|---|---|
| Long-term memory | 用户偏好、历史结论 | 跨任务、可删除 | “报告偏好中文” |
| Knowledge base | 外部事实、文档、指标 | 独立事实源、有版本 | “供应商 2026 年资质文件” |
记忆可以是:
{
"memoryType": "output_preference",
"content": "报告偏好中文",
"confidence": 0.92,
"sourceRunId": "run-88",
"expiresAt": "2026-12-31T00:00:00Z"
}
知识应该是:
{
"sourceId": "doc-9182",
"version": 12,
"updatedAt": "2026-09-23T08:00:00Z",
"confidence": 0.87
}
这两者不能互相替代。
检索不是只做向量 topK
在一个 5000 万文档、2000 租户的知识库里,正确路径通常是:
结构化过滤
-> FTS / 向量召回
-> rerank
-> topK 10
-> 返回 sourceId / version / updatedAt
结构化过滤必须先做:
- tenantId
- sourceType
- updatedAt
- entityRef
- 权限范围
如果跳过结构化过滤,直接全库向量检索,会出现:
- 跨租户数据进入候选。
- 过期文档排在前面。
- 无权限文档被召回。
- 相似但无关的文档干扰判断。
多租户隔离不能交给模型
tenant scope 必须由服务端注入。
错误请求:
{
"query": "供应商 A 的风险",
"tenantId": 7
}
如果 tenantId 来自模型输出,就有被改写风险。
正确做法:
{
"query": "供应商 A 的风险",
"tenantScope": {
"tenantId": 7,
"source": "server"
}
}
并在查询层强制:
where tenant_id = $1
模型永远只能提出“查什么”,不能决定“查哪个租户”。
引用必须可校验
RAG 输出不能只是“根据资料,供应商 A 有风险”。
应该返回:
{
"answer": "供应商 A 存在交付风险",
"sourceRefs": [
{
"sourceId": "doc-9182",
"version": 12,
"updatedAt": "2026-09-23T08:00:00Z",
"quote": "交付延迟记录"
}
]
}
Runtime validator 至少检查:
- sourceId 是否存在。
- version 是否是当前版本或允许版本。
- tenantId 是否匹配。
- 引用是否真的支持结论。
最后一点可以用引用一致性校验或人工抽检兜底。
知识更新和过期处理
知识库需要显式版本:
create table knowledge_documents (
id bigint primary key,
tenant_id bigint not null,
source_type text not null,
title text not null,
content_ref text not null,
version int not null,
updated_at timestamptz not null,
unique (tenant_id, id, version)
);
create table knowledge_chunks (
id bigint primary key,
tenant_id bigint not null,
document_id bigint not null,
document_version int not null,
chunk_index int not null,
chunk_hash text not null,
content text not null,
unique (tenant_id, document_id, document_version, chunk_index)
);
设计要点:
- 文档和 chunk 都有版本。
- 相同 hash 不重复入库。
- 检索时可以按版本过滤。
- 旧版本可归档,不建议直接删除。
记忆要可删除、可降权
用户记忆表:
create table user_memories (
id bigint primary key,
tenant_id bigint not null,
user_id bigint not null,
memory_type text not null,
content text not null,
confidence numeric not null,
source_run_id bigint,
created_at timestamptz not null,
expires_at timestamptz
);
create index user_memories_lookup_idx
on user_memories (tenant_id, user_id, memory_type, updated_at desc);
必须支持:
- 用户删除。
- 过期降权。
- 按 run 追溯来源。
- 记忆污染清理。
否则一次错误总结会长期影响后续任务。
RAG 结果如何进入上下文
检索结果也应该瘦身:
{
"query": "供应商 A 风险",
"results": [
{
"sourceId": "doc-9182",
"version": 12,
"snippet": "2026 年二季度出现两次交付延迟",
"score": 0.91
}
],
"totalCandidates": 1240,
"truncated": true
}
不要把整篇文档直接塞给模型。
如果需要全文,再通过 sourceId + version 精查。
和传统后端存储设计的关系
记忆和知识库的选型,和传统后端的存储设计是同一套思路:
| Agent 里的需求 | 传统后端里的对应物 |
|---|---|
| 短期记忆 | Redis / 本地缓存 |
| 长期记忆 | Postgres / MySQL 用户表 |
| 知识库 | 搜索引擎 + 对象存储 |
| 向量检索 | 向量数据库 / ANN 索引 |
| 版本和审计 | 不可变版本表 |
| 租户隔离 | 行级权限 / 分库分表 |
选型时仍然要看:
- 读写比例。
- 一致性要求。
- 查询模式。
- 数据生命周期。
- 成本和运维复杂度。
Agent 只是把“检索结果进入模型上下文”这一步加进了传统存储链路。
常见工程错误
| 错误 | 实际后果 | 正确做法 |
|---|---|---|
| 记忆当事实 | 旧结论误导当前任务 | 当前事实查知识库 |
| 无租户过滤 | 跨租户数据泄漏 | 服务端注入 tenant scope |
| 只做向量召回 | 过期或无关文档进入上下文 | 结构化过滤 + FTS + rerank |
| 引用只检查存在 | 伪造或过期引用通过 | 校验版本、租户和一致性 |
| 知识无版本 | 无法审计 | document version |
| 记忆不可删除 | 合规和污染风险 | user delete API |
| 检索返回全文 | 上下文膨胀 | snippet + sourceId |
上线前检查
- 记忆和知识分表。
- 记忆可删除、可过期。
- 知识有版本和更新时间。
- 检索先做租户和结构化过滤。
- FTS / 向量 / rerank 组合使用。
- 引用包含 sourceId、version、updatedAt。
- 输出校验引用真实存在。
- 检索结果进入上下文前先瘦身。