本地知识 Agent 的受限检索与来源核对
知识助手要交付一份能核对依据的草稿,就必须记录它实际读过什么,并限制它还能读什么。 把文档交给模型、要求“不要编造”,无法替代这些程序约束。
前面的 Agent 开发实战 介绍了工具循环和笔记适配方案。这篇沿着一个独立本机应用的实现,解释“选择资料 → 检索 → 阅读 → 生成 → 核对”怎样落到产品中。案例为 2026-10-27 的单用户版本,使用自建运行循环和本地 SQLite;示例资料均为虚构。
1 从一个能核对的小任务开始
用户选择一个知识库,输入“整理发布相关资料,总结检查步骤和待确认事项”,还可以限定资料的更新时间范围。当前日期筛选按 UTC 日边界解释,界面也会标明这一点。
用户的选择先被服务端保存为任务范围。随后模型只能提出两种请求:搜索资料,以及读取命中的资料。保存草稿由另一个用户操作触发,生成阶段的工具列表里没有写入能力。
text
用户选择知识库与时间范围
↓
服务端创建任务并固定范围
↓
模型选择关键词 → 搜索候选
↓
模型选择候选 → 读取片段并登记来源
↓
校验草稿结构和引用 → 展示正文与来源
↓
用户核对后另行保存这个版本的读取范围是任务创建时的知识库和时间条件。它没有多用户登录体系,不能把本机固定身份直接搬成多人服务的权限模型。
2 搜索候选和读取正文是两件事
搜索工具返回标题、摘要、版本和是否还有下一页;读取工具才返回用于生成草稿的正文片段。这样可以先选资料,再为选中的内容支付上下文成本。
案例中的搜索是标题与正文的关键词匹配,结果主要按更新时间排列。没有向量索引,也没有语义相关度排序。面对小规模资料,这种实现方便解释和排查;面对同义表达、模糊问题或大量长文档,召回能力仍需单独改进。
读取时还会检查四件事:
- 文档标识是否来自本任务已经命中的搜索结果。
- 文档是否仍属于当前本地用户和选定知识库。
- 最新的更新时间是否仍在任务范围内。
- 本任务是否已经读取过这个文档版本。
因此,模型即使编造了一个格式合法的文档标识,也不能直接拿它读取任意资料。参数格式校验和资源范围校验承担不同职责,两者都要执行。
一个容易遗漏的长文档问题
当前搜索摘要围绕命中位置截取,正文读取却只取前 4,000 个 JavaScript 字符单位。如果关键词出现在文档后半部,搜索摘要可能显示命中,后续读取片段却不包含那段证据。
遇到这种情况,不能把“已经找到这篇文章”写成“已经阅读相关内容”。进一步改进时,可以让读取接口接受受限的片段位置,或者围绕命中段落返回窗口;每个窗口仍要保留位置、版本和截断信息。这是下一步设计,当前实现尚未提供。
3 来源编号由运行时分配
模型可以写引用标记,来源记录应由真正执行读取的程序生成。通用的来源结构可以是:
json
{
"ref": "S1",
"documentId": "example-release-note",
"version": 3,
"excerpt": "发布前应完成构建检查。",
"truncated": false
}这里保存的是实际读取时的版本和片段。文档随后发生变化,核对界面仍能展示生成草稿时的依据,同时提示当前资料已有新版本。
例如草稿写着“发布前完成构建检查 [S1]”,用户点开来源,应看到上面的原始片段,而不是悄悄替换成文档的新内容。否则一份旧回答会被配上新的依据,事后很难还原。
引用合法不等于结论正确
当前运行时检查:草稿至少包含一个来源标记,且出现的所有来源编号都属于实际读取记录。通过检查后,只保留被引用的来源,并对截断资料增加提示。
这能拦住不存在的来源编号,但不能证明每句话都有依据,也不能证明模型正确理解了原文。“主要结论应标注来源”仍包含提示要求和人工核对工作。段落与证据的逐项对应、矛盾识别、事实评测,需要另外设计。
4 用程序给工具循环设置边界
任务限制应该跟随整个任务。模型换一个关键词,不应获得一套新的检索额度。
| 约束 | 案例中的策略 |
|---|---|
| 搜索范围 | 全任务最多搜索两页,每页五条 |
| 正文读取 | 最多三篇,每篇前 4,000 个 JavaScript 字符单位 |
| 重复工作 | 同参数搜索、同版本读取会触发无进展终止 |
| 工具能力 | 仅有搜索和读取;未知工具和越界读取会终止任务 |
| 运行预算 | 同时限制模型步骤、工具次数、总时长、单次请求时长和上下文大小 |
这些数量是应用策略,不是模型能力的上限。字符长度也不是 Token 数:上下文长度检查和服务返回的 Token 统计分别计量,不能混用。
Token 用量在响应返回后累计检查;提供方没有返回用量时,程序会估算。因此这个阈值能停止后续工作,却不是账单的硬上限。增大模型上下文窗口,也不会自动增加本任务允许读取的资料范围。
参数格式错误会形成明确的工具失败结果,模型可以在剩余预算内调整请求;越界、未知工具和重复工作等错误则会结束任务。把所有失败统一当成“再试一次”,容易产生无意义循环。
5 让用户看见任务实际做了什么
运行记录应回答“做到了哪一步”,而不只是不断刷新一个“正在思考”的提示。案例记录任务开始、模型请求、工具开始、工具完成、工具失败和草稿就绪;工具事件带调用标识,便于对应请求与结果。
几个状态尤其要分开:
- 资料不足:已经完成有效检索,但现有材料不足以生成草稿。
- 执行失败:模型请求、工具或输出校验失败。
- 已取消:用户要求停止,后续步骤停止执行。
- 已中断:服务退出或重启打断任务,已有记录仍然保留。
- 待确认:草稿生成完毕,等待用户核对和采纳。
当前实现至少要求完成一次有效搜索,才接受模型返回“资料不足”。搜索失败不会被直接改写成“知识库没有相关内容”。
取消信号会传到模型请求,并在模型返回后和执行工具前再次检查。进程重启后,运行中的任务被标记为中断;已有历史供查看,但不会自动重放整条工具链。任务记录恢复和自动续跑是不同的能力。
6 草稿预览也有执行边界
资料正文和模型输出都作为不可信内容处理。预览会先解析 Markdown,再只保留允许的 HTML 结构;原始 HTML、外部图片和可点击外链不会作为可执行内容进入预览。
这可以减少脚本执行和外部资源加载风险,但不承担事实核验。来源核对仍要看到原始文字、版本以及截断提示。
“在本机运行”也不意味着资料始终不出本机。启用受支持的远程模型后,任务目标、搜索摘要和实际读取片段会随模型请求发送出去。案例把远程调用配置与界面提示放在开始整理之前,密钥由服务端持有。
怎样判断这条链路是否可用
从一份虚构资料开始,检查草稿引用能否定位到实际读取的版本。再分别尝试越界标识、重复读取、无效引用、检索无命中、工具失败和用户取消,核对每种情况是否留下准确状态。
长文档要特别检查“命中内容在读取窗口之外”的情况。模型选择的关键词是否合适、是否遗漏关键资料、总结是否忠实,都需要有预期答案的评测材料;工具调用成功只是其中一步。
延伸阅读
- AI 草稿保存的幂等与版本校验:用户确认以后怎样可靠地写入。
- 周报 Agent 的规则降级与草稿交付:步骤固定时如何组织生成流程。
- RAG 检索增强生成:进一步理解分块、召回和上下文组织。
- LLM 应用安全:不可信资料、工具权限与输出渲染。