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)
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# 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/` |
|
||||
Reference in New Issue
Block a user