173 lines
15 KiB
Markdown
173 lines
15 KiB
Markdown
## Context
|
||
|
||
当前 Diagnosis ReAct loop 只有模型、Tool、Token、字节和时间预算,没有“查询是否仍在产生信息”的状态。`HarnessToolInterceptor` 将完整 canonical `agent_result` 直接放入模型上下文;三个 Agent-facing Tool 使用裸业务 request 生成 Schema;`DiagnosisAgentUseCase` 只返回非空 `DiagnosisDraft`;`DiagnosisReleaseUseCase` 要求 Draft 非空并对所有 Draft 运行 EvidenceGuard/Repair/SemanticGuard。预算耗尽的临时 Fallback 位于 `ChatApplicationUseCase`,导致业务 Release 决策分散。
|
||
|
||
本变更是 L3 模型协作接口演进。公开 HTTP/SSE、业务 Tool backend、数据库表和 `SafeFallback` JSON 结构保持兼容。当前环境没有 semantic retrieval/LSP,调用链通过源码、测试和 `rg` 引用核查完成。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- 在硬预算耗尽前确定性停止连续无增益 Tool 调用。
|
||
- 让模型在看到成功非空结果后,以 `GAINED / NO_GAIN` 表达该结果是否推进当前诊断。
|
||
- 允许模型零次调用 Tool 或以 `conclusion=null` 合法结束。
|
||
- 信息饱和、预算终止和主动无结论均由 Diagnosis Release 发布为有过程的安全 Fallback。
|
||
- 有结论 Draft 继续经过完整 EvidenceGuard、单次 EvidenceRepair 和 SemanticGuard。
|
||
- 控制数据、raw payload、内部 thought 和预算不进入模型上下文或用户结果。
|
||
|
||
**Non-Goals:**
|
||
|
||
- 不引入 Judge、`UNKNOWN`、分数、`new_count`、`next_action` 或第二套诊断生命周期。
|
||
- 不做自然语言语义去重或跨 Run 进展继承。
|
||
- 不改变 Tool backend 参数、公开 SSE 事件或前端字段协议。
|
||
- 不把候选文档、`REFERENCE` 或非空结果自动认定为诊断证据。
|
||
|
||
## Decisions
|
||
|
||
### 1. RunContext 持有最小线程安全 ProgressTracker
|
||
|
||
新增 `DiagnosisProgressTracker` handle,并在 `DiagnosisHarnessCore.startRun` 时用固定阈值创建。tracker 维护:连续 `NO_GAIN`、`COLLECTING / SATURATED`、可选 `stop_reason`、最后一个待模型评价的成功 Tool Call、已成功检查的规范化 scope,以及已完成 canonical key/Tool Call ID 的有序索引。
|
||
|
||
tracker 不保存 raw response、完整 Agent result 或用户可见摘要。Canonical Store 仍是 Tool 真相源;结束投影器按 tracker 的 key 索引逐条读取 canonical 记录。
|
||
|
||
替代方案是给 `CanonicalInvocationStore` 增加按 Run 枚举。拒绝,因为停止状态是短生命周期控制数据,且该方案会扩大 Redis 接口及所有 fake store,实现另一种事实索引。
|
||
|
||
### 2. 使用三个强类型 Envelope,共享上一轮评价类型
|
||
|
||
Agent-facing Schema 使用三个具体输入类型:`RagToolCall`、`QueryLogsToolCall`、`MysqlToolCall`。每个类型包含可选 `previous_observation` 和必填 `input`;`input` 继续使用现有业务 request,`previous_observation` 使用共享的 `PreviousObservation(tool_call_id, information_gain)`。
|
||
|
||
不使用泛型 `ToolCallEnvelope<T>` 直接生成 Schema,因为运行时类型擦除可能使嵌套 `input` 丢失具体字段;不使用 `JsonNode`,因为它无法给模型提供强 Schema。interceptor 严格解析对应 Envelope,先消费控制字段,再把 `input` 序列化为原业务 JSON 交给 adapter。
|
||
|
||
首个 Tool Call 以及上一结果已由 Harness 确定性评价时允许省略 `previous_observation`。存在待评价调用时,新 Tool Call 必须携带完全匹配的上一 Tool Call ID 和二值评价;缺失、错序或跨 Run 引用返回有界协议错误且不执行 Tool。
|
||
|
||
### 3. 先应用上一轮评价,再决定本轮是否放行
|
||
|
||
interceptor 的固定顺序为:
|
||
|
||
1. 严格解析 Envelope 并校验 `previous_observation`。
|
||
2. 将上一轮 `GAINED` 清零计数,或将 `NO_GAIN` 加一。
|
||
3. 若达到阈值,拒绝当前业务 Tool,返回一次 `STOP_REQUIRED/INFORMATION_SATURATED`。
|
||
4. 规范化本次业务 request 的 scope;若与同 Tool 的成功历史 scope 重复,则不执行 Tool、记一次确定性 `NO_GAIN`,并返回有界重复提示或 STOP_REQUIRED。
|
||
5. 其余请求进入现有 adapter/ToolBoundary。
|
||
6. 成功后记录 canonical key;`NO_EVIDENCE` 由 Harness 立即记为 `NO_GAIN`,`EVIDENCE_FOUND` 标记为待模型评价。
|
||
|
||
Tool `ERROR` 不产生 information gain,也不把失败 scope 写入成功去重集合;它沿用技术故障和现有预算/重试边界。
|
||
|
||
替代方案是让 Tool 自行报告质量。拒绝,因为 Tool 只知道客观返回,无法判断对当前诊断假设的价值。
|
||
|
||
### 4. 只做确定性 scope 规范化
|
||
|
||
每个 Tool 提供一个无副作用 scope projector:RAG 使用规范化 query;日志使用 topic、query 和实际 lookback;MySQL 使用 logical datasource、规范化 SQL 文本和参数。仅规范化空白、大小写明确不敏感的枚举/标识、确定性默认值和结构化集合,不判断自然语言改写是否语义等价。
|
||
|
||
重复只比较 `tool_name + normalized_scope`,且只基于已成功执行的历史 scope。被拒绝的重复调用不进入 ToolBoundary、canonical store 或 Tool 调用预算,但会记录安全 Trace 并推进无增益计数。
|
||
|
||
### 5. Canonical 结果产生控制视图和模型白名单视图
|
||
|
||
ToolBoundary 继续保存完整 request、raw response 和 bounded `agent_result`。Tool 完成后:
|
||
|
||
- Harness Control View 读取 execution/evidence status、returned count、normalized scope、RAG relevance、truncated 和 canonical identity。
|
||
- Model Observation 只包含 Tool Call ID、实际 scope、有界 evidence/rows/events、`evidence_status`、可选 `relevance_level` 和 `truncated`。
|
||
|
||
`HarnessToolInterceptor` 不再直接返回完整 `agent_result`,而是通过按 Tool 类型的 `AgentObservationProjector` 白名单序列化。RAG projector 兼容读取 `relevanceLevel` 和 `relevance_level`,在 canonical RAG result 中统一为 `relevance_level`。`REFERENCE` 仍交给模型判断信息增益。
|
||
|
||
### 6. 饱和后只有一次正常收尾机会
|
||
|
||
当 Tool 结果使 tracker 直接饱和时,当前 Model Observation 同时携带 `stop_required=true` 和 `reason=INFORMATION_SATURATED`。当模型在下一次 Tool Call 中回传 `NO_GAIN` 后达到阈值时,该本轮 Tool 被拒绝并返回同样的 STOP_REQUIRED observation。
|
||
|
||
tracker 记录控制指令已交付。下一轮模型仍发起 Tool Call时,interceptor 抛出可识别的 `DiagnosisCollectionStoppedException`;`DiagnosisAgentUseCase` 不把它包装为内部故障,而是返回“无 Draft + stop reason”的执行结果。这样模型获得一次生成合法 Draft 的机会,同时无法靠重复 Tool Call继续空转。
|
||
|
||
替代方案是无限返回 STOP_REQUIRED。拒绝,因为模型无视指令时仍会消耗模型预算并重现原问题。
|
||
|
||
### 7. Agent 执行返回 Draft 与停止投影,而不是只返回 Draft
|
||
|
||
`DiagnosisAgentUseCase` 返回内部 `DiagnosisAgentExecution`:可选 Draft、`ProgressSnapshot` 和可选 `DiagnosisStopReason`。正常 Draft、强制饱和停止和预算异常都在 Diagnosis executor 边界形成 Release 输入。
|
||
|
||
`DiagnosisProgressProjector` 在 Tool loop 结束时读取 tracker 索引和 canonical store,一次性生成有界 snapshot;缺失、过期、ERROR 或不属于当前 Run 的记录不会成为 verified fact,并产生稳定 limitation/trace。snapshot 不进入模型上下文。
|
||
|
||
### 8. DiagnosisReleaseUseCase 统一业务发布决策
|
||
|
||
Release 接收 query、可选 Draft、ProgressSnapshot 和可选 stop reason:
|
||
|
||
- `conclusion != null`:执行现有完整 EvidenceGuard、一次 Repair、重验和 SemanticGuard。
|
||
- `conclusion == null`:不执行 Repair/SemanticGuard;若 Draft 有 Tool 引用,仅验证当前 Run canonical 引用真实性和负向语义,不要求正常结论结构。
|
||
- 无 Draft且 `INFORMATION_SATURATED` 或 `BUDGET_LIMIT_REACHED`:从 snapshot 确定性生成 Fallback,不调用额外模型。
|
||
- 零 Tool 且 Draft 的 `limitations.missing_info` 非空:发布 `MISSING_REQUIRED_CONTEXT`。
|
||
- 有有限排查但无可支持结论:发布 `INSUFFICIENT_EVIDENCE`。
|
||
- 真正 Tool/模型/基础设施故障且无法形成安全过程:继续 `FAILED`。
|
||
|
||
最终模型文本为空或不能严格解析为 `DiagnosisDraft` 时,非法内容本身始终被丢弃,不做 Markdown/自然语言 JSON 抽取,也不调用额外模型修复。Agent 输出异常携带一次有界 `ProgressSnapshot`;Executor 仅在 snapshot 含当前 Run 已验真的 observed facts 时交给 Release 生成 `INSUFFICIENT_EVIDENCE`,否则保持 `FAILED`。Trace 只记录固定失败类别、输出字节数和是否存在可发布进展,不记录模型原文、字段值或解析异常文本。
|
||
|
||
`ChatApplicationUseCase` 删除业务内容级 `recoverBudgetExhaustion`。Diagnosis executor 捕获可识别预算终止并调用 Release;Application 只允许该已处理 Diagnosis Fallback 跳过 `core.checkActive/completeSuccess`,持久化 `release_outcome=FALLBACK`。Run 内部仍保留 `BUDGET_EXHAUSTED`,数据库公开运行状态继续按既有规则记录为成功发布的 Fallback。
|
||
|
||
### 9. Prompt 只约束模型职责,不复制 Tool Schema
|
||
|
||
中文 Prompt 明确:无需强行得出根因;`conclusion=null` 是合法完成;缺少企业、时间、服务或错误信息时允许零 Tool 并填写 `limitations.missing_info`;只有新增可验证事实确认、排除或缩小假设才是 `GAINED`;正确但无用、通用或重复内容是 `NO_GAIN`;没有明确不同且可能产生新信息的 scope 时停止;收到 STOP_REQUIRED 后不得继续调用 Tool。
|
||
|
||
Prompt 不写 Tool 名、Schema、阈值、计数器、Projector、预算或 `next_action`。
|
||
|
||
### 10. Trace 记录决策,不泄露推理
|
||
|
||
Trace 增加有界事件或字段,记录 Tool Call ID、Tool name、scope 摘要、information gain 的生产者(Harness/Model)、连续计数变化、collection state 和 stop reason。不得记录 Prompt、模型 thought、raw Tool response、完整 SQL 参数、预算余量或模型评价理由。
|
||
|
||
### 11. Run 总账与模型调用明细使用同一份 Provider Usage
|
||
|
||
`RunContext` 增加最小线程安全模型调用账本,只维护组件轮次、已审计调用数、Usage 不可用调用数和 Token 合计。每次实际模型调用在预算放行后取得组件轮次;`HarnessModelInterceptor` 负责 Diagnosis Agent,`GuardModelCall` 负责 Router、System Chat、Knowledge Answer、Evidence Repair 和 Semantic Guard。两条入口都从 Spring AI `Usage` 读取同一组 input/output Token,先登记调用明细,再交给现有 `RunBudget` 累加总账。
|
||
|
||
每个模型调用 Trace 只包含 `component`、`component_round`、`usage_available`,并在 Usage 可用时包含 `input_tokens`、`output_tokens` 和 `total_tokens`;Usage 不可用时不写 Token 字段。Diagnosis Agent 的对应 `AgentStep.token_count` 回填 total Token;其他组件不伪装成 AgentStep。Run 结束事件同时写入预算总账、审计明细合计、Usage 不可用数量和 `tokens_reconciled`,从而显式暴露缺口而不是把未知 Token 当作零消耗。
|
||
|
||
进入 `HarnessToolInterceptor` 的 Tool 请求若因进展协议、重复 scope、信息饱和或观察合同失败而未进入/未成功交付业务边界,记录 `TOOL_REQUEST_REJECTED`。事件只保留安全 Tool Call ID、Tool name 和稳定 `error_code`;业务参数、原始响应、内部异常和预算余量均不进入 Trace。
|
||
|
||
不新增模型审计表:`diagnosis_trace_event` 是调用明细账,`RunBudget`/`diagnosis_run.total_token_count` 是 Run 总账,`AgentStep.token_count` 是 Diagnosis Agent 轮次摘要。这样避免三套可独立漂移的 Token 真相源。
|
||
|
||
## Module Map
|
||
|
||
```text
|
||
ChatApplicationUseCase
|
||
-> DiagnosisChatExecutor
|
||
-> DiagnosisAgentUseCase
|
||
-> ReactAgent
|
||
-> HarnessModelInterceptor
|
||
-> HarnessToolInterceptor
|
||
-> DiagnosisProgressTracker (RunContext handle)
|
||
-> HarnessEvidenceTools -> ToolBoundary -> CanonicalInvocationStore
|
||
-> AgentObservationProjector
|
||
-> DiagnosisProgressProjector -> CanonicalInvocationStore
|
||
-> DiagnosisReleaseUseCase
|
||
-> no-conclusion reference validation / SafeFallbackFactory
|
||
-> EvidenceGuard -> EvidenceRepair -> SemanticGuard (conclusion only)
|
||
-> ChatRunStore -> named SSE content
|
||
```
|
||
|
||
## Interface Impact
|
||
|
||
- 级别:L3 协作接口。
|
||
- Agent-facing input 从裸业务 request 改为 `{previous_observation?, input}`。
|
||
- 业务 adapter、backend、公开 HTTP/SSE、数据库和前端消费字段保持兼容。
|
||
- 所有 Tool loop scripted tests 必须使用新 Envelope;KnowledgeQueryExecutor 若直接调用 registry bridge,继续走业务 request,不使用 Agent-facing Envelope。
|
||
- 不提供旧/新 Schema 双轨;回滚以整个 change 为单位。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- [框架不能按预期生成嵌套强类型 Schema] -> 使用三个具体 Envelope record,并增加真实 callback schema 测试。
|
||
- [STOP_REQUIRED 后异常被框架包装] -> 使用 cause-chain 分类测试,只有专用受控停止异常可转换为 Release 输入。
|
||
- [预算终止与 Application active check 冲突] -> DiagnosisExecutionResult 显式标记已处理终止 Fallback,Application 仅对此窄分支跳过 success transition。
|
||
- [canonical TTL 到期导致过程不完整] -> Run timeout 小于 canonical TTL;投影缺失 fail closed 为 limitation,不伪造事实。
|
||
- [scope 规范化误判不同查询为重复] -> 首版只规范确定性字段,测试每个 Tool 的相同/不同 scope。
|
||
- [模型伪造上一轮评价 ID] -> tracker 只接受当前 Run 最后一个待评价 ID,错序/重复消费均拒绝。
|
||
- [无结论 Draft 绕过安全检查] -> 只跳过结论 Repair/SemanticGuard;引用真实性、当前 Run 所有权和负向语义仍确定性验证。
|
||
- [模型完成排查后输出非法 Draft 导致过程丢失] -> 丢弃非法 Draft;仅当 ProgressSnapshot 含已验真 observed facts 时由 Release 确定性降级,无进展仍 fail closed。
|
||
- [脏工作区行为丢失] -> 迁移预算 Fallback 的测试意图,实施前后用 scoped diff 核对,不覆盖无关修改。
|
||
|
||
## Migration Plan
|
||
|
||
1. 先加入状态/Envelope/scope/projector 类型和 focused contract tests,不切换 Release。
|
||
2. 接入 interceptor、tracker、STOP_REQUIRED 和执行结果,固定真实框架 loop 行为。
|
||
3. 接入 ProgressSnapshot 与统一 Release,迁移 Application 预算 Fallback。
|
||
4. 更新中文 Prompt、Trace、配置和文档。
|
||
5. 运行 focused、Harness 回归和全量测试;再用 Maven 启动项目执行原始未知 Query 的 SSE、日志、数据库 exact-run E2E。
|
||
6. 回滚时整体回滚本 change;不单独恢复旧 Tool Schema 或 Application 预算分支。
|
||
|
||
## Open Questions
|
||
|
||
无。阈值、状态、Prompt、协议、去重边界、Release 所有权和兼容范围均已确认。
|