Files
SuperBizAgent-java/mvp/engineering/harness/Harness agent 域学习笔记-从框架 ReAct 接入到受控停止.md
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

9.5 KiB
Raw Permalink Blame History

Harness agent 域学习笔记:从框架 ReAct 接入到受控停止

更新日期:2026-08-04 主题:agent 域完整链路——装配(Factory)/ 双拦截器(Model/Tool)/ 循环外壳(UseCase)/ 受控停止 / 双视图投影 配套:tool 域代码学习笔记(工具链)、progress 代码学习笔记(Tool 拦截器五道门)

1. 定位:框架 ReAct 接入层(粘合点)

框架(spring-ai-alibaba ReactAgent):负责 ReAct 多轮(模型 ↔ tool_call)
Harness:不复制 loop,只通过 Interceptor 卡住【每次消耗】
  → Model Interceptor:每次模型调用(预算/审计/Token)
  → Tool Interceptor:每次工具调用(五道门)

原则:拦截器是挂点,不是 loop 实现——Harness 不需要知道框架内部怎么循环

2. 装配图(DiagnosisAgentFactory——粘合点)

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

关键装配决策:

.parallelToolExecution(false)    ← 串行工具:预算与 step 绑定可解释
.returnReasoningContents(true)   ← 推理内容返回
.releaseThread(true)
每次 run 新建 Agent(create(context))——拦截器持有 RunContext,不可跨 run 复用

3. HarnessModelInterceptor(模型拦截器)

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 执行流程

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)——从异常栈捞回可控信号转正常返回值

① 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 的分工

controlledExecution:loop 被预算/收敛打断(往往还没有合法 draft)→ stopped
recoverInvalidDraft:loop 跑完了,但输出不是合法 DiagnosisDraft → 恢复/重试

5.4 输出解析(严格)

draftReader = FAIL_ON_UNKNOWN_PROPERTIES + FAIL_ON_TRAILING_TOKENS(严格模式)
JsonParseException → INVALID_JSON / SchemaInvalid → SCHEMA_INVALID(分类错误码)
空输出 → EMPTY_DRAFT(分类错误码)

6. 双视图投影(ToolResultViewProjector)

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/