diff --git a/mvp/architecture/README.md b/mvp/architecture/README.md index 8cb507c..568e2a8 100644 --- a/mvp/architecture/README.md +++ b/mvp/architecture/README.md @@ -1,6 +1,6 @@ # MVP 架构文档 -**更新日期**:2026-07-08 +**更新日期**:2026-07-10 这里是 MVP 当前架构的唯一入口。旧版设计、早期拆解和已经被新实现替代的方案已归档到: @@ -29,7 +29,7 @@ ## 当前架构一句话 -SuperBizAgent MVP 是一个面向故障诊断的可追踪 Agent 系统:Chat 和 AIOps 入口统一进入 Agent 编排,Executor 通过显式工具收集日志、指标和知识库证据,Chat 链路由 Gatekeeper 做引用真实性校验、Verifier 做可推导性判断、Composer 生成最终表达;执行过程落到 `diagnosis_session`、`agent_step`、`tool_invocation`,最终通过 Trace API 和评测脚本证明诊断链路可解释、可回放、可对比。 +SuperBizAgent MVP 是一个面向故障诊断的可追踪 Agent 系统:Chat 和 AIOps 入口统一进入 Agent 编排,Executor 通过显式工具收集日志、指标和知识库证据,Chat 链路由 Gatekeeper 做引用真实性校验、Verifier 做可推导性判断、Composer 生成最终表达;多轮会话元数据落到 `chat_session`,每次诊断运行落到 `diagnosis_run`,步骤和工具明细通过 `agent_step.run_id`、`tool_invocation.run_id` 关联,最终通过 Trace API 和评测脚本证明诊断链路可解释、可回放、可对比。 ## 阅读顺序 diff --git a/mvp/architecture/agent-orchestration.md b/mvp/architecture/agent-orchestration.md index 17dbbdd..602e05a 100644 --- a/mvp/architecture/agent-orchestration.md +++ b/mvp/architecture/agent-orchestration.md @@ -42,13 +42,15 @@ flowchart TB end subgraph Trace["Trace persistence"] - Session["diagnosis_session"] + ChatSession["chat_session"] + Run["diagnosis_run"] Step["agent_step"] Invocation["tool_invocation"] SelfEval["self_evaluation"] end - ChatService --> Session + ChatService --> ChatSession + ChatService --> Run ChatPlanner --> Step ChatExecutor --> Step ChatGatekeeper --> SelfEval @@ -57,7 +59,8 @@ flowchart TB ChatDecision --> SelfEval ChatComposer --> Step - AiOpsService --> Session + AiOpsService --> ChatSession + AiOpsService --> Run AiOpsPlanner --> Step AiOpsExecutor --> Step AiOpsTools --> Invocation @@ -103,7 +106,7 @@ sequenceDiagram participant G as gatekeeper participant V as chat_verifier participant M as chat_composer - participant S as diagnosis_session + participant R as diagnosis_run C->>P: 原始问题 + history + retry_context P-->>C: planner_plan @@ -115,13 +118,13 @@ sequenceDiagram G-->>C: gatekeeper_result C->>V: executor_structured_output + gatekeeper_result + tool_trace_summary V-->>C: PASS / LOW_CONFID / REJECT - C->>S: 写入 verifier_evaluation + C->>R: 写入 verifier_evaluation alt LOW_CONFID 且允许补证据 C->>P: retry_context: 仅补缺失证据 else PASS 或 REJECT C->>M: allowed_claims + missing_info + recommended_actions M-->>C: composer_output - C->>S: 保存 Composer 最终 answer + C->>R: 保存 Composer 最终 answer end ``` diff --git a/mvp/architecture/current-mvp-architecture.md b/mvp/architecture/current-mvp-architecture.md index 7f07706..a4bebb8 100644 --- a/mvp/architecture/current-mvp-architecture.md +++ b/mvp/architecture/current-mvp-architecture.md @@ -65,7 +65,8 @@ flowchart TB end subgraph Store["Persistence and Trace"] - Session["diagnosis_session"] + ChatSession["chat_session"] + Run["diagnosis_run"] Step["agent_step"] Invocation["tool_invocation"] ApiDoc["api_document"] @@ -130,9 +131,10 @@ RAG Retrieval -> Milvus SDK fallback Persistence - -> diagnosis_session - -> agent_step - -> tool_invocation + -> chat_session + -> diagnosis_run + -> agent_step.run_id + -> tool_invocation.run_id -> api_document -> Milvus/Zilliz collection @@ -163,21 +165,22 @@ sequenceDiagram User->>API: 提交诊断问题 API->>Chat: execute chat strategy + Chat->>DB: 创建 chat_session metadata + diagnosis_run(runId) Chat->>Planner: 复杂问题进入规划 - Planner->>DB: 写入 agent_step + Planner->>DB: 写入 agent_step.run_id Planner->>Executor: 下发排查方向 Executor->>Tool: lookup_knowledge / logs / metrics - Tool->>DB: 写入 tool_invocation + Tool->>DB: 写入 tool_invocation.run_id Tool-->>Executor: 返回证据 Executor->>Gatekeeper: 输出 executor_evidence_v2 Gatekeeper->>DB: 读取 tool_invocation.evidence_refs 并校验引用 Gatekeeper->>Verifier: 传入已验真的 claims / excerpts - Verifier->>DB: 合并 self_evaluation.verifier_evaluation + Verifier->>DB: 合并 diagnosis_run.self_evaluation.verifier_evaluation Verifier->>Composer: 传入 allowed_claims / missing_info / actions Composer->>Chat: 生成最终用户答复 - Chat->>DB: 保存 diagnosis_session.answer - User->>Trace: GET /api/diagnosis/{sessionId}/trace - Trace->>DB: 聚合 session / step / tool + Chat->>DB: 保存 diagnosis_run.answer + User->>Trace: GET /api/diagnosis/{sessionId}/trace?runId=... + Trace->>DB: 聚合 run / step / tool Trace-->>User: 返回可回放诊断链路 ``` @@ -194,13 +197,14 @@ POST /api/chat -> Gatekeeper 校验 Executor 证据引用真实性 -> Verifier 判断 claim 是否能由已核验证据推出 -> Composer 生成最终用户答复 - -> 保存 diagnosis_session - -> 保存 agent_step - -> 保存 tool_invocation - -> 合并 self_evaluation.verifier_evaluation + -> 保存 chat_session metadata + -> 保存 diagnosis_run + -> 保存 agent_step.run_id + -> 保存 tool_invocation.run_id + -> 合并 diagnosis_run.self_evaluation.verifier_evaluation ``` -Chat 链路的质量门禁由三段组成:Gatekeeper 先做代码级引用验真,Verifier 再做 LLM 可推导性判断,Composer 最后控制对用户的表达边界。Gatekeeper、Verifier、Composer 的输出合并到 `diagnosis_session.self_evaluation.verifier_evaluation`,Trace API 会展示该验证结果。 +Chat 链路的质量门禁由三段组成:Gatekeeper 先做代码级引用验真,Verifier 再做 LLM 可推导性判断,Composer 最后控制对用户的表达边界。Gatekeeper、Verifier、Composer 的输出合并到当前 `diagnosis_run.self_evaluation.verifier_evaluation`,Trace API 会展示该验证结果。 Agent 编排细节见 [agent-orchestration.md](agent-orchestration.md)。 @@ -255,7 +259,7 @@ POST /api/ai_ops -> Prometheus / logs / knowledge tools -> 生成告警分析报告 -> AiOpsRuleEvaluationService - -> 合并 self_evaluation.aiops_rule_evaluation + -> 合并 diagnosis_run.self_evaluation.aiops_rule_evaluation -> Trace API 可查看全链路 ``` @@ -317,23 +321,30 @@ RAG 总体设计见 [rag-architecture.md](rag-architecture.md),检索运行细 ## 6. 持久化模型 -当前诊断持久化以三张表为核心: +当前诊断持久化以 session/run/trace 明细为核心: ```text -diagnosis_session - -> 一次诊断会话的主记录 +chat_session + -> 多轮会话目录和元数据 + -> session_id / status / message_pair_count + +diagnosis_run + -> 一次诊断运行的主记录 + -> run_id / session_id -> query / status / agent_flow / answer -> self_evaluation -> step_count / tool_call_count / duration agent_step -> Agent 模型调用步骤 + -> session_id / run_id -> step_index / agent_name -> model_input / model_output / thought -> duration / token_count tool_invocation -> 工具调用事实 + -> session_id / run_id -> tool_name / input_params / output_preview -> retrieval_layer / retrieval_details -> retrieval_details.evidence_refs @@ -343,7 +354,8 @@ tool_invocation 说明: -- 旧的 `diagnosis_record` 已不是当前主模型,迁移脚本中已经由 `diagnosis_session + agent_step + tool_invocation` 取代。 +- 旧的 `diagnosis_record` 已不是当前主模型。 +- `diagnosis_session` 已降级为历史兼容和回滚表,新执行写入 `chat_session + diagnosis_run`。 - `api_document` 仍用于文档元数据管理。 - 文档向量内容存放在 Milvus/Zilliz collection 中。 @@ -353,11 +365,12 @@ tool_invocation ```text GET /api/diagnosis/{sessionId}/trace +GET /api/diagnosis/{sessionId}/trace?runId=run-... ``` Trace API 聚合: -- 会话状态和最终报告。 +- 会话元数据、运行状态和最终报告。 - Agent step 序列。 - 工具调用和检索细节。 - Chat Gatekeeper / Verifier / Composer 结果。 diff --git a/mvp/architecture/data-model.md b/mvp/architecture/data-model.md index 8ca97d5..ef023fd 100644 --- a/mvp/architecture/data-model.md +++ b/mvp/architecture/data-model.md @@ -1,31 +1,44 @@ # 数据模型总览 -**更新日期**:2026-07-08 +**更新日期**:2026-07-10 **状态**:当前可运行架构 ## 1. 定位 -本文从架构角度说明当前 MVP 的核心数据模型。详细字段仍以 Flyway migration 和 `mvp/tables/` 为准。 +本文从架构角度说明当前 MVP 的核心数据模型。详细字段以 Flyway migration、实体类和 `mvp/tables/` 为准。 核心数据分三组: -- 诊断 Trace:`diagnosis_session`、`agent_step`、`tool_invocation` +- 会话与诊断 Trace:`chat_session`、`diagnosis_run`、`agent_step`、`tool_invocation` - 知识库:`api_document`、`knowledge_domain`、Milvus/Zilliz metadata - 反馈沉淀:`case_library` +`diagnosis_session` 仍保留为历史兼容和回滚表,不再是新执行写入的主模型。 + ## 2. 总体关系 ```mermaid erDiagram - diagnosis_session ||--o{ agent_step : has - diagnosis_session ||--o{ tool_invocation : has - diagnosis_session ||--o| case_library : creates_when_useful + chat_session ||--o{ diagnosis_run : owns + diagnosis_run ||--o{ agent_step : has + diagnosis_run ||--o{ tool_invocation : has + diagnosis_run ||--o| case_library : creates_when_useful api_document ||--o{ milvus_chunk : indexed_as knowledge_domain ||--o{ api_document : groups - diagnosis_session { + chat_session { bigint id varchar session_id + varchar status + int message_pair_count + datetime last_active_at + datetime expires_at + } + + diagnosis_run { + bigint id + varchar run_id + varchar session_id text query varchar status varchar agent_flow @@ -37,42 +50,23 @@ erDiagram agent_step { bigint id varchar session_id + varchar run_id int step_index varchar agent_name text model_input text model_output - text thought boolean has_tool_call } tool_invocation { bigint id varchar session_id + varchar run_id + bigint step_id varchar tool_name json input_params text output_preview - varchar retrieval_layer json retrieval_details - varchar relevance_level - varchar dedup_reason - } - - api_document { - bigint id - varchar doc_id - varchar file_name - varchar file_path - varchar status - int chunk_count - text metadata - } - - knowledge_domain { - bigint id - varchar domain_id - varchar description - text when_to_retrieve - int document_count } case_library { @@ -84,58 +78,52 @@ erDiagram text root_cause text solution } - - milvus_chunk { - varchar id - text content - json metadata - vector vector - } ``` 说明:Milvus/Zilliz collection 不是 MySQL 表,图中的 `milvus_chunk` 是逻辑模型。 -## 3. 诊断 Trace 模型 +## 3. 会话与运行模型 -### diagnosis_session +### chat_session -会话级主记录。 - -关键字段: +`chat_session` 是会话目录表,保存 `sessionId` 的元数据: | 字段 | 说明 | |---|---| -| `session_id` | 外部关联键,Trace 和 Feedback 都使用它 | -| `query` | 用户原始问题或 AIOps 输入摘要 | -| `status` | 执行状态 | +| `session_id` | 外部会话 ID,用于多轮上下文和 run 列表 | +| `status` | 会话目录状态 | +| `message_pair_count` | Redis 对话轮次数快照 | +| `last_active_at` | 最近活跃时间 | +| `expires_at` | 可为空的目录 TTL 元数据 | + +它不保存完整对话历史,正文消息仍由 Redis `SessionContext.messageHistory` 管理。 + +### diagnosis_run + +`diagnosis_run` 是一次可回放诊断执行的主记录: + +| 字段 | 说明 | +|---|---| +| `run_id` | 运行 ID,格式为 `run-` + UUID | +| `session_id` | 所属 `chat_session.session_id` | +| `query` | 本次 Chat 问题或 AIOps 告警摘要 | +| `status` | 本次执行状态 | | `agent_flow` | `CHAT` / `AI_OPS` | -| `answer` | 最终答复或告警报告 | -| `self_evaluation` | rule/verifier/aiops 自评估容器 | -| `feedback` | 用户反馈 | +| `answer` | 本次运行最终答复或告警报告 | +| `self_evaluation` | 本次运行的 rule/verifier/aiops 自评估容器 | +| `feedback` | 本次运行的用户反馈 | + +同一个 `sessionId` 可以有多个 `runId`。Trace、反馈、评测和案例沉淀都应优先使用 `runId`,避免多轮同 session 下的数据混合。 + +## 4. Trace 明细模型 ### agent_step -记录模型调用步骤。 - -用途: - -- 回放 Agent 推理过程。 -- 查看 Planner / Executor / Verifier 的输入输出摘要。 -- 统计 step count、duration、token count。 +`agent_step` 记录模型调用步骤。新写入同时保留 `session_id` 和 `run_id`,其中 `run_id` 是回放边界。Trace 页面和评测应先按 `run_id` 隔离取数,展示顺序以 Trace API 返回顺序为准。 ### tool_invocation -记录工具调用事实。 - -用途: - -- 给 Trace API 展示证据。 -- 给 Gatekeeper 提供 `retrieval_details.evidence_refs` 引用验真源。 -- 给 Verifier 构造 `tool_trace_summary` 审计导航。 -- 给 `EvaluationService` 计算 evidence score。 -- 给 RAG eval 和人工排查提供检索细节。 - -`retrieval_details.evidence_refs` 是当前 Chat 证据链路的关键字段: +`tool_invocation` 记录显式工具调用事实。`retrieval_details.evidence_refs` 是 Chat 证据链路的关键字段: ```json { @@ -149,83 +137,28 @@ erDiagram } ``` -字段边界: - -| 字段 | 说明 | -|---|---| -| `evidence_status` | 工具证据状态,例如 `supported`、`no_evidence`、`deduped`、`failed` | -| `evidence_refs[].raw_path` | Executor 可引用的稳定路径,例如 `$.logs[0]`、`$.alerts[0]`、`$.evidence_blocks[0]`、`$.no_evidence` | -| `evidence_refs[].text` | 系统抽取的最小证据文本,Gatekeeper 用它核对 `evidence_excerpt` | - `$.no_evidence` 只表示“本次工具查询未检索到匹配证据”,不能被解释为“问题不存在”或“根因已排除”。 -## 4. 知识库模型 - -### api_document - -MySQL 中的文档元数据表。 - -职责: - -- 管理上传文件。 -- 保存 file hash,用于去重。 -- 记录索引状态和 chunk 数量。 -- 保存 frontmatter JSON。 - -### knowledge_domain - -领域级元数据。 - -职责: - -- 按 category 聚合文档。 -- 存储领域描述。 -- 存储 `when_to_retrieve`,辅助 Planner/Executor 判断什么时候检索该领域。 - -### Milvus/Zilliz metadata - -向量 collection 中每个 chunk 的 metadata 主要包括: - -```text -docId -_source -chunkIndex -totalChunks -title -breadcrumb -category -``` - -这些字段支撑: - -- category filter。 -- source 展示。 -- breadcrumb 上下文。 -- docId 删除和重建索引。 -- evidence block 构造。 - ## 5. 反馈沉淀模型 -### case_library - -`useful` 反馈会触发 `CaseLibraryService.createFromSession`。 +`useful` 反馈会触发 `CaseLibraryService.createFromRun`。 当前自动映射: | 字段 | 来源 | |---|---| | `case_id` | UUID | -| `diagnosis_id` | `diagnosis_session.session_id` | +| `diagnosis_id` | 新数据为 `diagnosis_run.run_id`;历史数据可能为 `diagnosis_session.session_id` | | `source_type` | `AUTO` | | `fault_category` | 当前默认 `GENERAL` | -| `title` | session query 前 100 字符 | -| `root_cause` | session answer | -| `solution` | session answer | +| `title` | run query 前 100 字符 | +| `root_cause` | run answer | +| `solution` | run answer | | `created_by` | `system` | ## 6. self_evaluation 结构 -`diagnosis_session.self_evaluation` 是 JSON 容器: +`diagnosis_run.self_evaluation` 是运行级 JSON 容器: ```json { @@ -235,78 +168,21 @@ category } ``` -边界: +Chat 通常写入 `rule_evaluation` 和 `verifier_evaluation`;AIOps 写入 `aiops_rule_evaluation`。 -- `rule_evaluation` 评估证据收集充分度。 -- `verifier_evaluation` 评估 Chat 结构化 claims 是否能由已验真证据推出,并保存 Gatekeeper、Verifier、Composer 的审计数据。 -- `aiops_rule_evaluation` 评估 AIOps 报告是否聚焦告警并使用证据。 - -当前 `verifier_evaluation` 关键结构: - -```json -{ - "verdict": "PASS", - "groundedness_score": 1.0, - "critical_fact_count": 1, - "claim_checks": [], - "facts_checked": [], - "rationale": "...", - "round": 1, - "traceability_version": "v1", - "executor_output_parse_status": {}, - "executor_structured_output": {}, - "gatekeeper_result": {}, - "composer_output": {}, - "tool_trace_summary": [] -} -``` - -必要审计字段: - -| 字段 | 说明 | -|---|---| -| `executor_output_parse_status` | Executor 输出是否能解析为 `executor_evidence_v2` | -| `executor_structured_output` | Executor 结构化 claims、hypotheses、recommended_actions、missing_info | -| `gatekeeper_result` | 引用真实性校验结果,包括 rule set version、checked bindings、failed rules、warnings、errors | -| `composer_output` | Composer 最终表达及解析状态 | -| `tool_trace_summary` | Verifier 调用时使用的工具调用导航索引,不是唯一证据源 | - -## 7. 数据写入时序 - -```mermaid -sequenceDiagram - autonumber - participant API as API - participant Svc as ChatService/AiOpsService - participant Session as diagnosis_session - participant Agent as Agent - participant Step as agent_step - participant Tool as tool_invocation - participant Eval as self_evaluation - participant Feedback as case_library - - API->>Svc: request - Svc->>Session: create/update RUNNING - Agent->>Step: before/after model - Agent->>Tool: tool call record - Svc->>Session: SUCCESS/FAILED + answer - Svc->>Eval: merge evaluation - API->>Svc: feedback useful - Svc->>Feedback: create case -``` - -## 8. 当前边界和后续 +## 7. 当前边界和后续 当前边界: -- `agent_step.session_id` 和 `tool_invocation.session_id` 通过 sessionId 关联,不强制外键。 -- `tool_invocation.step_id` 可为空。 -- Milvus chunk 与 `api_document` 通过 metadata.docId 逻辑关联。 -- `case_library` 与 session 通过 `diagnosis_id=session_id` 关联。 +- `chat_session` 只存会话元数据,不存完整正文历史。 +- `diagnosis_run` 存一次运行的长期审计状态。 +- `agent_step.run_id` 和 `tool_invocation.run_id` 是 Trace、Verifier、Eval 的运行边界。 +- 当前实现主要使用逻辑关联,不依赖数据库外键。 +- `case_library.diagnosis_id` 是过渡字段,新值按 `run_id` 解释,旧值可能按 `session_id` 解释。 +- `diagnosis_session` 只作为历史兼容和回滚表保留。 后续可增强: -1. 增加 run id,支持同 session 多次独立诊断。 -2. 强化 `tool_invocation.step_id` 关联。 -3. 将 Gatekeeper 规则配置化时的规则元数据保存为可审计版本。 -4. 将 `case_library` 的 rootCause/solution 从完整 answer 中结构化抽取。 +1. 强化 `tool_invocation.step_id` 关联。 +2. 将 Gatekeeper 规则配置化时的规则元数据保存为可审计版本。 +3. 将 `case_library` 的 rootCause/solution 从完整 answer 中结构化抽取。 diff --git a/mvp/architecture/executor-evidence-pipeline-refactor.md b/mvp/architecture/executor-evidence-pipeline-refactor.md index 7b1a2e4..792e0b4 100644 --- a/mvp/architecture/executor-evidence-pipeline-refactor.md +++ b/mvp/architecture/executor-evidence-pipeline-refactor.md @@ -391,7 +391,7 @@ Composer 位于 Verifier 之后,输入是 ChatService 过滤后的允许表达 ## 7. Trace Persistence -`diagnosis_session.self_evaluation.verifier_evaluation` 持久化: +`diagnosis_run.self_evaluation.verifier_evaluation` 持久化: ```json { diff --git a/mvp/architecture/feedback-architecture.md b/mvp/architecture/feedback-architecture.md index 21df1d7..6353ea2 100644 --- a/mvp/architecture/feedback-architecture.md +++ b/mvp/architecture/feedback-architecture.md @@ -1,6 +1,6 @@ # 反馈与自评估架构 -**更新日期**:2026-07-08 +**更新日期**:2026-07-10 **状态**:当前可运行架构 **参考历史文档**:`archive/2026-07-05-legacy/confidence-feedback.md` @@ -8,8 +8,8 @@ 反馈架构包含两条闭环: -1. 系统自评估:基于工具调用、Gatekeeper、Verifier、Composer、AIOps 规则检查,写入 `diagnosis_session.self_evaluation`。 -2. 用户反馈:用户标记 `useful` 或 `not_useful`,写入 `diagnosis_session.feedback`,其中 `useful` 会沉淀案例。 +1. 系统自评估:基于当前 run 的工具调用、Gatekeeper、Verifier、Composer、AIOps 规则检查,写入 `diagnosis_run.self_evaluation`。 +2. 用户反馈:用户标记 `useful` 或 `not_useful`,优先写入 `diagnosis_run.feedback`,其中 `useful` 会沉淀案例。 当前重要边界: @@ -21,7 +21,7 @@ ```mermaid flowchart TD - Answer["Chat / AIOps final answer"] --> Session["diagnosis_session.answer"] + Answer["Chat / AIOps final answer"] --> Run["diagnosis_run.answer"] subgraph SelfEval["Self evaluation"] Invocation["tool_invocation"] --> RuleEval["EvaluationService: rule_evaluation"] @@ -40,24 +40,24 @@ flowchart TD RuleEval --> Merge["SelfEvaluationMergeService"] VerifierEval --> Merge AiOpsEval --> Merge - Merge --> SelfJson["diagnosis_session.self_evaluation"] + Merge --> SelfJson["diagnosis_run.self_evaluation"] subgraph UserFeedback["User feedback"] UI["Feedback bar"] --> API["POST /api/feedback"] API --> FeedbackService["FeedbackService"] - FeedbackService --> FeedbackField["diagnosis_session.feedback"] + FeedbackService --> FeedbackField["diagnosis_run.feedback"] FeedbackService --> Useful{"feedback == useful?"} - Useful -->|yes| CaseService["CaseLibraryService.createFromSession"] + Useful -->|yes| CaseService["CaseLibraryService.createFromRun"] CaseService --> Case["case_library"] Useful -->|no| BadCase["Bad case by feedback=not_useful"] end - Session --> UI + Run --> UI ``` ## 3. self_evaluation JSON -`SelfEvaluationMergeService` 统一维护 `diagnosis_session.self_evaluation`。 +`SelfEvaluationMergeService` 统一维护当前运行的 `diagnosis_run.self_evaluation`。历史兼容数据可能仍存在于 `diagnosis_session.self_evaluation`,但新 Chat/AIOps 执行不再写旧表。 当前结构: @@ -149,7 +149,7 @@ flowchart LR Composer --> ComposerOutput["composer_output"] Output --> Merge["SelfEvaluationMergeService.mergeVerifierEvaluation"] ComposerOutput --> Merge - Merge --> Session["diagnosis_session.self_evaluation.verifier_evaluation"] + Merge --> Run["diagnosis_run.self_evaluation.verifier_evaluation"] ``` Verifier 输出: @@ -198,6 +198,7 @@ POST /api/feedback Content-Type: application/json { + "runId": "run-xxx", "sessionId": "xxx", "feedback": "useful" | "not_useful" } @@ -209,6 +210,8 @@ Content-Type: application/json { "success": true, "message": "反馈已记录", + "runId": "run-xxx", + "fallbackToLatestRun": false, "caseId": "uuid 或 null" } ``` @@ -217,10 +220,16 @@ Content-Type: application/json | feedback | 行为 | |---|---| -| `useful` | 写入 `DiagnosisSession.feedback`,调用 `CaseLibraryService.createFromSession` | -| `not_useful` | 写入 `DiagnosisSession.feedback`,不改变 session status | +| `useful` | 写入 `DiagnosisRun.feedback`,调用 `CaseLibraryService.createFromRun` | +| `not_useful` | 写入 `DiagnosisRun.feedback`,不改变 run status | | 其他值 | 返回 HTTP 400 | +兼容行为: + +- 请求带 `runId` 时,后端验证 `runId` 属于 `sessionId`。 +- 请求缺少 `runId` 且存在 run-backed 数据时,后端绑定 latest run,并返回 `fallbackToLatestRun=true` 和实际 `runId`。 +- 仅当没有 `diagnosis_run` 但存在历史 `diagnosis_session` 时,才使用历史 fallback;该路径不声明 latest-run fallback。 + ## 8. 案例沉淀 `useful` 反馈会生成或复用 `case_library` 记录。 @@ -230,7 +239,7 @@ Content-Type: application/json | CaseLibrary 字段 | 来源 | |---|---| | `caseId` | UUID | -| `diagnosisId` | `DiagnosisSession.sessionId` | +| `diagnosisId` | 新数据为 `DiagnosisRun.runId`;历史数据可能为 `DiagnosisSession.sessionId` | | `sourceType` | `AUTO` | | `faultCategory` | 当前固定为 `GENERAL` | | `title` | `query` 前 100 字符 | @@ -241,7 +250,7 @@ Content-Type: application/json 幂等性: ```text -case_library.diagnosisId == sessionId +case_library.diagnosisId == runId -> existing case: return existing -> missing case: create new ``` @@ -260,9 +269,9 @@ Trace API 会展示: | 视角 | 数据来源 | |---|---| -| 执行是否成功 | `diagnosis_session.status` | +| 执行是否成功 | `diagnosis_run.status` | | 证据是否充分 | `self_evaluation.rule_evaluation` / `verifier_evaluation` | -| 用户是否认可 | `diagnosis_session.feedback` | +| 用户是否认可 | `diagnosis_run.feedback` | ## 10. 后续增强 diff --git a/mvp/architecture/harness-quality-gates.md b/mvp/architecture/harness-quality-gates.md index 2e075ab..d228a26 100644 --- a/mvp/architecture/harness-quality-gates.md +++ b/mvp/architecture/harness-quality-gates.md @@ -36,7 +36,7 @@ flowchart TB Tools --> Invocation["tool_invocation"] Agent --> StepHook["AgentLoggingHook"] StepHook --> Step["agent_step"] - Agent --> Session["diagnosis_session"] + Agent --> Run["diagnosis_run"] Invocation --> EvidenceRefs["retrieval_details.evidence_refs"] EvidenceRefs --> Gatekeeper["ExecutorGatekeeperService"] @@ -49,7 +49,7 @@ flowchart TB Invocation --> AiOpsRule["AiOpsRuleEvaluationService"] AiOpsRule --> AiOpsEval["self_evaluation.aiops_rule_evaluation"] - Session --> TraceAPI["DiagnosisTraceService"] + Run --> TraceAPI["DiagnosisTraceService"] Step --> TraceAPI Invocation --> TraceAPI SelfEval --> TraceAPI @@ -210,7 +210,7 @@ Verifier 不再逐字核验 excerpt 真伪;这由 Gatekeeper 完成。Verifier 结果写入: ```text -diagnosis_session.self_evaluation.verifier_evaluation +diagnosis_run.self_evaluation.verifier_evaluation ``` 其中同时持久化 `executor_structured_output`、`gatekeeper_result`、`tool_trace_summary`、`prompt_audit` 和 `composer_output`,用于 Trace 回放。 @@ -229,7 +229,7 @@ AIOps 当前不走 Chat Verifier,而是用 `AiOpsRuleEvaluationService` 做轻 结果写入: ```text -diagnosis_session.self_evaluation.aiops_rule_evaluation +diagnosis_run.self_evaluation.aiops_rule_evaluation ``` ## 8. Eval Baseline diff --git a/mvp/architecture/interview-one-pager.md b/mvp/architecture/interview-one-pager.md index bfca187..e7c0641 100644 --- a/mvp/architecture/interview-one-pager.md +++ b/mvp/architecture/interview-one-pager.md @@ -34,14 +34,15 @@ flowchart TB AiOpsFlow --> Trace Tools --> Trace - Trace --> Session["diagnosis_session"] + Trace --> ChatSession["chat_session"] + Trace --> Run["diagnosis_run"] Trace --> Step["agent_step"] Trace --> Invocation["tool_invocation"] Invocation --> Verifier["Verifier / Rule Evaluation"] Verifier --> SelfEval["self_evaluation"] - Session --> TraceAPI["GET /api/diagnosis/{sessionId}/trace"] + Run --> TraceAPI["GET /api/diagnosis/{sessionId}/trace?runId=..."] Step --> TraceAPI Invocation --> TraceAPI SelfEval --> TraceAPI @@ -61,15 +62,15 @@ Planner 负责拆解,Executor 只负责调用知识库、日志和指标工具 AIOps 告警入口走 Supervisor 调度 Planner/Executor: 如果请求里有 alert payload,系统会进入 PAYLOAD_TARGETED 模式,报告必须聚焦这个告警,而不是被当前环境中的其他活跃告警带偏。 -所有过程都会落到 diagnosis_session、agent_step、tool_invocation。 -所以我可以用一个 sessionId 回放:模型怎么规划、调了哪些工具、工具返回什么、Gatekeeper 怎么验真、Verifier 怎么判定、Composer 最后怎么表达、用户最后是否反馈有用。 +会话元数据会落到 chat_session,每次诊断运行会落到 diagnosis_run,步骤和工具明细通过 run_id 关联。 +所以我可以用 sessionId + runId 精确回放:模型怎么规划、调了哪些工具、工具返回什么、Gatekeeper 怎么验真、Verifier 怎么判定、Composer 最后怎么表达、用户最后是否反馈有用。 ``` ## 4. 五个亮点 | 亮点 | 怎么讲 | |---|---| -| 可追踪 Agent | 每次诊断都有 `sessionId`,Trace API 可以回放 session、step、tool | +| 可追踪 Agent | 每次诊断都有 `runId`,Trace API 可以回放 run、step、tool;同一 `sessionId` 可有多次独立 run | | 显式工具证据链 | `lookup_knowledge`、日志、指标都记录到 `tool_invocation` | | RAG 工程化 | L0 降级为 hint,Spring AI VectorStore 做主检索,SDK fallback 保底 | | 质量门禁 | Chat Gatekeeper 验引用、Verifier 判可推导、Composer 控表达,AIOps rule evaluation 控制告警聚焦 | diff --git a/mvp/architecture/session-trace-lifecycle.md b/mvp/architecture/session-trace-lifecycle.md index c48d798..cbb61c4 100644 --- a/mvp/architecture/session-trace-lifecycle.md +++ b/mvp/architecture/session-trace-lifecycle.md @@ -1,69 +1,68 @@ # 会话与 Trace 生命周期 -**更新日期**:2026-07-08 +**更新日期**:2026-07-10 **状态**:当前可运行架构 **参考历史文档**:`archive/2026-07-05-legacy/session-management.md` ## 1. 定位 -旧版会话设计以 Redis 会话为主,MySQL 作为可选长期沉淀。当前 MVP 的可追踪诊断已经转为 MySQL Trace 三表为主: +当前 MVP 把“会话态”和“运行态”拆开: ```text -diagnosis_session - -> agent_step - -> tool_invocation +chat_session(sessionId) + -> diagnosis_run(runId) + -> agent_step(runId) + -> tool_invocation(runId) ``` -因此本文描述的是当前可运行链路: - -- `sessionId` 是一次诊断和后续 trace/feedback 的关联键。 -- `diagnosis_session` 保存会话级状态、问题、答案、自评估和反馈。 -- `agent_step` 保存每个 Agent 模型调用。 -- `tool_invocation` 保存工具调用事实。 -- `DiagnosisTraceService` 聚合三类记录,形成可回放 trace。 +- `sessionId` 表示多轮会话目录和 Redis 上下文。 +- `runId` 表示一次可回放诊断执行。 +- `DiagnosisTraceService` 聚合一个 run 的主记录、步骤和工具调用,形成可回放 Trace。 +- `diagnosis_session` 只保留为历史兼容和回滚表。 ## 2. 生命周期总图 ```mermaid flowchart TD Start["request: chat / ai_ops"] --> Resolve["resolve sessionId"] - Resolve --> Create["create or reset diagnosis_session"] - Create --> Running["status = RUNNING"] + Resolve --> Session["ensure chat_session metadata"] + Session --> Run["create diagnosis_run(runId)"] + Run --> Running["run.status = RUNNING"] Running --> Agent["Agent workflow"] - Agent --> StepHook["AgentLoggingHook"] - StepHook --> Step["agent_step"] - Agent --> Tool["Evidence tools"] - Tool --> Invocation["tool_invocation"] + Agent --> Context["execution context(sessionId, runId)"] + Context --> StepHook["AgentLoggingHook"] + StepHook --> Step["agent_step(session_id, run_id)"] + Context --> Tool["Evidence tools"] + Tool --> Invocation["tool_invocation(session_id, run_id)"] Invocation --> Gatekeeper["Gatekeeper evidence validation"] Agent --> Final{"workflow result"} - Final -->|success| Success["status = SUCCESS, answer saved"] - Final -->|failed| Failed["status = FAILED"] + Final -->|success| Success["run.status = SUCCESS, answer saved"] + Final -->|failed| Failed["run.status = FAILED"] - Success --> Evaluation["self_evaluation merge"] + Success --> Evaluation["diagnosis_run.self_evaluation merge"] Failed --> Evaluation - Evaluation --> Trace["GET /api/diagnosis/{sessionId}/trace"] - Success --> Feedback["POST /api/feedback"] - Feedback --> Case["useful -> case_library"] + Evaluation --> Trace["GET /api/diagnosis/{sessionId}/trace?runId=..."] + Success --> Feedback["POST /api/feedback(sessionId, runId)"] + Feedback --> Case["useful -> case_library(run_id)"] ``` -## 3. sessionId 规则 +## 3. ID 规则 -| 链路 | sessionId 来源 | -|---|---| -| Chat | 如果请求带 sessionId,则复用;否则生成短 UUID | -| AIOps | 如果 payload 带 sessionId,则复用;否则生成 UUID | -| Trace | URL path 中的 `{sessionId}` | -| Feedback | request body 中的 `sessionId` | +| ID | 来源 | 含义 | +|---|---|---| +| `sessionId` | Chat request `Id`、AIOps payload `sessionId`,缺失时由服务生成 | 多轮会话目录和 Redis 上下文 | +| `runId` | 每次有效 Chat/AIOps 执行创建 | 一次诊断运行和 Trace 回放边界 | 设计含义: -- 同一个 `sessionId` 可以贯穿诊断、trace 查询和用户反馈。 -- 当前诊断开始时会重置当前 session 的运行态字段,例如 answer、duration、step/tool count。 -- `sessionId` 是业务关联键,不依赖数据库自增 ID 暴露给外部。 +- 同一个 `sessionId` 可以贯穿多轮 Chat。 +- 每次有效 Chat/AIOps 执行都会创建新的 `runId`。 +- Trace 和 Feedback 新客户端应传 `runId`;只传 `sessionId` 时兼容解析 latest run。 +- latest run 排序使用 `diagnosis_run.created_at DESC, id DESC`,不使用 `updated_at`。 -## 4. 状态流转 +## 4. 运行状态流转 ```mermaid stateDiagram-v2 @@ -77,14 +76,14 @@ stateDiagram-v2 字段边界: -| 字段 | 含义 | -|---|---| -| `status` | 执行状态:`PENDING` / `RUNNING` / `SUCCESS` / `FAILED` | -| `answer` | Agent 最终返回给用户的报告或答复 | -| `self_evaluation` | 系统自评估 JSON | -| `feedback` | 用户反馈:`useful` / `not_useful` / null | +| 字段 | 所属表 | 含义 | +|---|---|---| +| `status` | `diagnosis_run` | 单次运行执行状态 | +| `answer` | `diagnosis_run` | 本次运行最终报告或答复 | +| `self_evaluation` | `diagnosis_run` | 本次运行系统自评估 JSON | +| `feedback` | `diagnosis_run` | 本次运行用户反馈 | -`feedback` 不修改 `status`。一个执行成功但用户标记 `not_useful` 的 session,仍然应该是 `SUCCESS + feedback=not_useful`。 +`feedback` 不修改 `status`。一个执行成功但用户标记 `not_useful` 的 run,仍然应该是 `SUCCESS + feedback=not_useful`。 ## 5. agent_step 写入 @@ -93,93 +92,49 @@ stateDiagram-v2 ```mermaid sequenceDiagram autonumber - participant Agent as ReactAgent + participant Agent as Agent participant Hook as AgentLoggingHook participant DB as agent_step - Agent->>Hook: before_model(messages, sessionId) - Hook->>DB: insert step_index / agent_name / model_input - Agent-->>Agent: model call - Agent->>Hook: after_model(messages, sessionId) - Hook->>DB: update model_output / thought / has_tool_call / duration / token_count + Agent->>Hook: before_model(messages, sessionId, runId) + Hook->>DB: insert step(session_id, run_id, model_input, step_index) + Agent->>Hook: after_model(output, sessionId, runId) + Hook->>DB: update model_output, duration, token_count, has_tool_call ``` -当前记录: - -- `session_id` -- `step_index` -- `agent_name` -- `model_input` -- `model_output` -- `thought` -- `has_tool_call` -- `duration_ms` -- `token_count` +新写入必须带 `run_id`,同时保留 `session_id` 便于粗粒度排查。 ## 6. tool_invocation 写入 -工具调用记录真实工具事实,不记录模型猜测。 - -关键字段: +工具调用记录同样通过执行上下文拿到 `sessionId + runId`: ```text -session_id -step_id -tool_name -input_params -output_preview -output_length -retrieval_layer -l0_match_count -l1_match_count -retrieval_details - -> evidence_refs -relevance_level -dedup_reason -duration_ms -success -error_message +ToolInvocationRecorder + -> tool_invocation.session_id + -> tool_invocation.run_id + -> retrieval_details / evidence_refs ``` -对 `lookup_knowledge`,`retrieval_details` 会承载 L0/L1、领域、证据状态、去重等检索细节。对日志、指标和知识库工具,`retrieval_details.evidence_refs` 会记录 Gatekeeper 可核验的最小证据引用: - -```json -{ - "evidence_refs": [ - { - "raw_path": "$.logs[0]", - "text": "最小证据文本" - } - ] -} -``` - -当工具明确没有返回匹配证据时,可以记录 `raw_path=$.no_evidence`。该路径只表示“本次工具查询未检索到匹配证据”,不表示问题被排除。 +Verifier、Gatekeeper 和 EvaluationService 应按 `run_id` 读取工具调用,避免同一 session 的其他 run 参与评分或证据校验。 ## 7. Trace API 聚合 ```text GET /api/diagnosis/{sessionId}/trace +GET /api/diagnosis/{sessionId}/trace?runId=run-... ``` 聚合逻辑: ```text -diagnosis_session by sessionId - + agent_step ordered by step_index - + tool_invocation ordered by id +diagnosis_run by sessionId + runId + + chat_session metadata when available + + agent_step where run_id = runId, ordered by the Trace API + + tool_invocation where run_id = runId order by id -> DiagnosisTraceResponse ``` -Trace 视图回答的问题: - -- 这次诊断是否成功? -- 哪些 Agent 参与了? -- 每一步模型输入输出是什么摘要? -- 调用了哪些工具? -- 工具返回了什么证据? -- Gatekeeper / Verifier / Composer / AIOps rule 是否通过? -- 用户是否反馈有用? +当 `runId` 缺失时,Trace API 为兼容旧客户端解析最新 run,并在响应中返回 resolved `runId`。当 `runId` 属于其他 `sessionId` 时,API 必须拒绝,不能泄漏其他会话的 Trace。 ## 8. Chat 与 AIOps 差异 @@ -189,22 +144,17 @@ Trace 视图回答的问题: | 编排方式 | `SequentialAgent`: Planner -> Executor -> Gatekeeper -> Verifier -> Composer | `SupervisorAgent`: Planner + Executor | | 自评估 | `rule_evaluation` + `verifier_evaluation` | `aiops_rule_evaluation` | | 答案字段 | Chat 最终答复 | 告警分析报告 | -| payload | 用户自然语言 + history | alert payload 或 auto-discovery | +| runId 暴露 | `/api/chat` JSON response | `/api/ai_ops` SSE metadata message | ## 9. 清理与边界 -当前会话持久化边界: - -- MySQL Trace 记录是主要可回放来源。 -- Chat 历史仍可作为请求上下文传入 Agent,但不是本文档的主持久化模型。 -- Redis 主会话存储是历史设计,不作为当前架构事实。 -- `RetrievedDocTracker` 是 session 级运行时去重状态,诊断结束后清理。 +- Redis 会话历史用于多轮上下文,不是长期审计记录。 +- MySQL `diagnosis_run + agent_step + tool_invocation` 是主要可回放来源。 +- `chat_session.expires_at` 只是目录元数据;Redis 消息历史可独立过期。 +- `RetrievedDocTracker` 仍是 session 级运行时去重状态,诊断结束后清理。 ## 10. 后续增强 -可考虑: - 1. Trace API 增加更结构化的 `self_evaluation` 展示。 2. `agent_step` 与 `tool_invocation.step_id` 建立更严格关联。 -3. 对多轮同 session 诊断增加 run id,避免复用 session 时历史记录混杂。 -4. 为 Trace 增加导出能力,服务面试演示和回归分析。 +3. 旧 `diagnosis_session` 只读观察期结束后,再评估数据库层面的约束收紧或归档策略。 diff --git a/mvp/demo/README.md b/mvp/demo/README.md index 0743be5..f4757a8 100644 --- a/mvp/demo/README.md +++ b/mvp/demo/README.md @@ -67,10 +67,23 @@ Invoke-RestMethod ` -Body $body ``` +如果要继续手动查询同一次诊断运行,先保留响应中的 run id: + +```powershell +$chat = Invoke-RestMethod ` + -Method Post ` + -Uri "http://localhost:9900/api/chat" ` + -ContentType "application/json" ` + -Body $body + +$runId = $chat.data.runId +``` + 期望结果: - `data.success = true` - `data.sessionId = mvp-demo-payment-timeout-001` +- `data.runId` 为本次诊断运行的唯一 ID - `data.answer` 包含诊断答复 ## 4. 查询 Trace @@ -78,13 +91,15 @@ Invoke-RestMethod ` ```powershell Invoke-RestMethod ` -Method Get ` - -Uri "http://localhost:9900/api/diagnosis/$sessionId/trace" + -Uri "http://localhost:9900/api/diagnosis/$sessionId/trace?runId=$runId" ``` 期望结果: - `code = 200` +- `data.runId` 等于 `$runId` - `data.session.sessionId` 等于 Chat session id +- `data.run.runId` 等于 `$runId` - `data.steps` 包含 planner / executor / verifier 等步骤 - `data.toolInvocations` 包含 `lookup_knowledge`、`query_logs`、`query_metrics` 等证据工具 - `data.session.selfEvaluation` 包含 verifier 或 rule evaluation @@ -96,6 +111,7 @@ Invoke-RestMethod ` ```powershell $feedback = @{ sessionId = $sessionId + runId = $runId feedback = "useful" } | ConvertTo-Json @@ -109,7 +125,8 @@ Invoke-RestMethod ` 期望结果: - `success = true` -- 后续 Trace 中 `data.session.feedback = useful` +- `runId = $runId` +- 后续精确 Trace 中 `data.session.feedback = useful` - useful 反馈会尝试沉淀 `case_library` ## 6. AIOps 告警诊断 Demo @@ -135,19 +152,19 @@ Invoke-WebRequest ` 期望结果: -- SSE 首条包含 `session` 消息,sessionId 为 `mvp-demo-aiops-payment-cpu-001` +- SSE 首条是 `type=metadata` 的 `message` 事件,包含 sessionId `mvp-demo-aiops-payment-cpu-001` 和本次 AIOps `runId` - 后续流式输出包含 AIOps 告警分析报告 - 报告聚焦输入的 `HighCPUUsage/payment-service` -- 同一 session 的 Trace 中 `data.session.agentFlow = AI_OPS` +- 精确 Trace 中 `data.session.agentFlow = AI_OPS` - `data.session.answer` 包含最终告警报告 - `data.toolInvocations` 包含证据工具调用 -查询 AIOps Trace: +查询 AIOps Trace 时优先使用 SSE metadata 中的 runId: ```powershell Invoke-RestMethod ` -Method Get ` - -Uri "http://localhost:9900/api/diagnosis/$aiopsSessionId/trace" + -Uri "http://localhost:9900/api/diagnosis/$aiopsSessionId/trace?runId=$aiopsRunId" ``` ## 7. Demo 主线 @@ -155,7 +172,7 @@ Invoke-RestMethod ` Chat 主线: ```text -一个 session id +一个 session id + 一个 run id -> 用户问题 -> 多 Agent 执行 -> 证据工具 @@ -168,7 +185,7 @@ Chat 主线: AIOps 主线: ```text -一个 session id +一个 session id + 一个 run id -> 告警 payload -> AIOps Planner / Executor -> 证据工具 diff --git a/mvp/demo/aiops-alert-acceptance.md b/mvp/demo/aiops-alert-acceptance.md index 063b9d9..b925a2d 100644 --- a/mvp/demo/aiops-alert-acceptance.md +++ b/mvp/demo/aiops-alert-acceptance.md @@ -2,7 +2,7 @@ ## 1. 目标 -验证旧版 `/api/ai_ops` 入口可以作为可追踪的告警触发诊断入口,并且 payload 模式下报告聚焦输入告警。 +验证 `/api/ai_ops` 入口可以作为可追踪的告警触发诊断入口,并且 payload 模式下报告聚焦输入告警。 ## 2. 输入 @@ -25,14 +25,14 @@ ## 3. 验收标准 -1. SSE 流输出 `session` 消息,且包含请求中的 session id。 -2. AIOps 执行创建或更新 `diagnosis_session`,并写入 `agent_flow = AI_OPS`。 -3. 持久化的 session query 包含告警名、服务名、等级、时间范围和描述。 -4. 如果生成最终报告,`diagnosis_session.answer` 包含该报告。 -5. `GET /api/diagnosis/{sessionId}/trace` 返回 AIOps session、按顺序排列的 agent steps 和 tool invocations。 +1. SSE 流首条输出 `type=metadata` 的 `message` 事件,且包含请求中的 session id 和本次 AIOps run id。 +2. AIOps 执行创建 `diagnosis_run`,并写入 `agent_flow = AI_OPS`。 +3. 持久化的 run query 包含告警名、服务名、等级、时间范围和描述。 +4. 如果生成最终报告,`diagnosis_run.answer` 包含该报告。 +5. `GET /api/diagnosis/{sessionId}/trace?runId=...` 返回 AIOps run、按顺序排列的 agent steps 和 tool invocations。 6. payload 模式下,报告主线聚焦 `HighCPUUsage/payment-service`。 7. 其他活跃告警最多作为相关风险或上下文出现,不应展开成完整独立根因章节。 -8. `self_evaluation.aiops_rule_evaluation` 存在,并能反映报告完整性、payload 聚焦和证据工具覆盖情况。 +8. `diagnosis_run.self_evaluation.aiops_rule_evaluation` 存在,并能反映报告完整性、payload 聚焦和证据工具覆盖情况。 ## 4. 已知边界 diff --git a/mvp/demo/interview-walkthrough.md b/mvp/demo/interview-walkthrough.md index d7a9706..5bdc555 100644 --- a/mvp/demo/interview-walkthrough.md +++ b/mvp/demo/interview-walkthrough.md @@ -13,7 +13,7 @@ 关键主张不是“模型回答了一次”,而是: ```text -系统能展示用了什么证据、答案如何被检查、如何用 sessionId 回放整次诊断。 +系统能展示用了什么证据、答案如何被检查、如何用 sessionId + runId 精确回放这次诊断。 ``` ## 2. Demo 流程 @@ -23,7 +23,7 @@ 3. 打开 `mvp/demo/output/chat-response.json`。 4. 打开 `mvp/demo/output/trace-response.json`。 5. 指出证据工具和 verifier evaluation。 -6. 提交 feedback,并展示它挂在同一个 session 上。 +6. 提交 feedback,并展示它挂在当前 run 上。 7. 打开 `evidence-pipeline-scenarios.md`,说明 PASS / LOW_CONFID / REJECT / no-evidence 的固定回归矩阵。 ## 3. 命令 @@ -59,7 +59,7 @@ mvp/demo/output/chat-response.json 话术: ```text -这是用户看到的答案。这里的 sessionId 是稳定的,所以我后面可以追踪这一次回答是怎么来的。 +这是用户看到的答案。这里的 sessionId 是稳定的,同时响应里会返回 runId,所以我后面可以精确追踪这一次回答是怎么来的。 ``` ### 4.2 证据 Trace @@ -112,7 +112,7 @@ mvp/demo/output/feedback-response.json 话术: ```text -feedback 会挂在同一个 diagnosis session 上。 +feedback 会挂在当前 diagnosis run 上。 这让后续挖掘 useful case 或 not_useful bad case 成为可能。 ``` diff --git a/mvp/demo/payment-timeout-acceptance.md b/mvp/demo/payment-timeout-acceptance.md index bbf8cb7..0befb75 100644 --- a/mvp/demo/payment-timeout-acceptance.md +++ b/mvp/demo/payment-timeout-acceptance.md @@ -12,14 +12,16 @@ ## 3. 验收标准 -1. Chat 返回成功答复,且 session id 与请求一致。 -2. Trace API 返回 session 元数据、最终答案、按顺序排列的 agent steps 和 tool invocations。 +1. Chat 返回成功答复,且 session id 与请求一致,并返回本次诊断的 run id。 +2. Trace API 使用 `sessionId + runId` 返回会话元数据、运行摘要、最终答案、按顺序排列的 agent steps 和 tool invocations。 3. Trace 中有足够证据说明用了哪些工具,以及 verifier / self-evaluation 是否已持久化。 -4. 可以使用同一个 session id 提交反馈。 -5. 后续 Trace 查询能看到已持久化的 feedback 值。 +4. 可以使用同一个 session id 和本次 run id 提交反馈。 +5. 后续精确 Trace 查询能看到已持久化的 feedback 值。 ## 4. 需要检查的 Trace 字段 +- `data.runId` +- `data.run.runId` - `data.session.query` - `data.session.answer` - `data.session.selfEvaluation` diff --git a/mvp/demo/scripts/run-interview-demo-check.ps1 b/mvp/demo/scripts/run-interview-demo-check.ps1 index df11a41..be63919 100644 --- a/mvp/demo/scripts/run-interview-demo-check.ps1 +++ b/mvp/demo/scripts/run-interview-demo-check.ps1 @@ -78,9 +78,14 @@ $chat = Invoke-RestMethod @chatRequest $chatPath = Join-Path $OutputDir "chat-response.json" $chat | ConvertTo-Json -Depth 30 | Set-Content -Encoding UTF8 -Path $chatPath +$runId = $chat.data.runId +if (-not $runId) { + throw "Chat response did not include runId; exact trace verification cannot continue." +} + $traceRequest = @{ Method = "Get" - Uri = "$BaseUrl/api/diagnosis/$SessionId/trace" + Uri = "$BaseUrl/api/diagnosis/$SessionId/trace?runId=$([System.Uri]::EscapeDataString($runId))" } $trace = Invoke-RestMethod @traceRequest @@ -89,6 +94,7 @@ $trace | ConvertTo-Json -Depth 80 | Set-Content -Encoding UTF8 -Path $tracePath $feedbackBody = @{ sessionId = $SessionId + runId = $runId feedback = "useful" } | ConvertTo-Json @@ -136,6 +142,7 @@ $summaryPath = Join-Path $OutputDir "interview-demo-summary.json" $summary = [ordered]@{ sessionId = $SessionId + runId = $runId baseUrl = $BaseUrl chatSuccess = $chat.data.success verdict = $verdict diff --git a/mvp/demo/scripts/run-payment-timeout-demo.ps1 b/mvp/demo/scripts/run-payment-timeout-demo.ps1 index 008011a..c963fd5 100644 --- a/mvp/demo/scripts/run-payment-timeout-demo.ps1 +++ b/mvp/demo/scripts/run-payment-timeout-demo.ps1 @@ -26,15 +26,22 @@ $chat = Invoke-RestMethod ` $chat | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -Path "$OutputDir/chat-response.json" Write-Host "已保存 Chat 响应: $OutputDir/chat-response.json" +$runId = $chat.data.runId +if (-not $runId) { + throw "Chat 响应缺少 runId,无法查询精确 Trace。" +} +Write-Host "RunId: $runId" + $trace = Invoke-RestMethod ` -Method Get ` - -Uri "$BaseUrl/api/diagnosis/$SessionId/trace" + -Uri "$BaseUrl/api/diagnosis/$SessionId/trace?runId=$([System.Uri]::EscapeDataString($runId))" $trace | ConvertTo-Json -Depth 50 | Set-Content -Encoding UTF8 -Path "$OutputDir/trace-response.json" Write-Host "已保存 Trace 响应: $OutputDir/trace-response.json" $feedbackBody = @{ sessionId = $SessionId + runId = $runId feedback = "useful" } | ConvertTo-Json diff --git a/mvp/demo/ten-minute-interview-demo.md b/mvp/demo/ten-minute-interview-demo.md index 9b54887..92acf44 100644 --- a/mvp/demo/ten-minute-interview-demo.md +++ b/mvp/demo/ten-minute-interview-demo.md @@ -53,7 +53,7 @@ mvp/demo/output/interview-demo-summary.json ```text 这里我用固定 sessionId 跑一个支付接口超时问题。 -固定 sessionId 的好处是,后面 trace 和 feedback 都能关联到同一次诊断。 +固定 sessionId 的好处是保留多轮上下文;每次诊断还会返回 runId,后面 trace 和 feedback 都用这个 runId 精确关联到同一次运行。 ``` ## 3. 展示用户答案 @@ -68,6 +68,7 @@ mvp/demo/output/chat-response.json ```text data.sessionId +data.runId data.answer ``` @@ -76,7 +77,7 @@ data.answer ```text 这是用户看到的答案。 但这个项目的重点不是这段文字,而是这段文字是否有证据链。 -接下来我用同一个 sessionId 查 trace。 +接下来我用同一个 sessionId 加 runId 查 trace。 ``` ## 4. 展示 Trace @@ -182,7 +183,7 @@ caseId 现场话术: ```text -用户反馈 useful 会写回同一个 diagnosis_session。 +用户反馈 useful 会写回当前 diagnosis_run。 后端会把这次诊断自动沉淀到 case_library,后续可以做案例检索或 bad case 分析。 这里 status 和 feedback 是分开的: diff --git a/mvp/demo/trace-inspection-checklist.md b/mvp/demo/trace-inspection-checklist.md index 9cdd08a..5ebc134 100644 --- a/mvp/demo/trace-inspection-checklist.md +++ b/mvp/demo/trace-inspection-checklist.md @@ -6,14 +6,15 @@ | JSON path | 检查点 | 面试讲点 | |---|---|---| -| `data.session.sessionId` | 是否等于 `mvp-demo-payment-timeout-001` | 一个 session id 串起 chat、工具、verifier、feedback 和 trace | +| `data.runId` / `data.run.runId` | 是否等于 demo 响应中的 `runId` | `runId` 精确绑定这一次诊断运行 | +| `data.session.sessionId` | 是否等于 `mvp-demo-payment-timeout-001` | `sessionId` 保留多轮上下文,Trace 精确回放依赖 `runId` | | `data.session.query` | 是否包含支付超时问题 | Trace 记录了原始用户意图 | | `data.session.answer` | 是否包含最终诊断答案 | 最终答案没有脱离 Trace | | `data.session.selfEvaluation` | 是否包含 verifier 或 rule evaluation | 答案经过质量门,不只是模型原始输出 | | `data.session.selfEvaluation.verifier_evaluation.gatekeeper_result.rule_set_version` | 如果是 Chat V2 链路,是否记录 Gatekeeper 规则版本 | 安全规则可审计、可回归 | | `data.session.selfEvaluation.verifier_evaluation.prompt_audit.version` | 如果是 Chat V2 链路,是否记录 Prompt 审计版本 | Prompt 变更可解释、可回归 | | `data.session.selfEvaluation.verifier_evaluation.prompt_audit.prompts[*].version` | 是否记录 planner / executor / verifier / composer 版本 | 便于定位 Prompt 变更影响 | -| `data.session.feedback` | 提交反馈后是否变为 `useful` | 用户反馈挂在同一次诊断上 | +| `data.session.feedback` | 提交反馈后是否变为 `useful` | 用户反馈挂在当前 diagnosis run 上 | ## 2. Agent 步骤 @@ -48,10 +49,10 @@ ## 5. 好的结果长什么样 ```text -同一个 session id +同一个 session id + run id -> 最终答案 -> 持久化 agent steps -> 持久化 evidence tool calls -> verifier / self-evaluation --> feedback attached to the same session +-> feedback attached to the same run ``` diff --git a/mvp/tables/Agent步骤表-agent_step.md b/mvp/tables/Agent步骤表-agent_step.md index 2336f6c..51cde2c 100644 --- a/mvp/tables/Agent步骤表-agent_step.md +++ b/mvp/tables/Agent步骤表-agent_step.md @@ -5,14 +5,15 @@ ## 定位 -`agent_step` 记录一次诊断过程中每个 Agent 步骤的模型输入、输出、耗时和 Token 消耗。页面展示执行链路时应优先按 `step_index` 排序。 +`agent_step` 记录一次诊断运行中每个 Agent 步骤的模型输入、输出、耗时和 Token 消耗。`run_id` 是执行隔离边界;Trace 页面展示顺序以 Trace API 返回顺序为准。 ## 字段 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `id` | BIGINT | 是 | 自增主键 | -| `session_id` | VARCHAR(64) | 是 | 关联 `diagnosis_session.session_id` | +| `session_id` | VARCHAR(64) | 是 | 所属会话目录 ID,保留用于粗粒度过滤和兼容 | +| `run_id` | VARCHAR(64) | 否 | 所属 `diagnosis_run.run_id`;新执行应写入 | | `step_index` | INT | 是 | 步骤序号,从 0 开始 | | `agent_name` | VARCHAR(32) | 是 | Agent 名称,例如 planner、executor、verifier、composer | | `model_input` | TEXT | 否 | 模型输入摘要;`V006` 已从 JSON 改为 TEXT | @@ -27,15 +28,18 @@ | 索引 | 字段 | 用途 | |---|---|---| -| `idx_session_step` | `session_id, step_index` | Trace 页面按会话和步骤顺序查询 | +| `idx_session_step` | `session_id, step_index` | 历史兼容和粗粒度排查 | +| `idx_agent_step_run_step` | `run_id, step_index` | 按运行筛选步骤并辅助顺序查询 | | `idx_agent_name` | `agent_name` | 按 Agent 类型筛选 | ## 关系 -- `agent_step.session_id` 逻辑关联 `diagnosis_session.session_id`。 +- `agent_step.run_id` 逻辑关联 `diagnosis_run.run_id`。 +- `agent_step.session_id` 保留为 `chat_session.session_id` 的冗余关联,便于粗粒度过滤和兼容查询。 - `tool_invocation.step_id` 可关联 `agent_step.id`,但当前允许为空且不强制外键。 ## 注意点 -- 前端展示步骤时应按 `step_index` 排序,而不是按 `created_at` 或数据库返回顺序。 +- 前端展示步骤时应使用 Trace API 返回顺序;服务端会在同一 `run_id` 范围内整理步骤顺序。 +- 新 Trace、Verifier 和评测读路径应按 `run_id` 取数,避免同一 `sessionId` 多轮诊断混入。 - Verifier 应在 Executor 循环完成后出现;如果 `step_index` 中 Verifier 提前,通常意味着编排或记录顺序有问题。 diff --git a/mvp/tables/README.md b/mvp/tables/README.md index 92d2001..ac5f147 100644 --- a/mvp/tables/README.md +++ b/mvp/tables/README.md @@ -1,6 +1,6 @@ # MVP 数据表索引 -**更新日期**:2026-07-09 +**更新日期**:2026-07-10 **状态**:当前表文档入口 本目录保存当前 MVP 使用的数据表说明。详细结构以 Flyway migration 和实体类为准;本目录用于面试讲解、排查索引和快速理解数据流。 @@ -9,26 +9,32 @@ | 表 | 用途 | 文档 | |---|---|---| -| `diagnosis_session` | 会话级主记录,保存 query、状态、最终答案和自评估 | [诊断会话表-diagnosis_session.md](诊断会话表-diagnosis_session.md) | -| `agent_step` | Agent 步骤记录,按 `step_index` 回放执行链路 | [Agent步骤表-agent_step.md](Agent步骤表-agent_step.md) | -| `tool_invocation` | 工具调用记录,支撑 Trace、Verifier 和评测 | [工具调用表-tool_invocation.md](工具调用表-tool_invocation.md) | +| `chat_session` | 会话目录元数据,保存同一个 `sessionId` 的多轮会话状态快照 | [聊天会话表-chat_session.md](聊天会话表-chat_session.md) | +| `diagnosis_run` | 运行级主记录,保存一次 Chat/AIOps 诊断的 query、状态、答案、自评估和反馈 | [诊断运行表-diagnosis_run.md](诊断运行表-diagnosis_run.md) | +| `agent_step` | Agent 步骤记录,按 `run_id` 隔离回放执行链路 | [Agent步骤表-agent_step.md](Agent步骤表-agent_step.md) | +| `tool_invocation` | 工具调用记录,按 `run_id` 支撑 Trace、Verifier 和评测 | [工具调用表-tool_invocation.md](工具调用表-tool_invocation.md) | | `api_document` | 知识库文档元数据,和向量库 chunk 通过 `doc_id` 关联 | [文档元数据表-api_document.md](文档元数据表-api_document.md) | | `knowledge_domain` | 知识域元数据,支撑 RAG domain hint 和检索策略 | [知识域表-knowledge_domain.md](知识域表-knowledge_domain.md) | | `case_library` | 用户反馈沉淀出的高质量诊断案例 | [案例库表-case_library.md](案例库表-case_library.md) | +| `diagnosis_session` | 历史兼容和回滚表,新执行写入不再依赖它 | [诊断会话表-diagnosis_session.md](诊断会话表-diagnosis_session.md) | ## 已归档表 | 表 | 归档原因 | 文档 | |---|---|---| -| `diagnosis_record` | 已由 `V007` 删除,被 `diagnosis_session + agent_step + tool_invocation` 替代 | [archive/2026-07-09-doc-cleanup/旧诊断记录表-diagnosis_record.md](archive/2026-07-09-doc-cleanup/旧诊断记录表-diagnosis_record.md) | +| `diagnosis_record` | 已由 `V007` 删除,历史上被 `diagnosis_session + agent_step + tool_invocation` 替代;当前新模型是 `chat_session + diagnosis_run + trace detail` | [archive/2026-07-09-doc-cleanup/旧诊断记录表-diagnosis_record.md](archive/2026-07-09-doc-cleanup/旧诊断记录表-diagnosis_record.md) | ## 核心关系 ```text +chat_session.session_id + -> diagnosis_run.session_id + -> agent_step.run_id + -> tool_invocation.run_id + -> case_library.diagnosis_id (new AUTO cases use run_id) + diagnosis_session.session_id - -> agent_step.session_id - -> tool_invocation.session_id - -> case_library.diagnosis_id + -> historical compatibility / rollback only api_document.doc_id -> vector chunk metadata.docId / doc_id diff --git a/mvp/tables/工具调用表-tool_invocation.md b/mvp/tables/工具调用表-tool_invocation.md index b1475b9..adb1499 100644 --- a/mvp/tables/工具调用表-tool_invocation.md +++ b/mvp/tables/工具调用表-tool_invocation.md @@ -5,14 +5,15 @@ ## 定位 -`tool_invocation` 记录 Agent 显式调用工具的事实,包括工具名、入参、输出摘要、检索层级、证据引用和失败信息。它是 Trace、Verifier、评测和人工排查的共同数据源。 +`tool_invocation` 记录 Agent 在一次诊断运行中显式调用工具的事实,包括工具名、入参、输出摘要、检索层级、证据引用和失败信息。它是 Trace、Verifier、评测和人工排查的共同数据源。 ## 字段 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `id` | BIGINT | 是 | 自增主键 | -| `session_id` | VARCHAR(64) | 是 | 关联 `diagnosis_session.session_id` | +| `session_id` | VARCHAR(64) | 是 | 所属会话目录 ID,保留用于粗粒度过滤和兼容 | +| `run_id` | VARCHAR(64) | 否 | 所属 `diagnosis_run.run_id`;新执行应写入 | | `step_id` | BIGINT | 否 | 可关联 `agent_step.id` | | `tool_name` | VARCHAR(64) | 是 | 工具名称,例如 `lookup_knowledge`、日志查询、指标查询 | | `input_params` | JSON | 是 | 工具入参 | @@ -34,13 +35,15 @@ | 索引 | 字段 | 用途 | |---|---|---| -| `idx_session_id` | `session_id` | 按会话查询工具调用 | +| `idx_session_id` | `session_id` | 历史兼容和粗粒度排查 | +| `idx_tool_invocation_run_id` | `run_id, id` | Trace、Verifier、评测按运行查询工具调用 | | `idx_tool_name` | `tool_name` | 按工具类型排查 | | `idx_retrieval_layer` | `retrieval_layer` | 观察 RAG L0/L1 行为 | ## 关系 -- `tool_invocation.session_id` 逻辑关联 `diagnosis_session.session_id`。 +- `tool_invocation.run_id` 逻辑关联 `diagnosis_run.run_id`。 +- `tool_invocation.session_id` 保留为 `chat_session.session_id` 的冗余关联,便于粗粒度过滤和兼容查询。 - `tool_invocation.step_id` 可关联 `agent_step.id`,但当前不强制。 ## 关键 JSON diff --git a/mvp/tables/案例库表-case_library.md b/mvp/tables/案例库表-case_library.md index ed3315f..4641a9c 100644 --- a/mvp/tables/案例库表-case_library.md +++ b/mvp/tables/案例库表-case_library.md @@ -5,7 +5,7 @@ ## 定位 -`case_library` 保存高质量诊断案例,用于后续相似案例推荐和知识沉淀。当前自动沉淀路径来自 `useful` 用户反馈:系统把 `diagnosis_session` 中的 query 和 answer 映射为案例内容。 +`case_library` 保存高质量诊断案例,用于后续相似案例推荐和知识沉淀。当前自动沉淀路径来自 `useful` 用户反馈:新数据把 `diagnosis_run` 中的 query 和 answer 映射为案例内容。 ## 字段 @@ -13,7 +13,7 @@ |---|---|---|---| | `id` | BIGINT | 是 | 自增主键 | | `case_id` | VARCHAR(64) | 是 | 案例唯一 ID | -| `diagnosis_id` | VARCHAR(64) | 否 | 关联诊断会话;当前自动生成时存 `diagnosis_session.session_id` | +| `diagnosis_id` | VARCHAR(64) | 否 | 关联诊断来源;新自动生成时存 `diagnosis_run.run_id`,历史数据可能是 `diagnosis_session.session_id` | | `source_type` | VARCHAR(16) | 否 | 来源类型:`AUTO` 或 `MANUAL` | | `fault_category` | VARCHAR(32) | 否 | 故障类别,实体侧使用 `FaultCategory` | | `fault_source` | VARCHAR(128) | 否 | 故障源,例如服务、系统或省份 | @@ -35,16 +35,17 @@ | `idx_error_code` | `error_code` | 按错误码精确匹配 | | `idx_fault_source` | `fault_source` | 按故障源筛选 | | `idx_fault_target` | `fault_target(100)` | 按故障目标筛选 | -| `idx_diagnosis_id` | `diagnosis_id` | 追溯来源会话 | +| `idx_diagnosis_id` | `diagnosis_id` | 追溯来源运行或历史会话 | | `idx_reference_count` | `reference_count` | 推荐排序 | | `idx_created_at` | `created_at` | 时间排序 | ## 关系 -- `case_library.diagnosis_id` 当前逻辑关联 `diagnosis_session.session_id`,不是旧的 `diagnosis_record`。 +- `case_library.diagnosis_id` 是过渡字段:新自动案例逻辑关联 `diagnosis_run.run_id`,历史自动案例可能仍是 `diagnosis_session.session_id`。 - 人工录入案例可以不填写 `diagnosis_id`。 ## 注意点 - 旧文档里提到的 `diagnosis_record` 已被 `V007` 删除,不再是当前主模型。 +- 查询新自动案例时优先按 `run_id` 追溯;遇到旧值时再按历史 `session_id` 解释。 - 当前自动沉淀仍比较粗:`root_cause` 和 `solution` 都可能来自完整 answer。后续可从结构化结论中拆分根因、证据和修复建议。 diff --git a/mvp/tables/聊天会话表-chat_session.md b/mvp/tables/聊天会话表-chat_session.md new file mode 100644 index 0000000..38c36d3 --- /dev/null +++ b/mvp/tables/聊天会话表-chat_session.md @@ -0,0 +1,40 @@ +# 聊天会话表:chat_session + +**状态**:当前会话目录表 +**来源**:`V011__add_session_run_isolation.sql`、`ChatSession` + +## 定位 + +`chat_session` 保存多轮 Chat 会话的元数据,用于把同一个 `sessionId` 下的多次诊断运行组织在一起。它不保存完整对话历史;正文消息仍由 Redis `SessionContext.messageHistory` 管理。 + +## 字段 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `id` | BIGINT | 是 | 自增主键 | +| `session_id` | VARCHAR(64) | 是 | 会话目录 ID,外部 API 仍通过它定位会话 | +| `status` | VARCHAR(16) | 否 | `ACTIVE`、`EXPIRED`、`CLOSED` | +| `message_pair_count` | INT | 否 | Redis 会话中问答轮次数的快照 | +| `created_at` | DATETIME | 是 | 创建时间 | +| `last_active_at` | DATETIME | 否 | 最近活跃时间 | +| `expires_at` | DATETIME | 否 | 目录元数据,可为空;Redis 消息历史可独立过期 | + +## 索引 + +| 索引 | 字段 | 用途 | +|---|---|---| +| `session_id` unique | `session_id` | 会话目录唯一约束 | +| `idx_chat_session_last_active` | `last_active_at` | 最近会话列表和排查 | +| `idx_chat_session_status` | `status` | 按状态筛选 | +| `idx_chat_session_expires_at` | `expires_at` | 过期目录排查 | + +## 关系 + +- `diagnosis_run.session_id` 逻辑关联 `chat_session.session_id`。 +- 当前不强制数据库外键,服务层校验 run/session ownership。 +- 一个 `chat_session` 可以拥有多个 `diagnosis_run`。 + +## 注意点 + +- `chat_session` 是会话元数据,不是诊断执行记录。 +- 不要把 query、answer、self_evaluation、feedback 写入该表;这些属于 `diagnosis_run`。 diff --git a/mvp/tables/诊断会话表-diagnosis_session.md b/mvp/tables/诊断会话表-diagnosis_session.md index 46b4ed0..94d371f 100644 --- a/mvp/tables/诊断会话表-diagnosis_session.md +++ b/mvp/tables/诊断会话表-diagnosis_session.md @@ -1,18 +1,18 @@ # 诊断会话表:diagnosis_session -**状态**:当前主表 +**状态**:历史兼容和回滚表 **来源**:`V005__create_session_storage.sql`、`V008__add_answer_to_diagnosis_session.sql`、`DiagnosisSession` ## 定位 -`diagnosis_session` 是一次 Chat 或 AIOps 诊断的会话级主记录,负责保存用户问题、执行状态、最终答案、总体统计和自评估结果。 +`diagnosis_session` 是旧版 session 级诊断主记录。`V011` 之后,新 Chat/AIOps 执行的运行态写入已经切到 `chat_session + diagnosis_run`;本表保留用于历史兼容、迁移回填和回滚比较。 ## 字段 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `id` | BIGINT | 是 | 自增主键 | -| `session_id` | VARCHAR(64) | 是 | 会话唯一 ID,Trace API 和反馈接口使用它 | +| `session_id` | VARCHAR(64) | 是 | 旧版会话唯一 ID,也是兼容 run 回填来源 | | `query` | TEXT | 是 | 用户原始问题或 AIOps 输入摘要 | | `status` | VARCHAR(16) | 否 | `PENDING`、`RUNNING`、`SUCCESS`、`FAILED` | | `agent_flow` | VARCHAR(32) | 否 | `CHAT` 或 `AI_OPS` | @@ -37,11 +37,12 @@ ## 关系 -- `agent_step.session_id` 逻辑关联 `diagnosis_session.session_id`。 -- `tool_invocation.session_id` 逻辑关联 `diagnosis_session.session_id`。 -- `case_library.diagnosis_id` 在自动生成案例时保存 `diagnosis_session.session_id`。 +- 迁移时每条 `diagnosis_session` 会生成一条兼容 `diagnosis_run`。 +- 历史 `agent_step.run_id` 和 `tool_invocation.run_id` 会尽量从兼容 `diagnosis_run` 回填。 +- 历史自动案例可能仍使用 `case_library.diagnosis_id = diagnosis_session.session_id`。 ## 注意点 -- 当前没有数据库外键,Trace 聚合依赖 `session_id`。 -- `self_evaluation` 是扩展容器,里面可能包含 `rule_evaluation`、`verifier_evaluation`、`aiops_rule_evaluation`。 +- 新执行不应再把 query、answer、self_evaluation、feedback、统计计数写入本表。 +- 新 Trace 聚合优先读取 `diagnosis_run + agent_step.run_id + tool_invocation.run_id`。 +- 历史 fallback 仅在没有 run-backed 数据时读取本表。 diff --git a/mvp/tables/诊断运行表-diagnosis_run.md b/mvp/tables/诊断运行表-diagnosis_run.md new file mode 100644 index 0000000..fb462b5 --- /dev/null +++ b/mvp/tables/诊断运行表-diagnosis_run.md @@ -0,0 +1,51 @@ +# 诊断运行表:diagnosis_run + +**状态**:当前诊断运行主表 +**来源**:`V011__add_session_run_isolation.sql`、`DiagnosisRun` + +## 定位 + +`diagnosis_run` 表示一次可回放的 Chat 或 AIOps 诊断执行。`run_id` 是运行级边界,Trace、反馈、自评估、案例沉淀和统计都应优先按 `run_id` 绑定。 + +## 字段 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `id` | BIGINT | 是 | 自增主键 | +| `run_id` | VARCHAR(64) | 是 | 运行唯一 ID,格式为 `run-` + UUID | +| `session_id` | VARCHAR(64) | 是 | 所属 `chat_session.session_id` | +| `query` | TEXT | 是 | 本次 Chat 问题或 AIOps 告警摘要 | +| `status` | VARCHAR(16) | 否 | `PENDING`、`RUNNING`、`SUCCESS`、`FAILED` | +| `agent_flow` | VARCHAR(32) | 否 | `CHAT` 或 `AI_OPS` | +| `answer` | LONGTEXT | 否 | 本次运行的最终答复或告警报告 | +| `self_evaluation` | JSON | 否 | 本次运行的 rule、verifier、aiops 自评估容器 | +| `feedback` | VARCHAR(16) | 否 | 本次运行的用户反馈 | +| `total_duration_ms` | INT | 否 | 本次运行总耗时 | +| `total_token_count` | INT | 否 | 本次运行 Token 消耗 | +| `step_count` | INT | 否 | 本次运行的 Agent 步骤数 | +| `tool_call_count` | INT | 否 | 本次运行的工具调用数 | +| `created_at` | DATETIME | 是 | 创建时间 | +| `updated_at` | DATETIME | 是 | 更新时间 | + +## 索引 + +| 索引 | 字段 | 用途 | +|---|---|---| +| `run_id` unique | `run_id` | 运行唯一约束 | +| `idx_diagnosis_run_session_created` | `session_id, created_at, id` | session 下最新运行解析和运行列表 | +| `idx_diagnosis_run_session_run` | `session_id, run_id` | exact trace / feedback ownership 校验 | +| `idx_diagnosis_run_status` | `status` | 状态筛选 | +| `idx_diagnosis_run_agent_flow` | `agent_flow` | 区分 Chat / AIOps | + +## 关系 + +- `diagnosis_run.session_id` 逻辑关联 `chat_session.session_id`。 +- `agent_step.run_id` 逻辑关联 `diagnosis_run.run_id`。 +- `tool_invocation.run_id` 逻辑关联 `diagnosis_run.run_id`。 +- 新的自动案例沉淀使用 `case_library.diagnosis_id = diagnosis_run.run_id`。 + +## 注意点 + +- `GET /api/diagnosis/{sessionId}/trace` 未带 `runId` 时只为兼容解析 latest run;新 demo 和新客户端应传 `runId`。 +- latest run 排序使用 `created_at DESC, id DESC`,避免 feedback 或自评估更新 `updated_at` 后改变回放目标。 +- 历史 `diagnosis_session` 会被迁移成兼容 run,但旧混合数据不能被还原成真实多轮边界。 diff --git a/openspec/changes/session-run-trace-isolation/phase-6-evidence.md b/openspec/changes/session-run-trace-isolation/phase-6-evidence.md new file mode 100644 index 0000000..6624b35 --- /dev/null +++ b/openspec/changes/session-run-trace-isolation/phase-6-evidence.md @@ -0,0 +1,253 @@ +# Phase 6 Evidence: Demo, Trace UI, Documentation, and Verification + +## Scope + +Phase 6 completed the run-aware demo/documentation surface and final verification for `session-run-trace-isolation`. + +Implemented: + +- Demo scripts read Chat `runId`, query exact Trace with `?runId=...`, and submit feedback with `runId`. +- Trace UI accepts `?sessionId=...&runId=...` and calls the exact Trace API when `runId` is present. +- Chat UI remembers the latest run target and links to Trace Workbench with `sessionId + runId` when available. +- MVP table and architecture docs now describe `chat_session`, `diagnosis_run`, `agent_step.run_id`, `tool_invocation.run_id`, and transitional `case_library.diagnosis_id` semantics. + +## Static Verification + +Commands: + +```powershell +node --check src\main\resources\static\app.js +node --check src\main\resources\static\trace.js + +$scripts = @( + 'mvp\demo\scripts\run-payment-timeout-demo.ps1', + 'mvp\demo\scripts\run-interview-demo-check.ps1' +) +foreach ($script in $scripts) { + [scriptblock]::Create((Get-Content -Raw -Encoding UTF8 $script)) | Out-Null + Write-Host "Parsed $script" +} + +openspec validate session-run-trace-isolation --strict +``` + +Result: + +- JavaScript syntax: passed. +- PowerShell script parsing: passed. +- OpenSpec strict validation: passed. + +## Focused Tests + +Command: + +```powershell +mvn -q "-Dtest=ChatControllerTest,DiagnosisTraceServiceTest,FeedbackControllerTest,FeedbackServiceTest,AiOpsServiceTest" test +``` + +Result: passed. + +## Final Same-Session Multi-Turn E2E + +Startup command: + +```powershell +mvn spring-boot:run -Dspring-boot.run.profiles=mvp-demo +``` + +Startup log: + +- `target/e2e/phase6-mvn-20260710-211831.out.log` +- `target/e2e/phase6-mvn-20260710-211831.err.log` + +Application readiness: + +- `Started Main in 15.501 seconds` +- `ReadinessState changed to ACCEPTING_TRAFFIC` + +E2E session: + +- `sessionId`: `e2e-phase6-chat-codex-20260710-2120` +- `run1`: `run-e2a97696-4398-4abc-90e4-28f45c838f92` +- `run2`: `run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172` + +Artifacts: + +- `target/e2e/phase6-chat1-20260710-2120.json` +- `target/e2e/phase6-chat2-20260710-2120.json` +- `target/e2e/phase6-trace-run1-20260710-2120.json` +- `target/e2e/phase6-trace-run2-20260710-2120.json` +- `target/e2e/phase6-trace-latest-20260710-2120.json` +- `target/e2e/phase6-e2e-summary-20260710-2120.json` + +Observed: + +```json +{ + "sessionId": "e2e-phase6-chat-codex-20260710-2120", + "run1": "run-e2a97696-4398-4abc-90e4-28f45c838f92", + "run2": "run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172", + "chat1Success": true, + "chat2Success": true, + "trace1RunId": "run-e2a97696-4398-4abc-90e4-28f45c838f92", + "trace2RunId": "run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172", + "latestRunId": "run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172", + "trace1Steps": 10, + "trace2Steps": 9, + "trace1Tools": 14, + "trace2Tools": 8 +} +``` + +Interpretation: + +- Two Chat requests reused the same `sessionId`. +- Each Chat request returned a distinct `runId`. +- Exact trace for run1 returned run1 only. +- Exact trace for run2 returned run2 only. +- Session-only Trace latest fallback returned run2. + +## Database Inspection + +Tool: `scripts/query_mysql.py` + +`diagnosis_run`: + +```text +run_id | session_id | status | agent_flow | step_count | tool_call_count | has_answer +------------------------------------------------------------------------------------------------------------------------------------------------- +run-e2a97696-4398-4abc-90e4-28f45c838f92 | e2e-phase6-chat-codex-20260710-2120 | SUCCESS | CHAT | 10 | 14 | 1 +run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172 | e2e-phase6-chat-codex-20260710-2120 | SUCCESS | CHAT | 9 | 8 | 1 +``` + +`agent_step` grouped by `run_id`: + +```text +run_id | step_rows | min_step | max_step +-------------------------------------------------------------------------- +run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172 | 9 | 0 | 5 +run-e2a97696-4398-4abc-90e4-28f45c838f92 | 10 | 0 | 6 +``` + +`tool_invocation` grouped by `run_id`: + +```text +run_id | tool_rows +---------------------------------------------------- +run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172 | 8 +run-e2a97696-4398-4abc-90e4-28f45c838f92 | 14 +``` + +Mixed row check: + +```text +mixed_rows +---------- +0 +0 +``` + +`chat_session` metadata: + +```text +session_id | status | message_pair_count | last_active_at +--------------------------------------------------------------------------------------- +e2e-phase6-chat-codex-20260710-2120 | ACTIVE | 2 | 2026-07-10 21:23:59 +``` + +`GET /api/chat/session/e2e-phase6-chat-codex-20260710-2120` returned `messagePairCount=2`. + +Interpretation: + +- Run table has exactly two successful Chat runs for the E2E session. +- Step/tool counts match the exact Trace API responses. +- No `agent_step` or `tool_invocation` rows for this session have NULL or unexpected `run_id`. +- `chat_session` metadata confirms multi-turn context continuity at two message pairs. + +## Log Inspection + +Searched: + +- `target/e2e/phase6-mvn-20260710-211831.out.log` +- `logs/application.log` +- `logs/chat.log` + +Patterns: + +- `e2e-phase6-chat-codex-20260710-2120` +- `run-e2a97696-4398-4abc-90e4-28f45c838f92` +- `run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172` + +Result: + +- Matching startup, Chat execution, run persistence, and trace lookup log lines were present in Maven output and `logs/application.log`. + +## Baseline Drift + +Focused baseline command: + +```powershell +mvn -q "-Dtest=DiagnosisTraceEvaluatorTest,DiagnosisEvalBaselineDiffTest" test +``` + +Broader baseline regression command from `mvp/eval/README.md`: + +```powershell +mvn -q "-Dtest=DiagnosisTraceEvaluatorTest,DiagnosisEvalBaselineDiffTest,ExecutorGatekeeperServiceTest,VerifierInputHookTest,ChatServiceSequentialAgentTest" test +``` + +Result: + +- Both commands passed. +- The baseline harness evaluates saved fixtures and does not depend on live DB/session tables. +- No baseline drift was observed. + +## Final Gate Checks + +Commands: + +```powershell +rg -n "diagnosisSessionRepository\.save|new DiagnosisSession|DiagnosisSession\.builder|setAnswer\(|setSelfEvaluation\(|setFeedback\(|createFromSession|persistFinalReport\(sessionId, finalReport|save\(session\)" src\main\java\com\superbiz\agent -g "*.java" + +rg -n "evaluate\(|evaluateRun\(|persistFinalReport\(|submitFeedback\(|createFromSession\(" src\main\java src\test\java -g "*.java" + +git diff --check -- . ':!devflow/index.md' +openspec validate session-run-trace-isolation --strict +``` + +Result: + +- New Chat write path calls `evaluationService.evaluateRun(...)` and writes `diagnosis_run`. +- New AIOps controller path calls `persistFinalReport(sessionId, runId, ...)` and writes `diagnosis_run`. +- Remaining `diagnosis_session` writes are legacy compatibility paths: + - `FeedbackService.submitLegacySessionFeedback(...)` + - `AiOpsService.persistLegacyFinalReport(...)` + - legacy `EvaluationService.evaluate(...)` +- `git diff --check`: passed after Markdown whitespace cleanup. +- OpenSpec strict validation: passed. + +## Documentation Review Follow-up + +After the final documentation review, the remaining demo helper docs were aligned with the run-aware contract: + +- `mvp/demo/trace-inspection-checklist.md` +- `mvp/demo/payment-timeout-acceptance.md` +- `mvp/demo/interview-walkthrough.md` +- `mvp/tables/Agent步骤表-agent_step.md` +- `mvp/tables/README.md` +- `mvp/architecture/data-model.md` +- `mvp/architecture/session-trace-lifecycle.md` + +The corrections remove session-only wording for Trace/Feedback and clarify that `run_id` is the execution isolation boundary while Trace API response order is the UI display contract. + +Follow-up gate after these documentation fixes: + +- `node --check src\main\resources\static\app.js`: passed. +- `node --check src\main\resources\static\trace.js`: passed. +- PowerShell demo script parsing: passed. +- `openspec validate session-run-trace-isolation --strict`: passed. +- `git diff --check -- . ':!devflow/index.md'`: passed. + +## Notes + +- The AGENTS-required `codebase-retrieval` and LSP tools were not available in this session. Fallback verification used OpenSpec context, `rg`, targeted file reads, focused tests, E2E, DB inspection, and log inspection. diff --git a/openspec/changes/session-run-trace-isolation/tasks.md b/openspec/changes/session-run-trace-isolation/tasks.md index 7caca15..40b8cad 100644 --- a/openspec/changes/session-run-trace-isolation/tasks.md +++ b/openspec/changes/session-run-trace-isolation/tasks.md @@ -51,9 +51,9 @@ ## 6. Demo, Trace UI, Documentation, and Verification -- [ ] 6.1 Update demo scripts to read `runId` from Chat/AIOps responses and pass `?runId=...` to Trace API. -- [ ] 6.2 Update Trace UI to accept `?sessionId=...&runId=...` and query exact trace when `runId` is present. -- [ ] 6.3 Update MVP table and architecture docs for `chat_session`, `diagnosis_run`, `run_id`, and transitional `case_library.diagnosis_id` semantics. -- [ ] 6.4 Run final same-session multi-turn E2E using Maven startup if needed; collect DB evidence through `scripts/query_mysql.py` and inspect `logs/`. -- [ ] 6.5 Run or explicitly evaluate the relevant baseline diff command and document whether drift is expected or a regression. -- [ ] 6.6 Final gate: ensure all OpenSpec tasks are checked, no new writes depend on `diagnosis_session`, phase evidence is archived, final commit is created, and the change is ready for OpenSpec archive. +- [x] 6.1 Update demo scripts to read `runId` from Chat/AIOps responses and pass `?runId=...` to Trace API. +- [x] 6.2 Update Trace UI to accept `?sessionId=...&runId=...` and query exact trace when `runId` is present. +- [x] 6.3 Update MVP table and architecture docs for `chat_session`, `diagnosis_run`, `run_id`, and transitional `case_library.diagnosis_id` semantics. +- [x] 6.4 Run final same-session multi-turn E2E using Maven startup if needed; collect DB evidence through `scripts/query_mysql.py` and inspect `logs/`. +- [x] 6.5 Run or explicitly evaluate the relevant baseline diff command and document whether drift is expected or a regression. +- [x] 6.6 Final gate: ensure all OpenSpec tasks are checked, no new writes depend on `diagnosis_session`, phase evidence is archived, final commit is created, and the change is ready for OpenSpec archive. diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js index e394be4..e31ee3c 100644 --- a/src/main/resources/static/app.js +++ b/src/main/resources/static/app.js @@ -9,6 +9,7 @@ class SuperBizAgentApp { this.chatHistories = this.loadChatHistories(); // 所有历史对话 this.isCurrentChatFromHistory = false; // 标记当前对话是否是从历史记录加载的 this.lastTraceSessionId = this.loadLastTraceSessionId(); + this.lastTraceRunId = this.loadLastTraceRunId(); this.initializeElements(); this.bindEvents(); @@ -310,13 +311,14 @@ class SuperBizAgentApp { id: this.sessionId, title: title, messages: [...this.currentChatHistory], + lastRunId: this.sessionId === this.lastTraceSessionId ? this.lastTraceRunId : '', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; // 添加到历史记录列表的开头 this.chatHistories.unshift(chatHistory); - this.rememberTraceSessionId(this.sessionId); + this.rememberTraceTarget(this.sessionId, null); // 限制历史记录数量(最多保存50条) if (this.chatHistories.length > 50) { @@ -344,6 +346,9 @@ class SuperBizAgentApp { const history = this.chatHistories[existingIndex]; history.messages = [...this.currentChatHistory]; history.updatedAt = new Date().toISOString(); + if (this.sessionId === this.lastTraceSessionId && this.lastTraceRunId) { + history.lastRunId = this.lastTraceRunId; + } // 如果标题需要更新(第一条消息改变了) const firstUserMessage = this.currentChatHistory.find(msg => msg.type === 'user'); @@ -444,7 +449,7 @@ class SuperBizAgentApp { // 加载历史对话 this.sessionId = history.id; - this.rememberTraceSessionId(this.sessionId); + this.rememberTraceTarget(this.sessionId, history.lastRunId || null); this.currentChatHistory = [...history.messages]; this.isCurrentChatFromHistory = true; // 标记为从历史记录加载 @@ -487,16 +492,39 @@ class SuperBizAgentApp { } } + loadLastTraceRunId() { + try { + return localStorage.getItem('lastTraceRunId') || ''; + } catch (e) { + return ''; + } + } + rememberTraceSessionId(sessionId) { + this.rememberTraceTarget(sessionId, null); + } + + rememberTraceTarget(sessionId, runId) { if (!sessionId) { return; } this.lastTraceSessionId = sessionId; + this.lastTraceRunId = runId || ''; try { localStorage.setItem('lastTraceSessionId', sessionId); + if (runId) { + localStorage.setItem('lastTraceRunId', runId); + } else { + localStorage.removeItem('lastTraceRunId'); + } } catch (e) { // localStorage may be unavailable in private or restricted contexts. } + const history = this.chatHistories.find(item => item && item.id === sessionId); + if (history && runId) { + history.lastRunId = runId; + this.saveChatHistories(); + } this.updateTraceWorkbenchLink(); } @@ -508,9 +536,30 @@ class SuperBizAgentApp { const sessionId = this.lastTraceSessionId || (this.currentChatHistory.length > 0 ? this.sessionId : '') || (recentHistory ? recentHistory.id : ''); - this.traceWorkbenchLink.href = sessionId - ? `trace.html?sessionId=${encodeURIComponent(sessionId)}` - : 'trace.html'; + if (!sessionId) { + this.traceWorkbenchLink.href = 'trace.html'; + return; + } + const params = new URLSearchParams({ sessionId }); + if (this.lastTraceRunId && sessionId === this.lastTraceSessionId) { + params.set('runId', this.lastTraceRunId); + } + this.traceWorkbenchLink.href = `trace.html?${params.toString()}`; + } + + rememberRunMetadata(sseMessage) { + if (!sseMessage) { + return; + } + const payload = sseMessage.data && typeof sseMessage.data === 'object' ? sseMessage.data : {}; + const sessionId = sseMessage.sessionId || payload.sessionId; + const runId = sseMessage.runId || payload.runId; + if (!sessionId) { + return; + } + this.lastSessionId = sessionId; + this.lastRunId = runId || ''; + this.rememberTraceTarget(sessionId, runId || null); } // 切换模式下拉菜单 @@ -675,10 +724,11 @@ class SuperBizAgentApp { const chatResponse = data.data; if (chatResponse && chatResponse.success) { - // 保存后端返回的 sessionId,用于 feedback 提交 + // 保存后端返回的 sessionId/runId,用于 feedback 提交和 Trace 精确定位 if (chatResponse.sessionId) { this.lastSessionId = chatResponse.sessionId; - this.rememberTraceSessionId(chatResponse.sessionId); + this.lastRunId = chatResponse.runId || ''; + this.rememberTraceTarget(chatResponse.sessionId, chatResponse.runId || null); } // 成功:添加实际响应消息(即使 answer 为空也显示) const answer = chatResponse.answer || '(无回复内容)'; @@ -784,7 +834,9 @@ class SuperBizAgentApp { console.log('[SSE调试] 解析JSON成功:', sseMessage); if (sseMessage && typeof sseMessage.type === 'string') { - if (sseMessage.type === 'content') { + if (sseMessage.type === 'metadata') { + this.rememberRunMetadata(sseMessage); + } else if (sseMessage.type === 'content') { const content = sseMessage.data || ''; fullResponse += content; console.log('[SSE调试] 添加内容:', content); @@ -897,7 +949,7 @@ class SuperBizAgentApp { // assistant 消息末尾加反馈栏(流式消息完成后由 handleStreamComplete 添加) if (type === 'assistant' && !isStreaming) { - messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId)); + messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId, this.lastRunId || '')); } messageDiv.appendChild(messageContentWrapper); @@ -919,7 +971,7 @@ class SuperBizAgentApp { } // 创建反馈栏,sessionId 闭包绑定,避免多轮对话时错位 - createFeedbackBar(sessionId) { + createFeedbackBar(sessionId, runId = '') { const bar = document.createElement('div'); bar.className = 'feedback-bar'; @@ -933,8 +985,8 @@ class SuperBizAgentApp { notUsefulBtn.title = '无用'; notUsefulBtn.innerHTML = ``; - usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId)); - notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId)); + usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId, runId)); + notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId, runId)); bar.appendChild(usefulBtn); bar.appendChild(notUsefulBtn); @@ -942,7 +994,7 @@ class SuperBizAgentApp { } // 提交反馈 - async submitFeedback(feedback, barElement, sessionId) { + async submitFeedback(feedback, barElement, sessionId, runId = '') { if (!sessionId) return; barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = true); @@ -951,7 +1003,7 @@ class SuperBizAgentApp { const response = await fetch(`${this.apiBaseUrl}/feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ sessionId, feedback }) + body: JSON.stringify({ sessionId, runId: runId || undefined, feedback }) }); const data = await response.json(); if (data.success) { @@ -1055,7 +1107,7 @@ class SuperBizAgentApp { } // 流式完成后追加反馈栏 if (messageContentWrapper && !messageContentWrapper.querySelector('.feedback-bar')) { - messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId)); + messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId, this.lastRunId || '')); } } // 保存流式消息到历史记录 @@ -1273,9 +1325,11 @@ class SuperBizAgentApp { for (const jsonStr of matches) { try { const sseMessage = JSON.parse(jsonStr); - if (sseMessage.type === 'session') { + if (sseMessage.type === 'metadata') { + this.rememberRunMetadata(sseMessage); + } else if (sseMessage.type === 'session') { this.lastSessionId = sseMessage.data; - this.rememberTraceSessionId(sseMessage.data); + this.rememberTraceTarget(sseMessage.data, null); } else if (sseMessage.type === 'content') { fullResponse += sseMessage.data || ''; } else if (sseMessage.type === 'done') { @@ -1306,9 +1360,11 @@ class SuperBizAgentApp { try { const sseMessage = JSON.parse(rawData); if (sseMessage && sseMessage.type) { - if (sseMessage.type === 'session') { + if (sseMessage.type === 'metadata') { + this.rememberRunMetadata(sseMessage); + } else if (sseMessage.type === 'session') { this.lastSessionId = sseMessage.data; - this.rememberTraceSessionId(sseMessage.data); + this.rememberTraceTarget(sseMessage.data, null); } else if (sseMessage.type === 'content') { fullResponse += sseMessage.data || ''; if (loadingMessageElement) { diff --git a/src/main/resources/static/trace.js b/src/main/resources/static/trace.js index de612ff..79cc5e9 100644 --- a/src/main/resources/static/trace.js +++ b/src/main/resources/static/trace.js @@ -36,7 +36,7 @@ class TraceWorkbench { bindEvents() { this.sessionForm.addEventListener('submit', (event) => { event.preventDefault(); - this.loadTrace(this.sessionIdInput.value.trim()); + this.loadTrace(this.sessionIdInput.value.trim(), ''); }); this.sessionIdInput.addEventListener('focus', () => { @@ -66,9 +66,10 @@ class TraceWorkbench { bootstrapFromUrl() { const params = new URLSearchParams(window.location.search); const sessionId = params.get('sessionId') || this.getLastTraceSessionId(); + const runId = params.get('runId') || ''; if (sessionId) { this.sessionIdInput.value = sessionId; - this.loadTrace(sessionId); + this.loadTrace(sessionId, runId); return; } this.renderEmpty(); @@ -107,6 +108,7 @@ class TraceWorkbench { id: history.id, title: history.title || '未命名对话', updatedAt: history.updatedAt || history.createdAt || '', + runId: history.lastRunId || '', source: 'history' })); } @@ -237,7 +239,7 @@ class TraceWorkbench { chooseSession(sessionId) { this.sessionIdInput.value = sessionId; this.closeSessionOptions(); - this.loadTrace(sessionId); + this.loadTrace(sessionId, ''); } shortSessionId(sessionId) { @@ -264,18 +266,23 @@ class TraceWorkbench { }); } - async loadTrace(sessionId) { + async loadTrace(sessionId, runId = '') { if (!sessionId) { this.setState('请先输入会话 ID。', 'error'); return; } - this.setState(`正在加载 ${sessionId}...`, 'loading'); - this.updateUrl(sessionId); + const runSuffix = runId ? ` / ${runId}` : ''; + this.setState(`正在加载 ${sessionId}${runSuffix}...`, 'loading'); + this.updateUrl(sessionId, runId); this.loadButton.disabled = true; try { - const response = await fetch(`/api/diagnosis/${encodeURIComponent(sessionId)}/trace`); + const url = new URL(`/api/diagnosis/${encodeURIComponent(sessionId)}/trace`, window.location.origin); + if (runId) { + url.searchParams.set('runId', runId); + } + const response = await fetch(url.toString()); const data = await this.handleResponse(response); this.trace = data; this.selectedToolId = data.toolInvocations && data.toolInvocations.length @@ -284,11 +291,14 @@ class TraceWorkbench { this.activeFilter = 'all'; try { localStorage.setItem('lastTraceSessionId', sessionId); + if (data.runId) { + localStorage.setItem('lastTraceRunId', data.runId); + } } catch (error) { // ignore storage failures } this.renderTrace(); - this.setState(`已加载 ${sessionId}。`, ''); + this.setState(`已加载 ${sessionId}${data.runId ? ` / ${data.runId}` : ''}。`, ''); } catch (error) { this.trace = null; this.renderEmpty(); @@ -324,9 +334,14 @@ class TraceWorkbench { return value; } - updateUrl(sessionId) { + updateUrl(sessionId, runId = '') { const url = new URL(window.location.href); url.searchParams.set('sessionId', sessionId); + if (runId) { + url.searchParams.set('runId', runId); + } else { + url.searchParams.delete('runId'); + } window.history.replaceState({}, '', url.toString()); } @@ -399,6 +414,9 @@ class TraceWorkbench { if (session.sessionId) { subtitleParts.push(`会话 ${session.sessionId}`); } + if (this.trace && this.trace.runId) { + subtitleParts.push(`运行 ${this.trace.runId}`); + } if (session.status) { subtitleParts.push(`状态 ${this.displayStatus(session.status)}`); }