123 lines
8.7 KiB
Markdown
123 lines
8.7 KiB
Markdown
## Context
|
||
|
||
公开 Chat 仍有同步 `/api/chat` 和伪流式 `/api/chat_stream` 两条旧 `ChatService` 链路;Controller 直接读取 Session 历史、获取模型/Tools、选择策略并维护 cached thread pool。阶段 6A 已完成 protocol-neutral `ChatApplicationUseCase`、observer 和 `ChatRunControl`,但阶段 2-6A 的 plain Java components 尚未形成生产 Bean graph。前端同时保留 quick JSON 与 stream SSE 两套消费者。
|
||
|
||
本 change 是 L4 破坏性协议迁移。ISS-014 已冻结 event schema、无兼容分支和阶段 7 清理边界;本阶段必须原子切换 Controller、生产装配与前端,不能出现旧路径绕过 release boundary。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
|
||
- 唯一 `POST /api/chat` 返回 named SSE events,固定顺序和互斥规则。
|
||
- Controller 只负责校验、SSE/HTTP 和连接生命周期,业务执行只调用 `ChatApplicationUseCase`。
|
||
- 使用同一个 Run control 处理断开/timeout/send failure,并禁止断开后的终态发送。
|
||
- 生产装配阶段 2-6A 的 Core、Tools、Agent、Guards、executors、store 和 use case。
|
||
- 使用集中配置与 Spring-managed bounded executors。
|
||
- 前端唯一 consumer 严格消费新契约并保存 exact session/run IDs。
|
||
|
||
**Non-Goals:**
|
||
|
||
- 不做 Token streaming 或最终内容切片。
|
||
- 不修改 `/api/ai_ops` 的 URL、请求或事件协议。
|
||
- 不物理删除旧多 Agent、ChatService、Hooks 或 ThreadLocal;阶段 7 处理。
|
||
- 不做断线续传、event replay、WebSocket、轮询或 live E2E。
|
||
|
||
## Decisions
|
||
|
||
### 1. Named SSE event 是唯一协议层类型
|
||
|
||
新增 immutable SSE payload:`metadata(session_id,run_id)`、`status(code,message)`、`content(content_type,payload)`、`failure(code,message)`、`done(outcome)`。`SseEmitter.event().name(type).data(payload)` 直接使用业务 event name,不再发送旧 `message + {type,data}` 包装。
|
||
|
||
替代继续用统一 `message` event;拒绝,因为它允许 type 漂移并让浏览器保留旧兼容分支。`content.payload` 直接引用阶段 6A sealed typed content,禁止 Map/Object 临时协议。
|
||
|
||
### 2. 每个连接使用显式 first-terminal-wins SSE session
|
||
|
||
新增 protocol adapter 持有 emitter、`AtomicReference<ChatRunControl>`、disconnect flag、metadata flag 和 terminal flag。`onStarted` 固定发送 metadata;若 disconnect 已先发生则立即取消新得到的 Run control。status 只能在 metadata 后、terminal 前发送。success 原子发送 content + done 后 complete;failure 原子发送 failure + done 后 complete。
|
||
|
||
`onTimeout`、`onError`、异常 send 和非正常 `onCompletion` 只标记 disconnected 并调用 `cancelClientDisconnect()`;不尝试补发 failure/done。正常 complete 在回调前标记 terminal,避免把成功误判为断开。替代仅在 IOException catch 取消;拒绝,因为 timeout 和 disconnect-before-onStarted 会漏掉。
|
||
|
||
### 3. 请求接纳失败不进入 SSE 事件序列
|
||
|
||
空 query 在创建 emitter/Run 前返回 HTTP 400;bounded worker rejection 在创建 Run 前返回 HTTP 503。五事件顺序只适用于已接纳并成功持久化 Run 的请求,因而 metadata 始终是第一个 SSE event。Session ID 格式错误由 use case 形成有 Run 的稳定 failure,除非它在 startRun 前失败;Controller 只做 query shape 校验。
|
||
|
||
### 4. Chat Controller 不拥有模型、Tools、Session 或业务执行
|
||
|
||
删除同步 Chat 方法、`/chat_stream`、ChatResponse、历史 helper、内容切片和 Chat 使用的 `ChatService/SessionManager/ChatModel/ToolCallbackProvider`。Controller 构造注入 `ChatApplicationUseCase` 和 qualifier Chat TaskExecutor。
|
||
|
||
同一类中的 `/api/ai_ops` 仍需旧模型/Tools。为保持其公开协议不变又清理 Controller 依赖,向 `AiOpsService` 增加内部 overload,由 service 注入 ChatModel/ToolCallbackProvider 并调用既有执行方法;Controller 只传 request/session/run。替代本阶段迁移 AiOps 到新 use case;拒绝,因为超出阶段 6B。
|
||
|
||
### 5. Production assembly 使用一个显式 Bean graph
|
||
|
||
新增 `HarnessChatConfiguration`,按依赖顺序装配:
|
||
|
||
```text
|
||
properties/Clock/executors
|
||
-> DiagnosisHarnessCore + HarnessRetryExecutor + GuardModelCall
|
||
-> RedisCanonicalInvocationStore + ToolBoundary
|
||
-> RAG/log/MySQL adapters + HarnessEvidenceTools
|
||
-> DiagnosisAgentFactory/UseCase
|
||
-> EvidenceGuard/Repair + SemanticGuard + ReleaseUseCase
|
||
-> Router + System/Knowledge/Diagnosis executors
|
||
-> ChatApplicationUseCase(JpaChatRunStore)
|
||
```
|
||
|
||
所有模型入口复用同一 Spring `ChatModel` 和 model executor。RAG/log adapters 复用现有 `LookupKnowledgeTool`/`QueryLogsTools` legacy executors,但所有 Agent-facing output 仍经过 ToolBoundary projection。MySQL 从 `MysqlToolProperties` 生成独立 datasource map;空配置时 Tool 仍注册但 fail closed 为 unknown datasource。
|
||
|
||
### 6. 并发与预算全部集中配置
|
||
|
||
新增 `harness.chat` configuration properties,覆盖 Chat worker/model pool、Run deadline/budget、canonical TTL/bytes、Agent/Router/Guard/single-turn/Knowledge limits 和 key prefix。worker 与 model executor 均使用 bounded `ThreadPoolExecutor + AbortPolicy`,由 Spring 负责 shutdown;Controller 不持有或创建 executor。
|
||
|
||
Retry attempt audit 首版写结构化 debug log,不新增持久化表。替代硬编码构造参数;拒绝,因为阶段 0 已要求上限集中可校准。
|
||
|
||
### 7. 前端删除 quick/stream 双轨
|
||
|
||
移除 mode selector DOM、`currentMode`、`sendQuickMessage` 和旧 `/chat_stream` parser。`sendMessage` 只调用 `sendChatMessage`,POST `/api/chat` 并按空行分隔完整 SSE frame,解析 named event 与 JSON payload。metadata 更新 trace target;status 更新等待说明;content 一次渲染 typed payload;failure 显示安全消息;done 校验 outcome 并结束。
|
||
|
||
typed payload 使用专用 renderer 转换 SYSTEM_CHAT、KNOWLEDGE、DIAGNOSIS_REPORT、FALLBACK,不把对象隐式拼接为字符串。unknown event、重复 metadata/content/failure/done、content/failure 同时出现或流结束前无 done 均 fail closed。
|
||
|
||
## Module and Ownership Audit
|
||
|
||
```text
|
||
Browser fetch POST /api/chat
|
||
-> ChatController (validation + connection admission)
|
||
-> ChatSseSession (event order + connection cancellation)
|
||
-> bounded chat worker
|
||
-> ChatApplicationUseCase (session/run/routing/dispatch/terminal persistence)
|
||
-> Harness Core / Agent / Tools / Guards
|
||
-> typed content or stable failure
|
||
-> ChatSseSession (single content|failure + done)
|
||
```
|
||
|
||
- Controller owns HTTP/SSE only;SSE session owns event state;Application owns business Run;Core owns cancellation/budget;Store owns MySQL;Harness configuration owns infrastructure graph。
|
||
- 数据真理源仍为 `diagnosis_run`;SSE metadata 只透出同一 use case 生成的 IDs。
|
||
- 最大生命周期风险是 disconnect 与 onStarted 竞态;atomic control + pending disconnect 解决。
|
||
- 最大部署风险是 L4 前后端不同步;Controller/前端必须同一 commit 原子部署,回滚也必须成对。
|
||
- 无 ADR 冲突;ISS-013 的 token-like “incremental content”被更新的 ISS-014 明确收敛为实时 status + 单次安全 content。
|
||
|
||
## Interface Impact
|
||
|
||
- Level: L4 breaking HTTP/frontend contract。
|
||
- Removed: synchronous JSON `POST /api/chat`、`POST /api/chat_stream`、旧 `ChatResponse` 和 message-wrapper Chat SSE。
|
||
- Added: SSE `POST /api/chat` with five named events and exact snake_case payload fields。
|
||
- Consumers: bundled `static/app.js`、外部 Chat API callers、Controller tests/MockMvc;Trace/feedback continue using metadata IDs。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- [客户端仍调用旧 endpoint] -> 同 commit 迁移 bundled frontend;外部消费者必须按 L4 文档迁移,不提供兼容层。
|
||
- [发送中断后 worker 继续] -> every send failure/timeout/error cancels exact Run control;late result blocked by Core。
|
||
- [executor 饱和] -> bounded queue + HTTP 503 before Run;不 caller-runs、不无界排队。
|
||
- [Spring graph 缺 Bean/歧义] -> focused configuration context test + Maven compile。
|
||
- [legacy Tool side effects] -> Agent-facing output 仍经新 boundary;legacy classes stage 7 physically removed。
|
||
- [typed diagnosis payload frontend rendering复杂] -> deterministic renderer and JS contract tests/static assertions。
|
||
|
||
## Migration Plan
|
||
|
||
1. 部署前确保 V012 已迁移,Redis 与模型配置可用。
|
||
2. 同一版本注册 Harness Bean graph、切换 `/api/chat` SSE、删除 `/chat_stream` 并更新 bundled frontend。
|
||
3. smoke/focused tests 验证 metadata/status/content|failure/done、same IDs 和 cancellation;live E2E 延后阶段 7。
|
||
4. 回滚必须同时回滚 Controller 与 frontend 到阶段 6A commit;数据库 V012 nullable additive 可保留。
|
||
|
||
## Open Questions
|
||
|
||
- None。初始数值为可配置默认值,阶段 7 根据 live trace 校准。
|