- Annotate 43 tool domain classes (contract/projection/boundary/store/adapter/mysql) - Add tool registration and execution chain learning note - Add tool call chain runtime journey note (model decision to observation)
9.2 KiB
Harness Tool 调用链:一次工具调用的完整旅程
更新日期:2026-08-03 主题:从「模型决定调用工具」到「模型收到观察」的运行时完整链路——拦截器 → invoke → Adapter → ToolBoundary → 返回 → 二次加工 → ToolCallResponse 结构篇:Harness tool 域代码学习笔记-工具的注册调用与执行链路(讲装配/注册/静态结构) 本文:动态时序(一次调用怎么跑完)
1. 旅程全景(一张图)
sequenceDiagram
participant M as 模型
participant F as 框架 ReactAgent
participant I as HarnessToolInterceptor(per-Run)
participant ET as HarnessEvidenceTools(单例)
participant AD as RagToolAdapter(单例)
participant TB as ToolBoundary(单例)
participant P as DiagnosisProgressTracker
rect rgb(240, 248, 255)
Note over M,I: 阶段 A:模型决定 → 拦截器(执行前)
M->>F: 输出 tool_call(工具名 + 参数 JSON)
F->>I: 回调 interceptToolCall(request, handler)
I->>I: ① supports 注册检查
I->>ET: ② parse(typed 严格契约)
ET-->>I: ParsedAgentToolCall(previous_observation + input)
I->>P: ③ 协议校验(pending 评价)+ 判重
end
rect rgb(255, 250, 240)
Note over I,TB: 阶段 B:invoke → 执行(backend + 投影)
I->>ET: ④ invoke(context, toolName, toolCallId, args)
ET->>AD: bridge 闭包 → adapter.execute(context, envelope)
AD->>TB: boundary.execute(context, envelope, executor, projector)
TB->>TB: ⑤ 五阶段:preflight/预算/begin → executor 跑 backend → 校验 → projector 投影 → markReady
TB-->>I: ToolBoundaryResult(READY/ERROR)
end
rect rgb(245, 255, 245)
Note over I,M: 阶段 C:返回 → 模型(执行后)
I->>I: ⑥ 双源校验(controlView 重读 evidence_status)
I->>P: ⑦ recordCompleted(NO_EVIDENCE 立即 NO_GAIN / FOUND 挂 pending)
I->>I: ⑧ modelObservation 加工(有界观察 + stop_required/reason)
I-->>F: ToolCallResponse.of(toolCallId, toolName, observation)
F-->>M: observation 作为本轮 tool 结果
end
三个阶段:A 执行前(模型决定→门禁)→ B 执行中(invoke→backend→投影)→ C 执行后(校验→记账→成型)。
2. 阶段 A:模型决定 → 拦截器(执行前)
2.1 模型怎么知道有这个工具
模型 → callbacks 里看到工具(名字+描述+Schema)→ 决定调用 lookup_knowledge
→ 输出 tool_call JSON(工具名 + 参数)
工具名是模型决定的——框架把模型输出包成 ToolCallRequest(含 toolName + arguments),回调拦截器。
2.2 拦截器的三道执行前门
flowchart LR
A["① supports(toolName)?"] -->|"否(非证据工具)"| X["handler.call 透传"]
A -->|"是"| B["② parse:typed 严格契约<br/>FAIL_ON_UNKNOWN_PROPERTIES + FAIL_ON_TRAILING_TOKENS"]
B -->|"违规"| Y["协议处理(不执行)"]
B --> C["③ 协议校验(pending 评价)+ 判重"]
C -->|"重复"| Z["recordDuplicateScope(不执行)"]
C -->|"通过"| D["进入阶段 B:invoke"]
关键:不是「拿到名字就执行」——parse(模型输出必须精确匹配 RagToolCall{previous_observation, input},多一个字段都炸 INVALID_ENVELOPE)、协议校验、判重,三道门不通过都不执行 backend。
3. 阶段 B:invoke → 执行(backend + 投影)
3.1 invoke 的委托链
I.invoke(context, "lookup_knowledge", "call-1", args)
→ ET.invokers.get("lookup_knowledge") ← 注册表取 bridge 闭包
→ bridge lambda:adapter.execute(context,
new ToolCallRequestEnvelope(runId, "call-1", "lookup_knowledge", args, true, true))
→ ragAdapter.execute(context, envelope)
→ boundary.execute(context, envelope, executor, projector)
envelope 是 bridge 里现造的:authorized=true, readOnly=true 写死——每个进 ToolBoundary 的信封都声明「已授权 + 只读」。
3.2 Adapter 组装两个函数(接线员)
return boundary.execute(context, envelope,
// executor:跑 backend 拿 raw(LookupResult 序列化成 JSON 文本)
ignored -> objectMapper.writeValueAsString(legacyExecutor.execute(request.query())),
// projector:raw → 有界契约 + evidenceStatus
raw -> projector.project(request, envelope.toolCallId(), raw));
| 端口 | 干什么 | 产物 |
|---|---|---|
executor |
调具体后端 | rawResponse(JSON 文本,执行链「货币」) |
projector |
净化定型 | ProjectedToolResult(agentResult, evidenceStatus) |
模型永远看不到 raw——raw 只用于校验、落 canonical、投影。
3.3 ToolBoundary 五阶段
flowchart TD
A["① preflight + Tool 预算 + request bytes → begin(PROJECTING)"]
B["② executor.execute(requestJson) → backend raw"]
C["③ raw 大小校验 + Run bytes 预留"]
D["④ projector.project(raw) → 有界 agent_result + evidenceStatus"]
E["⑤ agent_result 校验 + bytes → markReady(READY) 或 markError(ERROR)"]
A --> B --> C --> D --> E
返回 ToolBoundaryResult(READY/ERROR)——PROJECTING 永不外泄。
4. 阶段 C:返回 → 模型(执行后)
4.1 拦截器的二次加工(不是直接返回)
flowchart LR
A["ToolBoundaryResult"] --> B{"status == READY?"}
B -->|"否"| E1["error observation<br/>BUDGET_EXHAUSTED 额外 markBudgetLimitReached"]
B -->|"是"| C["⑥ 双源校验:controlView 重读 evidence_status"]
C -->|"不一致"| E2["OBSERVATION_CONTRACT_MISMATCH 拒绝"]
C -->|"一致"| D["⑦ recordCompleted<br/>NO_EVIDENCE → 立即 NO_GAIN<br/>FOUND → 挂 pending"]
D --> F["⑧ modelObservation 加工<br/>(有界观察 + stop_required/reason)"]
F --> G["ToolCallResponse.of(...) → 框架 → 模型"]
4.2 双源校验(自洽性防线)
源1:result.evidenceStatus() ← Projector 投影时计算的声明值
源2:controlView(agentResult).evidenceStatus() ← 从 agent_result 内容重读
一致 ? 通过 : OBSERVATION_CONTRACT_MISMATCH 拒绝
防止「声明有证据但内容空 / 声明无证据但内容有」的不一致状态进入 progress 记账。
4.3 给模型的对象形态
ToolCallResponse.of(toolCallId, toolName, observation)
observation = 有界观察:
正常结果:脱敏后的契约内容(可能裁剪)
饱和时: 附加 stop_required:true + reason
协议错误:repair_required:true + violation_type/期望ID/指令
框架把 observation 作为本轮 tool 结果给模型——模型下一轮读取它,决定继续调用(带评价)还是输出 Draft 收尾。
5. 旅程的衔接点(模型视角的闭环)
flowchart LR
A["模型调工具"] --> B["观察(有界契约)"]
B --> C{"模型决定"}
C -->|"继续"| D["下次 Tool Call + previous_observation 评价"]
C -->|"收尾"| E["输出 Draft → Release 发布"]
D --> B
progress 协议的闭环:模型每次继续调用,都要在 Envelope 里回带对上一轮的 GAINED/NO_GAIN 评价——这就是拦截器 ③ 校验的 pending 逻辑(可回看 progress 笔记)。
6. 关键点总结
| 阶段 | 关键认知 |
|---|---|
| A 执行前 | 工具名是模型决定的;parse 是 typed 严格契约(输出必须匹配 Schema);三道门不通过不执行 |
| B 执行中 | executor/projector 是 Adapter 组装进 boundary 的参数;执行链货币是 JSON 文本;模型永远看不到 raw |
| C 执行后 | 拦截器不直接返回——双源校验 + progress 记账 + modelObservation 成型;ToolCallResponse 才是模型拿到的对象 |
7. 面试 30 秒说法
"一次工具调用的完整旅程分三段:执行前,模型从 callbacks 看到工具并决定调用,拦截器做 supports 分流、typed 严格 parse、协议校验和判重——三道门不通过都不执行 backend;执行中,invoke 经 bridge 到 Adapter,Adapter 把 executor(跑 backend 拿 raw)和 projector(raw 投影成有界脱敏契约)组装进 ToolBoundary 的五阶段门禁,返回 ToolBoundaryResult;执行后,拦截器不直接返回——先双源校验 evidence_status,再 recordCompleted 记进度,再 modelObservation 加工成有界观察,最后包装成 ToolCallResponse 给模型。模型看到的永远是脱敏后有界的观察,raw 只进 canonical 供审计验真。"
8. 代码位置索引
| 环节 | 文件 |
|---|---|
| 拦截器(A/C 阶段) | src/main/java/com/superbiz/agent/harness/agent/HarnessToolInterceptor.java |
| 注册表 + parse + invoke | src/main/java/com/superbiz/agent/harness/agent/HarnessEvidenceTools.java |
| Adapter 组装(B 阶段) | src/main/java/com/superbiz/agent/harness/tool/adapter/RagToolAdapter.java |
| 五阶段门禁 | src/main/java/com/superbiz/agent/harness/tool/boundary/ToolBoundary.java |
| 双源校验 + 观察成型 | src/main/java/com/superbiz/agent/harness/agent/ToolResultViewProjector.java |
| 模型观察形态 | src/main/java/com/superbiz/agent/harness/agent/ToolControlView.java |