7.0 KiB
03 Tool 事实边界:让模型看到必要信息,系统保留真相
这一篇只回答一个问题:Tool 返回的数据应该由谁保管,模型究竟可以看到多少?
1. 先看一个具体问题
Agent 查询支付日志,后端一次返回了几千行内容,其中包含重复堆栈、敏感字段和远超上下文窗口的正文。
直接把原始结果塞回模型会产生几个问题:
- 敏感数据进入模型上下文;
- 大结果挤占 Token,真正关键的证据反而被淹没;
- 模型后续引用经过裁剪的文本,系统无法证明它对应哪次真实调用;
- 若只保存裁剪结果,EvidenceGuard 又失去了独立验真的事实来源;
- 若把完整 raw 长期写入数据库,泄露面和存储成本都会扩大。
所以 Tool 设计的核心不是“怎样调用后端”,而是谁拥有原始事实,以及不同消费者应该看到哪一层数据。
2. 一次 Tool 调用的数据怎样变化
flowchart LR
REQ["Typed Tool Request"] --> B["ToolBoundary<br/>权限、只读、预算、大小检查"]
B --> RAW["Backend Raw Result"]
RAW --> P["Tool-specific Projector"]
P --> C["Canonical Invocation<br/>短期保存 request、raw、agent_result 和状态"]
C --> CV["Control View<br/>供 Harness 判断状态和进展"]
C --> MO["Model Observation<br/>供 Agent 推理的白名单内容"]
C --> EG["EvidenceGuard<br/>按当前 Run 独立验真"]
B -.-> AU["Durable Audit<br/>长期只留有界元数据"]
第一次阅读只需区分三份内容:
- Canonical Invocation:系统在当前 Run 内短期保管的完整调用真相。
- Control View:Harness 用来判断状态、scope 和证据数量的控制字段。
- Model Observation:模型真正看到的安全、有限内容。
3. ToolBoundary:所有 Tool 的统一入口
RAG、日志和 MySQL 后端完全不同,但它们都必须遵守相同的不变量:
- Run 仍然 active,调用 ID 与当前 Run 匹配;
- Tool 已授权,请求声明和实际行为保持只读;
- 调用前预算允许,结果大小没有越界;
- canonical 状态只能从
PROJECTING进入READY或ERROR; - 长期 Audit 不保存完整请求和 raw response。
如果每个 Adapter 自己实现这些规则,三种 Tool 很快会出现不同的错误码、预算顺序和存储行为。ToolBoundary 集中处理共同规则,Adapter 只处理业务协议。
ToolBoundary 不判断信息增益,也不判断某条日志是否支持根因。它只保证调用合法、状态可信、结果有界。
主要代码入口:ToolBoundary、ToolCallRequestEnvelope、ToolBoundaryResult、ToolBoundaryErrorCode。
4. Projector:把后端结果变成稳定事实
不同后端的 raw 数据不能直接成为 Agent 契约:
- RAG 需要限制证据数量和摘录长度,并保留文档身份;
- Logs 需要限制事件、聚合 pattern,明确实际时间范围和是否截断;
- MySQL 需要限制行、列、单元格和总 bytes,并保留查询 scope。
每种 Tool 使用自己的 ToolResultProjector。Projector 负责标准化和计算客观 EvidenceStatus,但它不调用模型,也不判断这些事实是否足以支持最终结论。
这是刻意保留的业务差异。强行做一个“万能 JSON Cleaner”,往往会丢掉每类证据真正需要的身份和范围信息。
5. Canonical Store:为什么要保留独立真相
Agent Observation 是为推理优化的,它会被裁剪和白名单投影,不能反过来充当验真依据。Canonical Store 保存当前调用的 request、raw、标准化 agent_result、状态和时间,使 EvidenceGuard 能绕开 Agent 上下文独立读取事实。
当前选择 Redis TTL,是因为完整调用记录:
- 只在当前 Run 的验证阶段需要;
- 可能包含敏感内容,不应永久保存;
- 需要按
runId + tool_call_id快速定位; - 容量必须有硬上限,读取不能自动续期。
raw 超限时选择失败,而不是悄悄截断。因为一旦截断,canonical record 就不再代表后端真实返回,后续验真会建立在不完整事实之上。
主要代码入口:CanonicalInvocationStore、RedisCanonicalInvocationStore、CanonicalToolInvocation、CanonicalInvocationLimits。
6. Model Observation:模型只拿完成任务所需的内容
即使 canonical agent_result 已经标准化,其中仍可能包含 Harness 控制字段。ToolResultViewProjector 会进一步拆成:
Control View:status、evidence count、scope、截断状态等
Model Observation:有界证据正文、可读来源和下一步推理所需字段
模型不会看到 raw response、Redis key、预算阈值、重复指纹和完整 Harness 状态。这样既减少上下文噪声,也避免模型利用或复述内部控制信息。
7. MySQL 为什么还需要单独的只读沙箱
readOnly=true 只是请求声明,不能证明模型生成的 SQL 安全。MySQL Tool 在进入真实执行前还要经过:
- JSqlParser AST 解析;
- 单条 SELECT 和保守语法子集限制;
- 数据源、schema、table、column 精确 allowlist;
- 禁止投影通配符和元数据探测;
- 只读账号、timeout、LIMIT 与最大行数。
Validator 产生已经批准的 MysqlQueryPlan,Executor 只接受这个 plan,不重新信任原始字符串。这是纵深防御:任何一层都不能单独被当作完整授权。
8. 为什么短期真相和长期审计要分开
flowchart TB
C["Canonical Store<br/>完整、敏感、短期"] -->|"用于"| V["当前 Run 验真"]
A["Durable Audit<br/>有界、元数据、长期"] -->|"用于"| O["排障、统计、成本对账"]
把两者合并会走向两个极端:要么长期复制所有敏感正文,要么为了安全只存元数据,导致当前 Run 无法验真。分开后,每种存储只承担自己的责任。
9. 为什么没有采用其他方案
| 方案 | 没有采用的原因 |
|---|---|
| raw 直接返回 Agent | 泄露、超限、噪声和无法独立验真 |
| 只保存 Agent Observation | 投影内容不是完整事实,Guard 会依赖模型看到的版本 |
| 完整 raw 永久写 MySQL | 扩大敏感数据暴露面和长期存储成本 |
| raw 超限后静默截断 | canonical truth 会变成不完整真相 |
| 所有 Tool 共用万能 Projector | 无法保持日志、RAG、表格各自的身份和 scope 语义 |
10. 先记住这些
- ToolBoundary 统一执行规则,Adapter 处理具体后端。
- Canonical Invocation 是系统真相,Model Observation 是模型视图。
- Agent 看到的内容不能反过来成为 EvidenceGuard 的事实来源。
- 完整真相短期保存,长期 Audit 只留有界元数据。
- Tool 找到候选内容,不代表它支持最终根因。
下一篇:04 验证与发布。
需要深入字段和存储取舍时,阅读Tool 双视图专题。