305 lines
16 KiB
Markdown
305 lines
16 KiB
Markdown
# 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 节进入专题即可,不需要重新从组件清单开始阅读。
|