Files
SuperBizAgent-java/mvp/engineering/harness/Harness Tool 调用链-一次工具调用的完整旅程.md
T
zhuyongxin ff0752a16c docs(harness): annotate tool domain classes and add tool chain learning notes
- 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)
2026-08-04 18:37:17 +08:00

9.2 KiB
Raw Blame History

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