375 lines
31 KiB
Markdown
375 lines
31 KiB
Markdown
# Harness 组件全景:职责、设计原因与边界
|
||
|
||
**更新日期**:2026-07-29
|
||
**适用代码**:`src/main/java/com/superbiz/agent/harness`
|
||
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
|
||
**配套主文**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md)
|
||
|
||
> 本文是完整组件参考手册,不建议第一次接触 Harness 时顺序阅读。入门请先读 [Harness 阅读入口](README.md),需要逐组理解组件时使用[组件渐进式导读](components/README.md)。
|
||
|
||
## 1. 这份文档怎样定义“全部组件”
|
||
|
||
当前 `harness` 目录包含 10 个一级职责域、189 个 Java 源文件。它们并不都是独立运行的“服务”:
|
||
|
||
- **执行组件**拥有行为,例如 Core、Interceptor、Boundary、Guard、Release、Executor;
|
||
- **端口与适配器**隔离框架、Redis、JPA、JDBC 和业务 Tool;
|
||
- **状态与契约类型**固定跨组件语言,防止字符串协议漂移;
|
||
- **Limits、Prompt 和异常类型**把边界配置与失败语义显式化。
|
||
|
||
因此本篇先解释 10 个职责域为什么存在,再列出每个生产类型。判断某个类应该放在哪里时,只问三个问题:它拥有什么状态、它能作出什么决定、它绝不能决定什么。
|
||
|
||
## 2. 组件地图
|
||
|
||
| 职责域 | 文件数 | 解决的问题 | 核心组件 |
|
||
|---|---:|---|---|
|
||
| `application` | 34 | 谁创建 Run、路由请求、持久化和映射公开结果 | `ChatApplicationUseCase`、三个 Executor、`ChatRunStore` |
|
||
| `core` | 14 | deadline、取消、预算和唯一终态由谁拥有 | `DiagnosisHarnessCore`、`RunContext`、`RunBudget`、`RunLifecycle` |
|
||
| `agent` | 17 | 如何把框架 ReAct 接入 Harness,而不复制 ReAct | `DiagnosisAgentUseCase`、Factory、Model/Tool Interceptor |
|
||
| `progress` | 14 | 如何识别无增益、重复和协议空转 | `DiagnosisProgressTracker`、Projector、scope normalizer |
|
||
| `tool` | 49 | Tool 如何安全执行、保存真相并只暴露必要内容 | `ToolBoundary`、Adapters、Projectors、Canonical Store、MySQL sandbox |
|
||
| `guard` | 15 | 如何分开验证引用真实性和结论支持度 | `EvidenceGuard`、`SemanticGuard`、`GuardModelCall` |
|
||
| `release` | 6 | 谁拥有最终 SUCCESS / FALLBACK 决策 | `DiagnosisReleaseUseCase`、`EvidenceRepair`、`SafeFallbackFactory` |
|
||
| `retry` | 8 | 哪些失败允许重试、attempt 如何可见 | `HarnessRetryExecutor`、Policies、Failure taxonomy |
|
||
| `audit` | 17 | 如何重放决策而不复制敏感正文 | Trace、Tool audit、Model ledger、Agent hook |
|
||
| `contract` | 15 | 跨层公开语言如何保持类型化 | Draft、PublishedResult、Fallback、状态枚举 |
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
APP["application<br/>Run 与公开用例"] --> CORE["core<br/>执行不变量"]
|
||
APP --> AGENT["agent<br/>ReAct 接入"]
|
||
AGENT --> PROGRESS["progress<br/>收敛控制"]
|
||
AGENT --> TOOL["tool<br/>证据边界"]
|
||
APP --> RELEASE["release<br/>唯一发布"]
|
||
RELEASE --> GUARD["guard<br/>真实性与支持度"]
|
||
CORE --> RETRY["retry<br/>显式 attempt"]
|
||
CORE -.-> AUDIT["audit<br/>可观测账本"]
|
||
AGENT -.-> AUDIT
|
||
TOOL -.-> AUDIT
|
||
RELEASE -.-> AUDIT
|
||
CONTRACT["contract<br/>类型化语言"] -.-> APP
|
||
CONTRACT -.-> AGENT
|
||
CONTRACT -.-> TOOL
|
||
CONTRACT -.-> GUARD
|
||
CONTRACT -.-> RELEASE
|
||
```
|
||
|
||
## 3. Application:Run 的应用所有者
|
||
|
||
### 为什么需要
|
||
|
||
Core 只知道一次 Run 是否活跃,并不知道 HTTP、SSE、意图路由、数据库持久化和上一轮上下文。若这些职责塞进 Core,Harness 会变成业务工作流引擎;若散落在 Controller,则每个入口都可能产生不同的终态和 Fallback。
|
||
|
||
### 核心组件
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `ChatApplicationUseCase` | 为一次请求建立唯一应用事务边界 | 解析 session、读取历史、创建 Run、路由、执行分支、落终态和安全输出 | 不做诊断推理,不自行构造诊断 Fallback |
|
||
| `IntentRouter` | 路由也会消耗模型、超时并返回非法 JSON | 在有界输入、timeout 和显式 retry 下输出唯一 `IntentType` | 不执行业务 Tool,不生成最终回答 |
|
||
| `SystemChatExecutor` | 系统问答不需要 ReAct,但仍必须受模型预算控制 | 单轮回答产品能力和闲聊 | 不声称查询了实时数据 |
|
||
| `KnowledgeQueryExecutor` | 知识问答需要一次 RAG 和一次受控生成,但不需要完整诊断链 | 调用知识 Tool、校验 Tool result、生成带来源答案 | 首版不进入 SemanticGuard,不执行多 Tool 诊断 |
|
||
| `DiagnosisChatExecutor` | Agent 执行和安全发布需要一个明确接合点 | 执行 Agent、处理合法停止/非法 Draft、调用 Release、映射公开内容 | 不重复 Guard 或 Release 决策 |
|
||
| `ChatRunStore` / `JpaChatRunStore` | 内存 Run 状态与长期数据库状态职责不同 | 保存 Run 开始、intent、结果、预算摘要;读取安全上一轮 | 不保存 canonical raw;只允许安全发布结果进入 PreviousTurn |
|
||
| `PublishedResultPolicy` | 直接复用上一轮完整结果会让上下文无限增长并传播失败内容 | 生成可持久化 PublishedResult 和有界 PreviousTurn | Fallback、失败、raw evidence 不进入下一轮 |
|
||
|
||
### 类型清单
|
||
|
||
| 类型 | 分类与功能 |
|
||
|---|---|
|
||
| `ChatApplicationRequest`、`ChatApplicationResult` | 应用入口和出口 DTO;固定 session、run、intent、outcome 和内容类型 |
|
||
| `ChatApplicationContent`、`ChatContentType` | 公开内容的 sealed/typed 边界,避免任意对象直接发给 SSE |
|
||
| `DiagnosisContent`、`KnowledgeContent`、`SystemChatContent`、`FallbackContent` | 四种公开内容载体;分别包装安全诊断、知识答案、系统回答和 Fallback |
|
||
| `ChatApplicationStatus`、`ChatApplicationObserver` | 向 SSE 报告有界阶段,不泄露模型内部步骤 |
|
||
| `ChatRunControl` | 只向入口暴露 exact session/run 和客户端断开取消能力 |
|
||
| `ChatApplicationException`、`ChatFailureCode` | 把内部异常映射为稳定、可公开的失败语义 |
|
||
| `IntentRouting`、`SystemChatOperation`、`KnowledgeQueryOperation`、`DiagnosisOperation` | 四个应用端口;使主用例不依赖具体模型或执行器 |
|
||
| `DiagnosisExecutionResult` | Diagnosis 分支的内部返回,携带 outcome、content、published result 和预算已处理标记 |
|
||
| `IntentRouterInput`、`IntentRouterLimits`、`IntentRouterPrompt`、`IntentRoutingException` | 路由输入、边界、Prompt 和失败类型 |
|
||
| `KnowledgeQueryLimits`、`SingleTurnExecutorLimits` | 知识与单轮模型路径的输入、输出、timeout 上限 |
|
||
| `ChatRunStore`、`RoutingHistory` | 持久化端口及最小路由历史 |
|
||
| `PreviousTurnLimits`、`PublishedResultPolicy` | 安全历史的字段/大小限制与投影策略 |
|
||
|
||
## 4. Core:每个 Run 的执行不变量
|
||
|
||
### 为什么需要
|
||
|
||
模型、Tool、Guard 和 Application 都要检查取消、deadline 和预算。如果每层各自维护计数或终态,会出现多个真相源;如果依赖 ThreadLocal,则异步线程无法可靠继承。
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `DiagnosisHarnessCore` | 所有执行边界需要同一套 active / budget / terminal 规则 | 创建 RunContext,执行模型/Tool/Token/bytes 门禁,处理取消和终态 | 不持久化,不调用 Agent/Tool,不维护全局 Run Map |
|
||
| `RunContext` | Run 身份和状态句柄必须一起显式传播 | 固定 sessionId、runId、deadline 及 per-run handles | record 结构不可变,不代表内部计数不变化 |
|
||
| `RunLifecycle` | 成功、失败、取消可能竞态到达 | 原子 `compareAndSet` 实现 first-terminal-wins | 不映射公开 ReleaseOutcome |
|
||
| `RunCancellation` | 取消原因和回调只能被第一个请求确定 | first-reason-wins,并通知 lifecycle/资源回调 | 不承诺强杀同步 Provider 请求 |
|
||
| `RunBudget` | 多维预算必须原子地先检查再计数 | 模型、Tool、单 Tool、输入/输出/总 Token 和 bytes 计量 | 不判断信息是否有价值 |
|
||
| `RunCapacityCounter` | bytes 可能由并发边界累计 | CAS 方式维护 Run 总容量 | 不负责字段级截断策略 |
|
||
|
||
### 类型清单
|
||
|
||
| 类型 | 分类与功能 |
|
||
|---|---|
|
||
| `RunBudgetLimits`、`RunBudgetUsage` | 预算配置和值快照;区分限制与已使用量 |
|
||
| `BudgetKind`、`BudgetExceededException` | 明确指出耗尽的是模型、Tool、Token 还是 bytes |
|
||
| `RunState`、`RunTermination` | 内存执行状态与不可变终止快照 |
|
||
| `RunCancellationReason` | 客户端断开、用户请求、deadline、预算和内部失败的取消分类 |
|
||
| `RunAbortedException` | 将已经确定的 RunTermination 穿过深层调用栈,不丢失终态 |
|
||
|
||
## 5. Agent:框架 ReAct 与 Harness 的接合层
|
||
|
||
### 为什么需要
|
||
|
||
业务需要框架原生 Tool Calling 和 ReAct loop,但框架默认并不知道项目的 RunContext、预算、审计、Tool 双视图和停止协议。接合层的目标是“拦截边界”,不是重新实现 Agent 循环。
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `DiagnosisAgentFactory` | 每个 Run 的 interceptor 和 metadata 不同 | 为当前 Run 创建 ReactAgent,注册 Tool callback、Prompt、Hook 和 interceptor | 不缓存跨 Run Agent 状态 |
|
||
| `DiagnosisAgentUseCase` | 框架输入输出是字符串,业务要求严格 Draft 和 bytes 边界 | 序列化输入、显式注入 Run metadata、调用 Agent、严格解析 Draft、映射受控停止 | 不执行证据和语义校验 |
|
||
| `HarnessModelInterceptor` | 每一轮 ReAct 模型调用都必须进入预算和 Token 账本 | 调用前 reserve,调用后记录 Provider usage 并再次检查 active | 不重试模型 |
|
||
| `HarnessToolInterceptor` | 模型 Tool Call 中混有 Harness 进展协议和业务参数 | 校验 Envelope、上一轮增益、重复/饱和、调用 Tool、投影 observation、交付 STOP_REQUIRED | 不执行 backend,不复制 ToolBoundary 预算 |
|
||
| `HarnessEvidenceTools` | Tool schema 必须由服务端原生注册,且业务 Tool 可选启用 | 注册 RAG/log/MySQL callback,严格解析通用 Envelope,桥接 Adapter | Prompt 不写死 Tool schema;未配置 MySQL 时不暴露死 Tool |
|
||
| `ToolResultViewProjector` | canonical agent_result 仍含 Harness 控制字段 | 生成 Control View 和白名单 Model Observation | 不读取 raw response,不判断根因 |
|
||
|
||
### 类型清单
|
||
|
||
| 类型 | 分类与功能 |
|
||
|---|---|
|
||
| `DiagnosisAgentInput`、`DiagnosisAgentExecution` | Agent 用例输入,以及 Draft/ProgressSnapshot/stop reason 的执行结果 |
|
||
| `DiagnosisAgentLimits` | query、previous turn、总输入和 Draft 的 UTF-8 bytes 上限 |
|
||
| `DiagnosisAgentPrompt`、`DiagnosisDraftOutputSchema` | 最小职责 Prompt 与严格结构化输出 Schema |
|
||
| `EvidenceToolInvoker` | Agent 层到具体 Adapter 的函数端口 |
|
||
| `ParsedAgentToolCall` | 解包后的 previous observation、typed business input 和 JSON 参数 |
|
||
| `ToolControlView` | Harness 消费的 evidence status、count、scope 等控制视图 |
|
||
| `DiagnosisAgentLimitException` | Agent 输入或输出越界 |
|
||
| `DiagnosisAgentOutputException` | 空、非法 JSON、Schema 不合格 Draft,并可携带安全 ProgressSnapshot |
|
||
| `DiagnosisCollectionStoppedException` | STOP_REQUIRED 后仍请求 Tool 时,把受控停止穿出框架 loop |
|
||
|
||
## 6. Progress:从资源上限到正常收敛
|
||
|
||
### 为什么需要
|
||
|
||
预算只能阻止无限消耗,不能识别“连续查询没有推进诊断”。Progress 子系统只保存 Run 内最小控制状态,不复制完整证据。
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `DiagnosisProgressTracker` | 连续无增益、待评价调用和协议错误需要线程安全单一所有者 | 记录 completed scope、pending evaluation、NO_GAIN、协议错误、饱和和一次停止指令 | 不保存 raw/agent_result,不判断非空内容的业务价值 |
|
||
| `ToolScopeNormalizer` | 字段顺序或无关格式不应绕过重复检测 | 将各 Tool typed input 规范化为稳定 scope | 首版不做自然语言语义去重 |
|
||
| `DiagnosisProgressProjector` | 受控停止或非法 Draft 后仍需安全说明已检查内容 | 按 Tracker identity 回读 canonical READY 记录,生成有界事实、来源和限制 | 无法验真的记录直接排除,不输出 raw |
|
||
| `DiagnosisProgressProjection` | Agent 用例不应依赖具体 Redis projector | 定义 Run 到安全快照的端口,并提供 empty 实现 | 不决定 Fallback 类型 |
|
||
|
||
### 类型清单
|
||
|
||
| 类型 | 分类与功能 |
|
||
|---|---|
|
||
| `InformationGain` | 仅 `GAINED / NO_GAIN`,避免引入含混质量等级 |
|
||
| `DiagnosisCollectionState` | `COLLECTING / SATURATED`,只描述信息收集状态 |
|
||
| `DiagnosisStopReason` | 区分信息饱和、预算限制和进展协议错误 |
|
||
| `PreviousObservation` | 模型在下一次 Tool Call 回传上一轮 `tool_call_id + information_gain` |
|
||
| `CompletedToolCall`、`ToolScopeIdentity` | 保存已完成调用的 identity 和规范化 scope,不保存 payload |
|
||
| `DiagnosisProgressSnapshotState` | Tracker 的内部控制快照,含计数、pending ID 和停止指令状态 |
|
||
| `DiagnosisProgressSnapshot` | Release 可消费的安全快照,含 verified sources、observed facts 和 limitations |
|
||
| `ProgressProtocolViolationType`、`ProgressProtocolViolationException` | 缺字段、乱序评价、意外评价和非法 Envelope 的稳定分类 |
|
||
|
||
## 7. Tool:执行、真相、投影与后端安全
|
||
|
||
Tool 是文件最多的职责域,但可以按四层理解。
|
||
|
||
### 7.1 Boundary:所有 Tool 共用的确定性入口
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `ToolBoundary` | 每个 Adapter 自己实现授权、预算、store 和 audit 会产生漂移 | 统一 preflight、active、read-only、Tool budget、bytes、canonical 状态迁移和 audit | 不理解 Tool 业务内容;不做信息增益判断 |
|
||
| `ToolCallRequestEnvelope` | 调用必须同时证明 Run、ID、Tool、参数、授权和只读意图 | Boundary 的内部调用信封 | 不等同于模型侧 progress Envelope |
|
||
| `ToolExecutor` | Boundary 不依赖具体 backend | raw 执行函数端口 | 不投影结果 |
|
||
| `ToolResultProjector` | raw 到 canonical agent_result 的逻辑因 Tool 而异 | 标准化、限制、计算客观 evidence status | 不判断结论支持度 |
|
||
| `ToolBoundaryResult` | 只允许 READY 或 ERROR 离开 Boundary | 向上返回 ID、状态、agent result、evidence status 或稳定错误码 | PROJECTING 不对外暴露 |
|
||
| `ProjectedToolResult` | projector 同时返回有界 agent result 与客观状态 | Boundary/store 的中间值 | 不是最终 Model Observation |
|
||
| `ToolBoundaryErrorCode` | 不能把内部异常正文交给 Agent | 固定非法 ID、Run mismatch、未授权、非只读、超限、执行/投影/store 等错误 | 不包含敏感原因 |
|
||
|
||
### 7.2 Canonical Store:TTL 内的完整 Tool 真相
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `CanonicalInvocationStore` | Guard 需要独立于 Agent 上下文读取原始调用真相 | 定义 begin/find/markReady/markError 状态端口 | 不负责长期审计 |
|
||
| `RedisCanonicalInvocationStore` | 完整 request/raw 有敏感性和容量,适合短期 TTL 存储 | 原子创建,保持剩余 TTL 的状态更新,读取不续期 | 只有该 Adapter 访问 Redis |
|
||
| `CanonicalToolInvocation` | request、raw、agent_result 和两个状态必须形成合法组合 | 封装 PROJECTING -> READY/ERROR 转换及 Run 可引用判断 | ERROR/PROJECTING 不可被 EvidenceGuard 引用 |
|
||
| `CanonicalInvocationLimits` | Redis record、raw 候选和 agent result 需要独立硬限制 | 统一 UTF-8 bytes 校验 | 不执行截断 |
|
||
| `ToolCallKeyFactory` | Key 要隔离 Run 且保留框架 ID | 校验安全 segment,生成 prefix/runId/toolCallId | 不生成或改写 tool_call_id |
|
||
| `DuplicateInvocationException`、`InvocationStateException`、`CanonicalStoreException`、`ResultTooLargeException` | Store 失败需要可分类而不是字符串猜测 | 区分重复、非法迁移、基础设施错误和容量错误 | 最终对 Agent 仍映射为安全错误码 |
|
||
|
||
### 7.3 Adapter 与 Projector:隔离业务后端
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `RagToolAdapter` | 检索实现会演进,但 Agent schema 和 Boundary 不应随之变化 | 解析 RAG request,经 Boundary 调 backend 和 `RagResultProjector` | 不把检索轨迹直接给模型 |
|
||
| `QueryLogsToolAdapter` | 现有日志 Tool 的返回和时间语义需要规范化 | 校验范围、桥接 backend、投影日志事件 | 0 命中是 `NO_EVIDENCE`,不是技术失败 |
|
||
| `MysqlToolAdapter` | LLM 生成 SQL 必须经过授权和只读沙箱 | 解析 request、先校验 SQL,再经 Boundary 执行和投影 | 未配置数据源时 Tool 不注册 |
|
||
| `RagResultProjector` | 上游 evidence、score、relevance 表达不稳定 | 输出有界证据,并保留粗粒度 `relevance_level` | 非空/REFERENCE 不等于支持根因 |
|
||
| `QueryLogsResultProjector` | raw 日志不可直接进入上下文 | 生成有界 events、pattern、scope 和截断标记 | 不泄露无限日志正文 |
|
||
| `MysqlResultProjector` | JDBC rows 和元数据需要稳定 Agent contract | 限制行列、单元格和 bytes,输出 rows/columns/scope | 不执行 SQL 安全判断 |
|
||
| `ToolProjectionLimits`、`MysqlToolLimits` | 各投影边界必须集中、可测试 | 配置证据数、文本、行列和结果上限 | 不改变 Run 总 bytes 预算 |
|
||
|
||
### 7.4 MySQL 只读沙箱
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `MysqlSqlValidator` | `readOnly=true` 声明不能证明 SQL 安全 | 解析并限制单条 SELECT、数据源、schema/table/column、limit 等 | 不执行查询 |
|
||
| `MysqlDataSourceDefinition` | 连接存在不代表 Agent 可访问所有表列 | 保存逻辑数据源和 allowlist | 不携带密码 |
|
||
| `MysqlQueryPlan` | 校验后的内容不应在 Executor 再解析原始请求 | 固定已批准数据源、SQL 和限制 | 只能由 Validator 产生 |
|
||
| `MysqlReadOnlyExecutor` / `JdbcMysqlReadOnlyExecutor` | JDBC 细节与 Harness Boundary 解耦 | 在只读连接、timeout 和 row limit 下执行 plan | 不接收未经验证的 request |
|
||
| `MysqlRawResult` | JDBC 原始但结构化的执行结果 | 携带 columns、rows、truncated、duration | 仍需 Projector 后才能进入 canonical agent_result |
|
||
| `MysqlSecurityException` | 安全拒绝必须与基础设施错误区分 | 表达非法 SQL、未授权对象等 | 不向 Agent泄露详细策略 |
|
||
|
||
### 7.5 Tool contract 完整清单
|
||
|
||
| 类型 | 设计原因与功能 |
|
||
|---|---|
|
||
| `AgentToolContracts` | Tool 名、描述和服务端注册契约的唯一常量源,防止 Prompt/代码漂移 |
|
||
| `RagToolCall`、`QueryLogsToolCall`、`MysqlToolCall` | 模型侧统一 Envelope:`previous_observation + input` |
|
||
| `RagToolRequest`、`QueryLogsRequest`、`MysqlToolRequest` | 业务 Tool 的 typed input;Interceptor 解包后仍保持原业务协议 |
|
||
| `RagToolResult`、`QueryLogsToolResult`、`MysqlToolResult` | canonical agent-facing 标准结果,不等同于 raw backend response |
|
||
| `RagEvidence`、`SourceDocument` | 有界知识证据及文档身份 |
|
||
| `RagRelevanceLevel` | `PRECISE / HIGHLY_RELEVANT / REFERENCE` 等客观检索相关度,不是诊断置信度 |
|
||
| `LogQueryScope`、`LogEvent`、`LogPattern` | 日志查询实际范围、事件和聚合模式 |
|
||
| `LogSourceKind`、`LogTopic` | 限制日志来源和主题的可选集合 |
|
||
| `ToolContractCollections` | 对 contract 集合做 defensive copy 和空值规范化 |
|
||
|
||
## 8. Guard:把两个验证命题分开
|
||
|
||
### 8.1 Evidence Guard
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `EvidenceGuard` | 模型不能被信任去验证自己引用的 ID 和 Run 归属 | 严格验证 Draft、analysis ID、canonical READY、当前 Run 所有权、evidence status 和引用闭包 | 不调用模型,不判断结论语义是否成立 |
|
||
| `EvidenceGuardResult` | 校验只能是 verified snapshot 或 violations | 阻止半有效结果继续发布 | 不包含原始 Draft 修复逻辑 |
|
||
| `VerifiedEvidenceSnapshot` | SemanticGuard 只能看到已验真的证据投影 | 汇总 verified analyses 和 sources | 不包含 raw Tool payload |
|
||
| `VerifiedAnalysisEvidence`、`VerifiedEvidence` | 保持 analysis 到证据的归属关系 | 提供来源、scope、excerpt 等安全证据 | 不提升为业务结论 |
|
||
| `EvidenceViolation`、`EvidenceViolationCode` | Repair 和 Fallback 需要稳定失败原因 | 标识缺 analysis、未知调用、Run mismatch、非 READY、引用不闭合等 | 不保存内部异常栈 |
|
||
|
||
### 8.2 Semantic Guard 与模型调用边界
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `SemanticGuard` | 引用真实仍可能无法支持结论 | 用隔离单轮调用输出 `SUPPORTED / UNSUPPORTED`,严格解析并有限 retry | 无 Tool、无记忆、不访问 Redis、不改写 Draft |
|
||
| `GuardModelCall` | Router、单轮回答、Repair、Semantic 都需一致的模型预算、timeout 和 bytes 控制 | 在线程池中执行单轮 ChatModel,记录 usage,超时取消 future | 不决定业务 retry policy |
|
||
| `SemanticDraftView` | Guard/Repair 只应比较用户可见语义 | 从 Draft 提取稳定语义视图,并判断修复前后是否一致 | 不包含引用实现细节 |
|
||
| `SemanticGuardInput`、`SemanticGuardDecision` | 固定 Guard 输入和带 verdict 的输出 | 只传 query、Draft view 和 verified snapshot | 不传 Prompt 历史或 raw Tool response |
|
||
| `SemanticGuardLimits`、`SemanticGuardPrompt` | 输入、输出、单次/总 timeout 和判定职责可测试 | 限定一次语义审查的成本与 Prompt | 不暴露给 Diagnosis Agent |
|
||
| `GuardModelCallException` | 单轮模型失败要按 timeout、transport、parse、schema 分类 | 为 Harness retry 提供类型化信号 | 不直接映射用户内容 |
|
||
|
||
## 9. Release:唯一公开决策点
|
||
|
||
### 为什么需要
|
||
|
||
如果 Agent、Guard、Application 都可以各自构造结果,同一个失败会出现不同用户语义,迟到内容也可能绕过安全校验。Release 必须集中回答一个问题:当前 Run 有哪些内容可以公开?
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `DiagnosisReleaseUseCase` | Draft、停止、Guard 和 Repair 的组合分支必须只有一个所有者 | 处理有结论、无结论、受控停止、非法 Draft;协调 Guard/Repair/Semantic;返回成功或 Fallback | 不持久化、不发送 SSE、不写新结论 |
|
||
| `EvidenceRepair` | 引用或结构小错不应总是丢掉语义正确的 Draft | 单轮修复引用,严格解析,并用 `SemanticDraftView` 保证用户语义不变 | 只 attempt 一次;不新增事实、不改结论 |
|
||
| `SafeFallbackFactory` | 失败文案若交给模型生成会再次引入幻觉 | 确定性构造 evidence failed、semantic unsupported/unavailable、insufficient evidence、missing context | 只使用已验真事实和有界问题码 |
|
||
| `DiagnosisReleaseResult` | 下游不能同时收到 Draft 和 Fallback | 类型化承载 outcome、Draft、verified evidence 或 SafeFallback | 不等同于 RunState |
|
||
| `EvidenceRepairLimits`、`EvidenceRepairPrompt` | Repair 的成本与职责必须比 Diagnosis Agent 更窄 | 限制输入/输出/timeout,固定只修引用的指令 | 不允许 Tool Calling |
|
||
|
||
## 10. Retry:显式、类型化、可审计的 attempt
|
||
|
||
### 为什么需要
|
||
|
||
重试会改变成本、延迟和副作用,必须是调用者的有意识决策。统一 Executor 负责循环,但具体组件持有自己的 Policy。
|
||
|
||
| 类型 | 设计原因与功能 |
|
||
|---|---|
|
||
| `HarnessRetryExecutor` | 在每个 attempt 前检查 Run active,统一记录成功/失败,并绝不吞掉取消和预算耗尽 |
|
||
| `HarnessRetryPolicies` | 集中定义 Router/SemanticGuard 最多两次,其余一次的严格策略 |
|
||
| `RetryPolicy` | `maxAttempts + retryable failures` 的不可变值,避免布尔 `retry=true` |
|
||
| `RetryFailure` | timeout、transport、parse、schema、invalid output、cancel、budget 等稳定分类 |
|
||
| `RetryAttempt` | 记录 attempt 序号、是否成功和失败类型,供 Trace 使用 |
|
||
| `RetryOperation`、`RetryFailureClassifier` | 将执行和异常分类作为端口注入,Executor 不依赖具体模型组件 |
|
||
| `RetryExecutionException` | 重试终止时保留最终 attempt 和失败分类 |
|
||
|
||
## 11. Audit:记录控制事实,而不是复制业务正文
|
||
|
||
### 为什么需要
|
||
|
||
诊断系统需要回答“为什么停止、调用了什么、Token 是否对账、发布为何降级”,但普通观察面不应长期保存 Prompt、SQL、日志 query、raw response 或 reasoning。
|
||
|
||
| 组件 | 为什么设计 | 作用 | 明确边界 |
|
||
|---|---|---|---|
|
||
| `DiagnosisTraceRecorder` / `JpaDiagnosisTraceRecorder` | 所有阶段需要同一 exact-run timeline | 按 Run 分配 sequence,追加安全事件;失败 best-effort | 不改变业务结果,不保存敏感正文 |
|
||
| `TraceAuditEvents` | 各组件手写 details 容易字段漂移或泄露 | 集中构造 run/routing/model/tool/progress/guard/release 事件 | 只接受有界、安全字段 |
|
||
| `DiagnosisTraceAuditEvent` | Recorder 与业务组件解耦 | 统一事件 identity、phase、type、status、details | 不是领域事件总线 |
|
||
| `ToolInvocationAuditSink` / `JpaToolInvocationAuditSink` | canonical raw 不能长期保存,但调用元数据要留存 | 保存 exact Run、Tool ID、状态、耗时、bytes 和有界 enrichments | 不保存完整 request/raw/agent result |
|
||
| `RagLookupAuditEnricher` | RAG 需要有限的质量诊断字段 | 从受限输入提取 step/query/relevance 等安全摘要 | 不打印完整 rewritten/raw query |
|
||
| `HarnessAgentAuditHook` | 框架每轮 Agent 模型步骤需关联数据库 step 和 Provider reasoning 可用性 | 写 AgentStep metadata,并把 reasoning/assistant text 放独立受限存储 | reasoning 不进普通 Timeline、Guard 或下一轮上下文 |
|
||
| `AgentStepAuditTracker` | Tool audit 需要关联当前 Agent step,但不能使用 ThreadLocal | 按 runId 显式绑定/查询/清理 stepId | 不保存 Step 实体 |
|
||
| `ModelCallAuditor` / `ModelCallLedger` | 所有模型入口都要按组件、轮次对账 Token | begin call、记录 Provider usage、写 MODEL_TOKEN_USAGE、生成 Run reconciliation | Usage 缺失时标 unavailable,不估算 |
|
||
| `RunConclusionExtractor` | Trace/DB 常需直接读取安全发布结论 | 从 public JSON 提取 conclusion | 不读取 Provider reasoning |
|
||
|
||
### Audit 类型清单
|
||
|
||
| 类型 | 分类与功能 |
|
||
|---|---|
|
||
| `ModelCallComponent` | Router、System、Knowledge、Diagnosis、Repair、Semantic 的统一组件枚举,并映射 Trace phase |
|
||
| `ToolInvocationAuditEvent` | durable Tool metadata 事件 |
|
||
| `TracePhase`、`TraceEventType`、`TraceEventStatus` | Timeline 的阶段、事件和结果词汇表 |
|
||
|
||
## 12. Contract:跨组件唯一语言
|
||
|
||
### 为什么需要
|
||
|
||
Harness 横跨模型 JSON、Java 对象、Redis、JPA 和 SSE。若状态以自由字符串在各层重复定义,`SUCCESS`、`READY`、`EVIDENCE_FOUND` 很容易被混为一谈。Contract 包将不同维度保持正交。
|
||
|
||
| 类型 | 为什么存在与表达什么 |
|
||
|---|---|
|
||
| `DiagnosisDraft` | Diagnosis Agent 唯一结构化输出;analysis 绑定 Tool Call 引用,允许 `conclusion=null` |
|
||
| `KnowledgeAnswerDraft` | Knowledge Query 单轮模型输出,答案项绑定文档来源 |
|
||
| `PublishedResult` | 只有成功、安全的诊断结果可持久化为下一轮候选 |
|
||
| `PreviousTurn` | PublishedResult 的有界历史投影,不是完整会话记录 |
|
||
| `SafeFallback` | 确定性公开降级结构,包含 verified sources、observed facts、limitations、next steps 和 validation issues |
|
||
| `SourceDocument` | 知识回答中的文档身份与来源 |
|
||
| `IntentType` | `SYSTEM_CHAT / KNOWLEDGE_QUERY / DIAGNOSIS` 路由结果 |
|
||
| `InvocationStatus` | Tool invocation 生命周期:`PROJECTING / READY / ERROR` |
|
||
| `EvidenceStatus` | Tool 客观结果:`EVIDENCE_FOUND / NO_EVIDENCE / ERROR` |
|
||
| `SemanticVerdict` | 最终推论支持度:`SUPPORTED / UNSUPPORTED` |
|
||
| `ReleaseOutcome` | Harness 发布结果:`SUCCESS / FALLBACK / FAILED / CANCELLED` |
|
||
| `SseOutcome` | 当前未被运行时消费的遗留枚举;公开 `done` 实际使用 `ReleaseOutcome`,不要把它作为 SSE 协议真理源 |
|
||
| `FallbackType` | evidence validation、semantic、insufficient evidence、missing context 等降级原因 |
|
||
| `AnalysisKind` | 区分正向证据分析与限定范围的负向观察 |
|
||
| `ContractCollections` | 对公共 contract 集合做 defensive copy、去空和不可变处理 |
|
||
|
||
## 13. 配置装配:组件如何真正连起来
|
||
|
||
`HarnessChatConfiguration` 不在 `harness` 包内,但它是运行时组件图的 Composition Root:
|
||
|
||
- 构造两个有界线程池:Chat worker 与 Harness model executor;
|
||
- 从 `ChatHarnessProperties` 创建 Run、预算、停止阈值和各模型调用 limits;
|
||
- 装配 Redis canonical store、ToolBoundary、三类 Adapter/Projector;
|
||
- 仅在存在有效逻辑数据源时注册 `query_mysql`;
|
||
- 为每个 Run 创建带 Model/Tool interceptor 和 Audit Hook 的 Agent;
|
||
- 装配 Guard、Repair、Release、Router、三个执行分支和顶层 Application UseCase。
|
||
|
||
它存在的原因是让所有限制和替换点在一个地方可见。组件内部不应自行读取 Spring 配置或寻找全局 Bean,否则 focused test 很难证明其边界。
|
||
|
||
## 14. 用调用链快速定位组件
|
||
|
||
| 想回答的问题 | 首先阅读 | 接着阅读 |
|
||
|---|---|---|
|
||
| 一次请求如何创建并结束 Run | `ChatApplicationUseCase` | `DiagnosisHarnessCore`、`JpaChatRunStore` |
|
||
| ReAct 每轮如何计预算 | `DiagnosisAgentFactory` | `HarnessModelInterceptor`、`ModelCallAuditor` |
|
||
| Tool 为什么被拒绝 | `HarnessToolInterceptor` | `DiagnosisProgressTracker`、`ToolBoundary` |
|
||
| Tool 结果为何没有原样给模型 | `ToolBoundary` | 具体 ResultProjector、`ToolResultViewProjector` |
|
||
| 一条 evidence_ref 如何验真 | `EvidenceGuard` | `CanonicalToolInvocation`、具体 ToolResult contract |
|
||
| 为什么最终是 FALLBACK | `DiagnosisReleaseUseCase` | `SafeFallbackFactory`、Trace 的 RELEASE 事件 |
|
||
| 为什么诊断停止继续查 | `DiagnosisProgressTracker` | Tool progress / rejection / collection stop Trace |
|
||
| Token 为什么对不上 | `ModelCallAuditor` | `ModelCallLedger`、`RUN_FINISHED` reconciliation |
|
||
|
||
## 15. 组件边界自检
|
||
|
||
未来新增能力时,可以用下面的问题判断放置位置:
|
||
|
||
1. 它是在判断业务根因吗?应留在 Diagnosis Agent,而不是 Core/Tool/Guard。
|
||
2. 它能由代码机械证明吗?应放在 Boundary、Tracker 或 EvidenceGuard。
|
||
3. 它需要完整 Tool 真相吗?读取 canonical store,不要从 Agent observation 反推。
|
||
4. 它要决定公开内容吗?只能进入 Release,不要在 Application 或 Guard 私自构造。
|
||
5. 它是短期验真数据还是长期运营元数据?前者 canonical,后者 metadata audit。
|
||
6. 它会再次调用模型吗?必须进入 Core budget、ModelCallAuditor、timeout 和显式 retry policy。
|
||
7. 它引入了新的状态吗?先确认是否只是 error code、stop reason 或 fallback reason,避免再造全局生命周期。
|