Files
SuperBizAgent-java/mvp/engineering/harness/Harness面试速查-一张图讲清设计.md
T

305 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Harness 面试速查:用一张图讲清设计
这篇文章是 Harness 系列的收尾,不增加新的组件和状态。它把现有设计压缩成一套可在面试中逐层展开的叙事:先用一句话定义,再用一张图说明边界,最后根据追问进入事实、停止、发布和失败设计。
如果只剩 10 分钟,阅读第 1、2、4 和 8 节即可。
## 1. 30 秒回答:什么是 Harness
> Harness 是包围非确定性 Agent 的确定性控制边界。Diagnosis Agent 负责提出假设、选择 Tool、解释观察并生成 Draft;Harness 负责一次 Run 的身份、deadline、预算、取消、Tool 权限和事实保管,发布前再验证引用真实性与结论支持度。它不保证 Agent 每次都找到根因,但保证执行过程有边界、失败能够收敛,并且只有可验证的内容能够发布。
这段回答包含三个重点:
```text
Agent 负责业务推理
Harness 负责确定性约束
Release 决定什么可以公开
```
不要一开始列 10 个职责域。面试官追问“具体怎么做”时,再沿下面的总图展开。
## 2. 一张图讲清完整设计
```mermaid
flowchart LR
U["用户问题"] --> APP["Chat Application<br/>创建 Run、路由、持久化、SSE"]
subgraph CONTROL["一、运行控制"]
CORE["Harness Core<br/>identity / deadline / budget<br/>cancel / lifecycle / retry"]
PROGRESS["Progress Control<br/>重复、信息增益、停止"]
end
subgraph REASONING["二、业务推理"]
AGENT["Diagnosis ReAct Agent<br/>假设、选 Tool、解释 Observation、写 Draft"]
end
subgraph TRUTH["三、事实边界"]
TB["ToolBoundary<br/>授权、只读、容量、状态迁移"]
CAN["Canonical Invocation<br/>当前 Run 的短期完整真相"]
OBS["Model Observation<br/>模型可见的有界投影"]
AUDIT["Metadata Audit<br/>长期可观测账本"]
end
subgraph PUBLICATION["四、验证发布"]
EG["EvidenceGuard<br/>引用是否真实"]
SG["SemanticGuard<br/>证据是否支持结论"]
REL["Release<br/>原报告或 SafeFallback"]
end
APP --> CORE
APP --> AGENT
CORE -.->|"RunContext 控制句柄"| AGENT
CORE -.->|"active / budget 门禁"| TB
AGENT --> PROGRESS
AGENT -->|"Tool Call"| TB
TB --> CAN
CAN --> OBS --> AGENT
TB -.-> AUDIT
AGENT -->|"DiagnosisDraft"| REL
REL --> EG
CAN --> EG --> SG --> REL
PROGRESS -->|"无结论或受控停止"| REL
REL --> APP --> U
```
这张图不表示 Harness 替 Agent 安排固定步骤。Agent 自己决定查什么、何时形成 Draft;Harness 只在每个边界回答:
- 这一步是否属于当前 active Run;
- 是否仍有时间、预算和调用权限;
- Tool 事实应该保存在哪里,模型可以看到多少;
- Agent 的引用能否从当前 Run 的真实调用中验出;
- 现有证据是否足以支持对用户公开的结论;
- 无法继续时,应该发布安全说明还是失败。
## 3. 一次请求怎样穿过 Harness
```mermaid
sequenceDiagram
participant U as Client
participant APP as Chat Application
participant CORE as Harness Core
participant A as Diagnosis Agent
participant I as Tool Interceptor
participant T as ToolBoundary
participant C as Canonical Store
participant R as Release Pipeline
U->>APP: 支付服务为什么超时?
APP->>CORE: startRun(sessionId)
CORE-->>APP: RunContext(runId, deadline, handles)
APP->>A: query + bounded PreviousTurn
loop 框架原生 ReAct
A->>I: Tool Call Envelope
I->>I: progress / duplicate / saturation
I->>T: business input + RunContext
T->>T: active / auth / readonly / budget / bytes
T->>C: PROJECTING -> READY or ERROR
T-->>I: canonical projected result
I-->>A: 有界 Model Observation
end
A-->>R: DiagnosisDraft
R->>C: 回读当前 Run 的 Tool 真相
R->>R: EvidenceGuard + SemanticGuard
alt 验证通过
R-->>APP: 原始安全报告 / SUCCESS
else 证据不足或门禁失败
R-->>APP: SafeFallback / FALLBACK
end
APP->>CORE: first terminal wins
APP-->>U: content or failure + done
```
讲这条链路时只需要抓住四个时间点:
1. Agent 运行前先建立 Run 边界。
2. Tool 调用经过 Harness,但 Tool 选择仍由 Agent 决定。
3. Agent 只看到有界观察,完整事实由系统独立保管。
4. Draft 必须经过唯一发布出口,不能直接发送给用户。
## 4. 三个最核心的设计决策
### 决策一:按决策权拆分,而不是按角色拆分
项目早期使用过 Planner、Executor、Verifier、Composer 和 StateGraph。它提高了职责可见性,但也把一次自然 ReAct 拆成多个模型调用、Prompt、Schema 和状态搬运。
最终选择是:
| 决策 | 所有者 |
|---|---|
| 提出假设、选择 Tool、解释证据、撰写 Draft | Diagnosis Agent |
| 身份、预算、取消、权限、容量、唯一终态 | Harness |
| 引用能否被代码机械证明 | EvidenceGuard |
| 真实证据是否支持用户可见结论 | 隔离的 SemanticGuard |
| 发布正常报告还是 SafeFallback | Release |
这里不是把多个 Agent 粗暴合并成一个“大 Agent”。业务推理合并了,安全权力反而被拆得更清楚:Agent 没有 canonical raw 读取权、不能验证自己的引用,也没有最终发布权。
放弃的方案:在外层继续编排多 Agent 或重新实现一套 ReAct loop。
获得的能力:唯一业务上下文、唯一 Draft 作者、控制规则可测试。
付出的代价:Harness 契约和门禁必须完整,不能再依赖角色之间“互相提醒”。
### 决策二:系统事实、模型观察和长期审计不能共用一份数据
同一份 Tool 结果要服务三个互相冲突的目标:
```mermaid
flowchart TB
RAW["Tool backend raw result"] --> TB["ToolBoundary + Projector"]
TB --> CAN["Canonical truth<br/>当前 Run、短 TTL、可验真"]
CAN --> OBS["Model Observation<br/>白名单、有界、服务推理"]
CAN --> GUARD["EvidenceGuard<br/>独立回读、验证引用"]
TB -.-> META["Metadata Audit<br/>身份、状态、耗时、bytes"]
```
如果 raw 直接给模型,上下文、敏感数据和 prompt injection 风险不可控;如果只保存裁剪后的 Observation,EvidenceGuard 无法独立证明 Agent 引用了真实结果;如果把完整 raw 永久写入审计库,又会制造敏感数据副本。
因此当前设计将数据责任拆开:
- Redis canonical invocation 保存当前 Run 的短期完整 Tool 真相;
- Model Observation 只包含 Agent 下一步推理所需字段;
- 长期 Audit 只保存 identity、状态、耗时、Token 和 bytes 等元数据;
- EvidenceGuard 从 canonical store 验证物理真实性;
- SemanticGuard 只在 verified evidence 上判断语义支持关系。
放弃的方案:一份 Tool JSON 在 Agent、Guard 和数据库之间直接流转。
获得的能力:模型不能靠自己看到的内容完成自证,长期审计也不必复制全部敏感正文。
付出的代价:每类 Tool 都需要 projector、canonical contract 和容量策略,Redis TTL 也成为验证可用性的边界。
### 决策三:把“如何结束”设计成一等能力
Agent 系统最常见的问题不只是错误,而是无法正常结束:空日志、通用知识和相似查询都可能让模型持续尝试,最后撞上预算。
当前 Harness 使用两套不同机制:
```text
Budget:还能不能继续消耗资源
Information Gain:继续查询是否推进诊断
```
```mermaid
flowchart TD
X["一次 Tool 结果或执行问题"] --> C{"Run 还能继续?"}
C -->|"可以"| G{"结果是否推进诊断?"}
G -->|"GAINED"| N["继续 ReAct"]
G -->|"连续 NO_GAIN"| S["SATURATED<br/>停止新增 Tool"]
C -->|"不可以"| P{"已有可验证进展?"}
S --> P
P -->|"有"| F["SafeFallback<br/>已检查内容、限制和下一步"]
P -->|"无"| E["FAILED<br/>不发布未验证内容"]
N --> D{"最终 Draft 可发布?"}
D -->|"是"| OK["SUCCESS"]
D -->|"否"| F
```
`READY + NO_EVIDENCE` 表示查询成功但当前 scope 为空,不是技术异常;`conclusion=null` 是合法 Draft,不是模型失败;`SATURATED` 只停止收集,不是 Run 终态;`FALLBACK` 表示请求被安全处理但没有正常报告,也不等于 `RunState.FAILED`。
放弃的方案:强制 Agent 必须给出根因,或者只依靠硬预算和全局异常结束。
获得的能力:证据不足可以成为诚实、可解释的产品结果,局部 Tool 故障也不必立即摧毁整个 Run。
付出的代价:RunState、Tool 状态、CollectionState 和 ReleaseOutcome 必须保持正交,排障不能只看一个 `status`。
## 5. 这套设计最难的地方是什么
面试中不要把难点回答成“接入了 Spring AI”或“写了很多 Interceptor”。真正困难的是确定责任边界,并让这些边界在异常和竞态下仍成立。
| 难点 | 核心问题 | 当前答案 |
|---|---|---|
| Run 归属 | 异步调用和多轮 Session 中,事实到底属于哪次执行 | 显式 `RunContext` 和 exact `runId` |
| 事实可信 | Agent 引用的 Tool 内容如何独立验真 | canonical invocation + EvidenceGuard |
| 结论可信 | 引用真实但推论牵强怎么办 | 隔离的 SemanticGuard |
| 正常收敛 | 没有证据时如何避免反复查询 | Information Gain + SATURATED + ProgressSnapshot |
| 失败一致 | 超时、预算、Tool ERROR 和断开如何对应结果 | first-terminal-wins + 唯一 Release Policy |
| 可观测性 | 如何回放决策又不永久保存敏感正文 | metadata audit + 短期 canonical truth |
如果面试官只允许选一个,回答“事实可信”最能代表这套设计:它要求系统同时解决 Tool 身份、Run 归属、数据视图、证据引用和最终发布,而不是只调一个模型接口。
## 6. 用真实案例讲 2 分钟
可以使用支付超时案例:
> 用户要求诊断支付服务超时。Application 先创建独立 Run,并为 Router、Agent、Tool 和 Guard 共享同一套 deadline、预算和取消能力。Diagnosis Agent 自主调用知识库和日志 Tool;ToolBoundary 执行调用并把完整事实保存为当前 Run 的 canonical invocation,只把有界 Observation 返回给 Agent。Agent 根据两个 Tool 结果生成 Draft,但 Draft 没有直接发给用户。EvidenceGuard 回读 canonical store 后发现引用无法完成真实性校验,因此 Release 没有继续让模型润色或猜测,而是发布 `EVIDENCE_VALIDATION_FAILED` SafeFallback。最终数据库记录请求处理成功、ReleaseOutcome 为 FALLBACK,SSE 也完整结束,但未经验证的根因没有离开系统。
这个案例的价值不在于“最终失败了”,而在于证明:
```text
Tool READY != 引用已验真
引用已验真 != 结论被支持
Agent 生成 Draft != 报告允许发布
```
完整数据和过程见[支付超时诊断案例](案例-从一次支付超时诊断看Harness如何控制Agent.md)。
## 7. 常见追问怎样展开
| 面试官追问 | 回答主线 | 深入阅读 |
|---|---|---|
| 为什么不继续使用多 Agent? | 多角色重复实现 ReAct;改为按决策权拆分 | [设计演进](Harness设计演进-从多Agent编排到确定性控制边界.md) |
| 为什么 Harness 不是工作流引擎? | Agent 选择下一步,Harness 只检查边界和发布资格 | [Harness 主设计](Harness设计-非确定性Agent的确定性控制边界.md) |
| 全部组件有哪些? | 先讲四组,再按需展开 10 个职责域 | [组件全景](Harness组件全景-职责-设计原因与边界.md) |
| Tool 结果为什么不直接给模型? | 真相、观察和长期审计有不同数据责任 | [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md) |
| 如何防止 Agent 编造证据? | framework Tool ID、canonical store、EvidenceGuard | [证据安全链](Harness证据安全链-从引用真实到结论可发布.md) |
| EvidenceGuard 已经通过,为什么还要 SemanticGuard? | 引用真实不等于结论被支持 | [证据安全链](Harness证据安全链-从引用真实到结论可发布.md) |
| Agent 为什么不会无限调用 Tool? | 预算止损,信息增益负责正常收敛 | [信息增益停止](Harness信息增益停止-让无证据诊断正常收敛.md) |
| Tool 报错是不是 Run 就失败? | 局部失败先看是否可继续及是否已有安全进展 | [失败图谱](Harness失败图谱-异常-停止-降级与终态.md) |
| FALLBACK 算成功还是失败? | RunState、ReleaseOutcome、数据库和 SSE 是不同视图 | [生命周期与状态](Harness生命周期与状态.md) |
| 如何验证不是纸面设计? | focused tests 证明不变量,live E2E 验证组合契约 | [支付超时诊断案例](案例-从一次支付超时诊断看Harness如何控制Agent.md) |
| 当前还有什么限制? | 语义去重、TTL、同步取消、SemanticGuard 不确定性、Reasoning 治理 | [Harness 主设计](Harness设计-非确定性Agent的确定性控制边界.md) |
## 8. 面试中最容易讲错的六件事
### 不要说:Harness 负责安排 Agent 的执行步骤
应说:ReAct Agent 自己选择 Tool 和下一步,Harness 负责运行边界、事实边界和发布边界。
### 不要说:SemanticGuard 是第二个诊断 Agent
应说:它是无 Tool、无记忆、单轮二值判断的隔离审查器,不能探索事实或改写报告。
### 不要说:Tool 返回 SUCCESS 就找到了证据
应说:`InvocationStatus=READY` 只表示调用完成,还要结合 `EvidenceStatus`;存在候选证据也不代表支持结论。
### 不要说:FALLBACK 就是 Run 失败
应说:Fallback 是安全发布结果。典型证据不足场景可以是 `RunState.SUCCESS + ReleaseOutcome.FALLBACK`。
### 不要说:Redis 是完整的长期审计库
应说:Redis canonical store 保存当前 Run 的短期完整 Tool 真相;长期审计只保存有界元数据。
### 不要说:取消能立刻杀死所有模型调用
应说:当前是协作式取消;first-terminal-wins、active check 和 SSE 状态保证迟到结果不能发布,但同步 Provider 计算未必立即停止。
## 9. 当前设计的代价和边界
一套可信的面试叙事不能只讲收益,还要主动说明代价:
1. 正交状态较多,必须用统一 Context 防止 `SUCCESS / READY / FALLBACK` 被混读。
2. canonical store、Projector 和 Guard 增加了实现复杂度与发布延迟。
3. Redis TTL 过期后只能保留元数据回放,不能恢复完整 Tool 正文。
4. 自然语言近义查询目前不能被确定性去重,只能依赖 Agent 的信息增益义务。
5. SemanticGuard 仍是模型判断,只是被收缩到最小、隔离、无 Tool 的范围。
6. 协作式取消保护逻辑终态和发布,不等于强制终止 Provider 计算。
7. 信息增益阈值和各类预算仍需依靠固定评测集持续校准。
主动说出这些边界,会让设计从“组件介绍”变成可讨论的工程决策。
## 10. 最后只记住四句话
```text
Agent 决定如何诊断,Harness 决定诊断必须遵守什么边界。
系统保管 Tool 真相,模型只读取完成下一步所需的有界观察。
引用真实与结论成立是两个问题,必须由不同门禁处理。
Harness 不保证每次找到答案,但保证任何结束方式都诚实、可审计、不会越权发布。
```
到这里,Harness 文档的主线已经闭合。需要回忆某个细节时,通过第 7 节进入专题即可,不需要重新从组件清单开始阅读。