# 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
evidenceTools.callbacks()"] L["ReactAgent 循环"] end subgraph Harness 控制面 I1["HarnessModelInterceptor
预算+Token 审计"] I2["HarnessToolInterceptor
五道门+投影"] H["Hooks
agent_step 落库"] O["outputSchema
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/` |