Skip to content

本地知识 Agent 的受限检索与来源核对 ​

知识助手要交付一份能核对依据的草稿,就必须记录它实际读过什么,并限制它还能读什么。 把文档交给模型、要求“不要编造”,无法替代这些程序约束。

前面的 Agent 开发实战 介绍了工具循环和笔记适配方案。这篇沿着一个独立本机应用的实现,解释“选择资料 → 检索 → 阅读 → 生成 → 核对”怎样落到产品中。案例为 2026-10-27 的单用户版本,使用自建运行循环和本地 SQLite;示例资料均为虚构。

1 从一个能核对的小任务开始 ​

用户选择一个知识库,输入“整理发布相关资料,总结检查步骤和待确认事项”,还可以限定资料的更新时间范围。当前日期筛选按 UTC 日边界解释,界面也会标明这一点。

用户的选择先被服务端保存为任务范围。随后模型只能提出两种请求:搜索资料,以及读取命中的资料。保存草稿由另一个用户操作触发,生成阶段的工具列表里没有写入能力。

text
用户选择知识库与时间范围
        ↓
服务端创建任务并固定范围
        ↓
模型选择关键词 → 搜索候选
        ↓
模型选择候选 → 读取片段并登记来源
        ↓
校验草稿结构和引用 → 展示正文与来源
        ↓
用户核对后另行保存

这个版本的读取范围是任务创建时的知识库和时间条件。它没有多用户登录体系,不能把本机固定身份直接搬成多人服务的权限模型。

2 搜索候选和读取正文是两件事 ​

搜索工具返回标题、摘要、版本和是否还有下一页;读取工具才返回用于生成草稿的正文片段。这样可以先选资料,再为选中的内容支付上下文成本。

案例中的搜索是标题与正文的关键词匹配,结果主要按更新时间排列。没有向量索引,也没有语义相关度排序。面对小规模资料,这种实现方便解释和排查;面对同义表达、模糊问题或大量长文档,召回能力仍需单独改进。

读取时还会检查四件事:

  1. 文档标识是否来自本任务已经命中的搜索结果。
  2. 文档是否仍属于当前本地用户和选定知识库。
  3. 最新的更新时间是否仍在任务范围内。
  4. 本任务是否已经读取过这个文档版本。

因此,模型即使编造了一个格式合法的文档标识,也不能直接拿它读取任意资料。参数格式校验和资源范围校验承担不同职责,两者都要执行。

一个容易遗漏的长文档问题 ​

当前搜索摘要围绕命中位置截取,正文读取却只取前 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、外部图片和可点击外链不会作为可执行内容进入预览。

这可以减少脚本执行和外部资源加载风险,但不承担事实核验。来源核对仍要看到原始文字、版本以及截断提示。

“在本机运行”也不意味着资料始终不出本机。启用受支持的远程模型后,任务目标、搜索摘要和实际读取片段会随模型请求发送出去。案例把远程调用配置与界面提示放在开始整理之前,密钥由服务端持有。

怎样判断这条链路是否可用 ​

从一份虚构资料开始,检查草稿引用能否定位到实际读取的版本。再分别尝试越界标识、重复读取、无效引用、检索无命中、工具失败和用户取消,核对每种情况是否留下准确状态。

长文档要特别检查“命中内容在读取窗口之外”的情况。模型选择的关键词是否合适、是否遗漏关键资料、总结是否忠实,都需要有预期答案的评测材料;工具调用成功只是其中一步。

延伸阅读 ​

为复用而记录,为理解而整理。