Files
SuperBizAgent-java/mvp/engineering/harness/Harness agent 域学习笔记-从框架 ReAct 接入到受控停止.md
T
zhuyongxin da18fdf4e1 docs(harness): add agent domain learning note and mark all nine domains complete
- Add agent note: factory assembly, dual interceptors (model budget/token audit,
  tool five gates), use case loop shell, controlled stop recovery, dual-view projection
- Mark agent domain complete in roadmap (all nine domains done)
2026-08-10 10:12:04 +08:00

187 lines
9.5 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 agent 域学习笔记:从框架 ReAct 接入到受控停止
**更新日期**:2026-08-04
**主题**:agent 域完整链路——装配(Factory)/ 双拦截器(Model/Tool)/ 循环外壳(UseCase)/ 受控停止 / 双视图投影
**配套**:[tool 域代码学习笔记](Harness%20tool%20域代码学习笔记-工具的注册调用与执行链路.md)(工具链)、[progress 代码学习笔记](Harness%20progress%20代码学习笔记-从拦截器五道门到唯一发布点.md)(Tool 拦截器五道门)
## 1. 定位:框架 ReAct 接入层(粘合点)
```text
框架(spring-ai-alibaba ReactAgent):负责 ReAct 多轮(模型 ↔ tool_call)
Harness:不复制 loop,只通过 Interceptor 卡住【每次消耗】
→ Model Interceptor:每次模型调用(预算/审计/Token)
→ Tool Interceptor:每次工具调用(五道门)
原则:拦截器是挂点,不是 loop 实现——Harness 不需要知道框架内部怎么循环
```
## 2. 装配图(DiagnosisAgentFactory——粘合点)
```mermaid
flowchart LR
subgraph 框架能力
M["ChatModel"]
T["tools<br/>evidenceTools.callbacks()"]
L["ReactAgent 循环"]
end
subgraph Harness 控制面
I1["HarnessModelInterceptor<br/>预算+Token 审计"]
I2["HarnessToolInterceptor<br/>五道门+投影"]
H["Hooks<br/>agent_step 落库"]
O["outputSchema<br/>DiagnosisDraft(conclusion 可 null)"]
end
M --> L
T --> L
L --> I1
L --> I2
L --> H
L --> O
```
**关键装配决策**:
```text
.parallelToolExecution(false) ← 串行工具:预算与 step 绑定可解释
.returnReasoningContents(true) ← 推理内容返回
.releaseThread(true)
每次 run 新建 Agent(create(context))——拦截器持有 RunContext,不可跨 run 复用
```
## 3. HarnessModelInterceptor(模型拦截器)
```text
interceptModel(request, handler):
core.beforeModelCall(context) ← 预算门(模型调用前扣预算)
call = auditor.begin(...) ← 审计开始(Token 记账)
response = handler.call(request) ← 框架实际调用
recordUsage(call, response) ← 记 prompt/completion tokens
core.checkActive(context) ← 终态检查(预算耗尽在此打断)
return response
异常:记 0 token + 上抛(不吞)
```
**三个动作**:预算(beforeModelCall)→ 记账(auditor.begin/recordUsage)→ 终态(checkActive)——每次模型调用都被 Harness 卡住一次。Usage 字段 null/负数安全兜底(nonNegative)。
## 4. HarnessToolInterceptor(工具拦截器,已深学)
五道门(progress 会话已沉淀):证据工具必炸 handler → 拦截器唯一执行路径 → 边界投影 → 审计落库 → 返回。本会话只补装配视角:`interceptors` 列表里第二个,构造时注入 context + evidenceTools + objectMapper + traceRecorder。
## 5. DiagnosisAgentUseCase(循环外壳)
### 5.1 执行流程
```text
execute(context, input):
checkActive → 输入限制(query/previous_turn/input 字节)
reserveRunBytes(input) ← 输入也占 Run 预算
RunnableConfig.metadata 挂 RunContext ← 显式传递(避免隐式 ThreadLocal)
agent = factory.create(context) ← 每次 run 新建
response = agent.call(inputJson, config) ← ★ 框架跑完整个 ReAct 循环
output → 字节限制 → reserveRunBytes(draft) → parse DiagnosisDraft
→ completed(draft) 或 受控停止
```
### 5.2 受控停止(controlledExecution)——从异常栈捞回可控信号转正常返回值
```text
① DiagnosisCollectionStoppedException(信息饱和后仍强 tool)
→ stopped(stopReason) ← 收集该停(draft=null)
② RunAbortedException + BUDGET_EXHAUSTED
→ markBudgetLimitReached + stopped(BUDGET_LIMIT_REACHED)
③ 其他 RunAborted(取消/超时/内部失败终态)→ 原样再抛
← 留给 Application 写 CANCELLED/FAILED(不降级为正常停止)
④ BudgetExceededException 或 lifecycle 已 BUDGET_EXHAUSTED → 预算 stopped
⑤ 都识别不了 → 包装 DiagnosisAgentOutputException(Agent 执行失败)
```
**关键**:不是笼统「业务异常 → 正常」——**只识别 Harness 约定的可控信号**(沿 cause 链找,因框架可能再包一层);取消/超时必须上抛(诚实终态)。
### 5.3 与 recoverInvalidDraft 的分工
```text
controlledExecution:loop 被预算/收敛打断(往往还没有合法 draft)→ stopped
recoverInvalidDraft:loop 跑完了,但输出不是合法 DiagnosisDraft → 恢复/重试
```
### 5.4 输出解析(严格)
```text
draftReader = FAIL_ON_UNKNOWN_PROPERTIES + FAIL_ON_TRAILING_TOKENS(严格模式)
JsonParseException → INVALID_JSON / SchemaInvalid → SCHEMA_INVALID(分类错误码)
空输出 → EMPTY_DRAFT(分类错误码)
```
## 6. 双视图投影(ToolResultViewProjector)
```text
modelObservation() → 模型观察:只含该工具的内容字段
lookup_knowledge → scope.query + evidence + relevance_level
query_logs → source_kind + scope + patterns + events
query_mysql → scope + columns + rows
+ stop_required/reason(需要停止时附加)
controlView() → 控制视图:evidence_status / relevance_level / returned_count / truncated
(Harness 控制面读,模型看不到)
分工:模型看到「内容」,Harness 看到「控制信息」(判级/截断/进度消费)
```
## 7. 定义层小件
| 类 | 作用 |
|---|---|
| `DiagnosisAgentInput` | query + previous_turn(query 必填) |
| `DiagnosisAgentLimits` | maxQuery/PreviousTurn/Input/DraftBytes 四类字节上限 |
| `DiagnosisAgentPrompt` | classpath 加载系统提示词(prompts/diagnosis-agent-prompt.md) |
| `DiagnosisAgentExecution` | completed(draft, progress) / stopped(progress, stopReason) 双形态 |
| `DiagnosisDraftOutputSchema` | BeanOutputConverter postProcess:conclusion 允许 object/null(无结论合法) |
| `EvidenceToolInvoker` | 函数式:RunContext + toolCallId + arguments → ToolBoundaryResult |
| `ParsedAgentToolCall` | 解析出的工具调用(previousObservation + businessInput + arguments) |
## 8. 关键设计点(面试)
| 设计 | 为什么 |
|---|---|
| **不复制 loop** | 框架 ReAct 是标准能力;Harness 用拦截器挂在每次消耗点,不需要知道框架内部怎么循环 |
| 每次 run 新建 Agent | 拦截器持有 RunContext——Agent 与 run 绑定,防跨 run 串状态 |
| RunContext 显式传(metadata) | 避免隐式 ThreadLocal(框架线程池/异步下 ThreadLocal 不可靠) |
| 串行工具(parallel=false) | 预算与 step 绑定可解释(并行会让「哪一步花多少钱」不可审计) |
| 受控停止只认约定信号 | 取消/超时绝不降级为正常停止(诚实终态) |
| conclusion 允许 null | 无结论也是合法 Draft(FALLBACK 路径) |
| 双视图 | 模型观察 vs 控制视图分离——控制信息(判级/截断)不进模型上下文 |
| 输出严格解析 | FAIL_ON_UNKNOWN/TRAILING——防止模型输出混入意外字段 |
## 9. 易错点
| 易错 | 正确 |
|---|---|
| Harness 自己实现 Agent loop | 框架跑 loop,拦截器挂消耗点(不复制 loop) |
| 任何异常都转 stopped | 只认约定信号(CollectionStopped/预算);取消/超时原样上抛 |
| Agent 复用 | 每次 run 新建(拦截器绑定 RunContext) |
| ThreadLocal 传 context | RunnableConfig metadata 显式传 |
| 并行工具省时间 | 串行(预算与 step 绑定可解释) |
| 模型看到控制信息 | 双视图:模型看内容,Harness 看控制 |
| 输出宽容解析 | FAIL_ON_UNKNOWN + FAIL_ON_TRAILING(严格模式) |
## 10. 面试话术(30 秒)
> "agent 域是框架 ReAct 的接入层:不复制 loop——spring-ai-alibaba 的 ReactAgent 负责多轮循环,Harness 通过两个拦截器卡住每次消耗:Model Interceptor(每次模型调用前 checkActive + 预算,调用后记 Token 审计)、Tool Interceptor(工具调用五道门)。装配在 DiagnosisAgentFactory,每次 run 新建 Agent(拦截器绑定 RunContext,RunnableConfig metadata 显式传递避免 ThreadLocal)。循环外壳 DiagnosisAgentUseCase 做输入/输出字节限制 + Run 预算预留,并实现受控停止——只把 Harness 约定的可控信号(信息饱和、预算耗尽)从异常栈捞回转成 stopped,取消/超时原样上抛留给 Application 写 CANCELLED/FAILED。串行工具保证预算与 step 绑定可解释。"
## 11. 代码位置索引
| 类 | 文件 |
|---|---|
| `DiagnosisAgentFactory` | `src/main/java/com/superbiz/agent/harness/agent/DiagnosisAgentFactory.java` |
| `HarnessModelInterceptor` | `src/main/java/com/superbiz/agent/harness/agent/HarnessModelInterceptor.java` |
| `HarnessToolInterceptor` | `src/main/java/com/superbiz/agent/harness/agent/HarnessToolInterceptor.java` |
| `DiagnosisAgentUseCase` | `src/main/java/com/superbiz/agent/harness/agent/DiagnosisAgentUseCase.java` |
| `ToolResultViewProjector` | `src/main/java/com/superbiz/agent/harness/agent/ToolResultViewProjector.java` |
| `HarnessEvidenceTools` | `src/main/java/com/superbiz/agent/harness/agent/HarnessEvidenceTools.java` |
| `DiagnosisAgentExecution` | `src/main/java/com/superbiz/agent/harness/agent/DiagnosisAgentExecution.java` |
| `DiagnosisAgentOutputException` | `src/main/java/com/superbiz/agent/harness/agent/DiagnosisAgentOutputException.java` |
| 契约(DiagnosisDraft/PreviousTurn) | `src/main/java/com/superbiz/agent/harness/contract/` |