feat(trace): finish run-aware demo verification

This commit is contained in:
zhuyongxin
2026-07-10 21:37:43 +08:00
parent 78c1477198
commit f9df94377b
28 changed files with 779 additions and 459 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# MVP 架构文档 # MVP 架构文档
**更新日期**:2026-07-08 **更新日期**:2026-07-10
这里是 MVP 当前架构的唯一入口。旧版设计、早期拆解和已经被新实现替代的方案已归档到: 这里是 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 和评测脚本证明诊断链路可解释、可回放、可对比。
## 阅读顺序 ## 阅读顺序
+9 -6
View File
@@ -42,13 +42,15 @@ flowchart TB
end end
subgraph Trace["Trace persistence"] subgraph Trace["Trace persistence"]
Session["diagnosis_session"] ChatSession["chat_session"]
Run["diagnosis_run"]
Step["agent_step"] Step["agent_step"]
Invocation["tool_invocation"] Invocation["tool_invocation"]
SelfEval["self_evaluation"] SelfEval["self_evaluation"]
end end
ChatService --> Session ChatService --> ChatSession
ChatService --> Run
ChatPlanner --> Step ChatPlanner --> Step
ChatExecutor --> Step ChatExecutor --> Step
ChatGatekeeper --> SelfEval ChatGatekeeper --> SelfEval
@@ -57,7 +59,8 @@ flowchart TB
ChatDecision --> SelfEval ChatDecision --> SelfEval
ChatComposer --> Step ChatComposer --> Step
AiOpsService --> Session AiOpsService --> ChatSession
AiOpsService --> Run
AiOpsPlanner --> Step AiOpsPlanner --> Step
AiOpsExecutor --> Step AiOpsExecutor --> Step
AiOpsTools --> Invocation AiOpsTools --> Invocation
@@ -103,7 +106,7 @@ sequenceDiagram
participant G as gatekeeper participant G as gatekeeper
participant V as chat_verifier participant V as chat_verifier
participant M as chat_composer participant M as chat_composer
participant S as diagnosis_session participant R as diagnosis_run
C->>P: 原始问题 + history + retry_context C->>P: 原始问题 + history + retry_context
P-->>C: planner_plan P-->>C: planner_plan
@@ -115,13 +118,13 @@ sequenceDiagram
G-->>C: gatekeeper_result G-->>C: gatekeeper_result
C->>V: executor_structured_output + gatekeeper_result + tool_trace_summary C->>V: executor_structured_output + gatekeeper_result + tool_trace_summary
V-->>C: PASS / LOW_CONFID / REJECT V-->>C: PASS / LOW_CONFID / REJECT
C->>S: 写入 verifier_evaluation C->>R: 写入 verifier_evaluation
alt LOW_CONFID 且允许补证据 alt LOW_CONFID 且允许补证据
C->>P: retry_context: 仅补缺失证据 C->>P: retry_context: 仅补缺失证据
else PASS 或 REJECT else PASS 或 REJECT
C->>M: allowed_claims + missing_info + recommended_actions C->>M: allowed_claims + missing_info + recommended_actions
M-->>C: composer_output M-->>C: composer_output
C->>S: 保存 Composer 最终 answer C->>R: 保存 Composer 最终 answer
end end
``` ```
+34 -21
View File
@@ -65,7 +65,8 @@ flowchart TB
end end
subgraph Store["Persistence and Trace"] subgraph Store["Persistence and Trace"]
Session["diagnosis_session"] ChatSession["chat_session"]
Run["diagnosis_run"]
Step["agent_step"] Step["agent_step"]
Invocation["tool_invocation"] Invocation["tool_invocation"]
ApiDoc["api_document"] ApiDoc["api_document"]
@@ -130,9 +131,10 @@ RAG Retrieval
-> Milvus SDK fallback -> Milvus SDK fallback
Persistence Persistence
-> diagnosis_session -> chat_session
-> agent_step -> diagnosis_run
-> tool_invocation -> agent_step.run_id
-> tool_invocation.run_id
-> api_document -> api_document
-> Milvus/Zilliz collection -> Milvus/Zilliz collection
@@ -163,21 +165,22 @@ sequenceDiagram
User->>API: 提交诊断问题 User->>API: 提交诊断问题
API->>Chat: execute chat strategy API->>Chat: execute chat strategy
Chat->>DB: 创建 chat_session metadata + diagnosis_run(runId)
Chat->>Planner: 复杂问题进入规划 Chat->>Planner: 复杂问题进入规划
Planner->>DB: 写入 agent_step Planner->>DB: 写入 agent_step.run_id
Planner->>Executor: 下发排查方向 Planner->>Executor: 下发排查方向
Executor->>Tool: lookup_knowledge / logs / metrics Executor->>Tool: lookup_knowledge / logs / metrics
Tool->>DB: 写入 tool_invocation Tool->>DB: 写入 tool_invocation.run_id
Tool-->>Executor: 返回证据 Tool-->>Executor: 返回证据
Executor->>Gatekeeper: 输出 executor_evidence_v2 Executor->>Gatekeeper: 输出 executor_evidence_v2
Gatekeeper->>DB: 读取 tool_invocation.evidence_refs 并校验引用 Gatekeeper->>DB: 读取 tool_invocation.evidence_refs 并校验引用
Gatekeeper->>Verifier: 传入已验真的 claims / excerpts 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 Verifier->>Composer: 传入 allowed_claims / missing_info / actions
Composer->>Chat: 生成最终用户答复 Composer->>Chat: 生成最终用户答复
Chat->>DB: 保存 diagnosis_session.answer Chat->>DB: 保存 diagnosis_run.answer
User->>Trace: GET /api/diagnosis/{sessionId}/trace User->>Trace: GET /api/diagnosis/{sessionId}/trace?runId=...
Trace->>DB: 聚合 session / step / tool Trace->>DB: 聚合 run / step / tool
Trace-->>User: 返回可回放诊断链路 Trace-->>User: 返回可回放诊断链路
``` ```
@@ -194,13 +197,14 @@ POST /api/chat
-> Gatekeeper 校验 Executor 证据引用真实性 -> Gatekeeper 校验 Executor 证据引用真实性
-> Verifier 判断 claim 是否能由已核验证据推出 -> Verifier 判断 claim 是否能由已核验证据推出
-> Composer 生成最终用户答复 -> Composer 生成最终用户答复
-> 保存 diagnosis_session -> 保存 chat_session metadata
-> 保存 agent_step -> 保存 diagnosis_run
-> 保存 tool_invocation -> 保存 agent_step.run_id
-> 合并 self_evaluation.verifier_evaluation -> 保存 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)。 Agent 编排细节见 [agent-orchestration.md](agent-orchestration.md)。
@@ -255,7 +259,7 @@ POST /api/ai_ops
-> Prometheus / logs / knowledge tools -> Prometheus / logs / knowledge tools
-> 生成告警分析报告 -> 生成告警分析报告
-> AiOpsRuleEvaluationService -> AiOpsRuleEvaluationService
-> 合并 self_evaluation.aiops_rule_evaluation -> 合并 diagnosis_run.self_evaluation.aiops_rule_evaluation
-> Trace API 可查看全链路 -> Trace API 可查看全链路
``` ```
@@ -317,23 +321,30 @@ RAG 总体设计见 [rag-architecture.md](rag-architecture.md),检索运行细
## 6. 持久化模型 ## 6. 持久化模型
当前诊断持久化以三张表为核心: 当前诊断持久化以 session/run/trace 明细为核心:
```text ```text
diagnosis_session chat_session
-> 一次诊断会话的主记录 -> 多轮会话目录和元数据
-> session_id / status / message_pair_count
diagnosis_run
-> 一次诊断运行的主记录
-> run_id / session_id
-> query / status / agent_flow / answer -> query / status / agent_flow / answer
-> self_evaluation -> self_evaluation
-> step_count / tool_call_count / duration -> step_count / tool_call_count / duration
agent_step agent_step
-> Agent 模型调用步骤 -> Agent 模型调用步骤
-> session_id / run_id
-> step_index / agent_name -> step_index / agent_name
-> model_input / model_output / thought -> model_input / model_output / thought
-> duration / token_count -> duration / token_count
tool_invocation tool_invocation
-> 工具调用事实 -> 工具调用事实
-> session_id / run_id
-> tool_name / input_params / output_preview -> tool_name / input_params / output_preview
-> retrieval_layer / retrieval_details -> retrieval_layer / retrieval_details
-> retrieval_details.evidence_refs -> 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` 仍用于文档元数据管理。 - `api_document` 仍用于文档元数据管理。
- 文档向量内容存放在 Milvus/Zilliz collection 中。 - 文档向量内容存放在 Milvus/Zilliz collection 中。
@@ -353,11 +365,12 @@ tool_invocation
```text ```text
GET /api/diagnosis/{sessionId}/trace GET /api/diagnosis/{sessionId}/trace
GET /api/diagnosis/{sessionId}/trace?runId=run-...
``` ```
Trace API 聚合: Trace API 聚合:
- 会话状态和最终报告。 - 会话元数据、运行状态和最终报告。
- Agent step 序列。 - Agent step 序列。
- 工具调用和检索细节。 - 工具调用和检索细节。
- Chat Gatekeeper / Verifier / Composer 结果。 - Chat Gatekeeper / Verifier / Composer 结果。
+70 -194
View File
@@ -1,31 +1,44 @@
# 数据模型总览 # 数据模型总览
**更新日期**:2026-07-08 **更新日期**:2026-07-10
**状态**:当前可运行架构 **状态**:当前可运行架构
## 1. 定位 ## 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 - 知识库:`api_document`、`knowledge_domain`、Milvus/Zilliz metadata
- 反馈沉淀:`case_library` - 反馈沉淀:`case_library`
`diagnosis_session` 仍保留为历史兼容和回滚表,不再是新执行写入的主模型。
## 2. 总体关系 ## 2. 总体关系
```mermaid ```mermaid
erDiagram erDiagram
diagnosis_session ||--o{ agent_step : has chat_session ||--o{ diagnosis_run : owns
diagnosis_session ||--o{ tool_invocation : has diagnosis_run ||--o{ agent_step : has
diagnosis_session ||--o| case_library : creates_when_useful diagnosis_run ||--o{ tool_invocation : has
diagnosis_run ||--o| case_library : creates_when_useful
api_document ||--o{ milvus_chunk : indexed_as api_document ||--o{ milvus_chunk : indexed_as
knowledge_domain ||--o{ api_document : groups knowledge_domain ||--o{ api_document : groups
diagnosis_session { chat_session {
bigint id bigint id
varchar session_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 text query
varchar status varchar status
varchar agent_flow varchar agent_flow
@@ -37,42 +50,23 @@ erDiagram
agent_step { agent_step {
bigint id bigint id
varchar session_id varchar session_id
varchar run_id
int step_index int step_index
varchar agent_name varchar agent_name
text model_input text model_input
text model_output text model_output
text thought
boolean has_tool_call boolean has_tool_call
} }
tool_invocation { tool_invocation {
bigint id bigint id
varchar session_id varchar session_id
varchar run_id
bigint step_id
varchar tool_name varchar tool_name
json input_params json input_params
text output_preview text output_preview
varchar retrieval_layer
json retrieval_details 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 { case_library {
@@ -84,58 +78,52 @@ erDiagram
text root_cause text root_cause
text solution text solution
} }
milvus_chunk {
varchar id
text content
json metadata
vector vector
}
``` ```
说明:Milvus/Zilliz collection 不是 MySQL 表,图中的 `milvus_chunk` 是逻辑模型。 说明:Milvus/Zilliz collection 不是 MySQL 表,图中的 `milvus_chunk` 是逻辑模型。
## 3. 诊断 Trace 模型 ## 3. 会话与运行模型
### diagnosis_session ### chat_session
会话级主记录。 `chat_session` 是会话目录表,保存 `sessionId` 的元数据:
关键字段:
| 字段 | 说明 | | 字段 | 说明 |
|---|---| |---|---|
| `session_id` | 外部关联键,Trace 和 Feedback 都使用它 | | `session_id` | 外部会话 ID,用于多轮上下文和 run 列表 |
| `query` | 用户原始问题或 AIOps 输入摘要 | | `status` | 会话目录状态 |
| `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` | | `agent_flow` | `CHAT` / `AI_OPS` |
| `answer` | 最终答复或告警报告 | | `answer` | 本次运行最终答复或告警报告 |
| `self_evaluation` | rule/verifier/aiops 自评估容器 | | `self_evaluation` | 本次运行的 rule/verifier/aiops 自评估容器 |
| `feedback` | 用户反馈 | | `feedback` | 本次运行的用户反馈 |
同一个 `sessionId` 可以有多个 `runId`。Trace、反馈、评测和案例沉淀都应优先使用 `runId`,避免多轮同 session 下的数据混合。
## 4. Trace 明细模型
### agent_step ### agent_step
记录模型调用步骤。 `agent_step` 记录模型调用步骤。新写入同时保留 `session_id` 和 `run_id`,其中 `run_id` 是回放边界。Trace 页面和评测应先按 `run_id` 隔离取数,展示顺序以 Trace API 返回顺序为准。
用途:
- 回放 Agent 推理过程。
- 查看 Planner / Executor / Verifier 的输入输出摘要。
- 统计 step count、duration、token count。
### tool_invocation ### tool_invocation
记录工具调用事实。 `tool_invocation` 记录显式工具调用事实。`retrieval_details.evidence_refs` 是 Chat 证据链路的关键字段:
用途:
- 给 Trace API 展示证据。
- 给 Gatekeeper 提供 `retrieval_details.evidence_refs` 引用验真源。
- 给 Verifier 构造 `tool_trace_summary` 审计导航。
- 给 `EvaluationService` 计算 evidence score。
- 给 RAG eval 和人工排查提供检索细节。
`retrieval_details.evidence_refs` 是当前 Chat 证据链路的关键字段:
```json ```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` 只表示“本次工具查询未检索到匹配证据”,不能被解释为“问题不存在”或“根因已排除”。 `$.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. 反馈沉淀模型 ## 5. 反馈沉淀模型
### case_library `useful` 反馈会触发 `CaseLibraryService.createFromRun`。
`useful` 反馈会触发 `CaseLibraryService.createFromSession`。
当前自动映射: 当前自动映射:
| 字段 | 来源 | | 字段 | 来源 |
|---|---| |---|---|
| `case_id` | UUID | | `case_id` | UUID |
| `diagnosis_id` | `diagnosis_session.session_id` | | `diagnosis_id` | 新数据为 `diagnosis_run.run_id`;历史数据可能为 `diagnosis_session.session_id` |
| `source_type` | `AUTO` | | `source_type` | `AUTO` |
| `fault_category` | 当前默认 `GENERAL` | | `fault_category` | 当前默认 `GENERAL` |
| `title` | session query 前 100 字符 | | `title` | run query 前 100 字符 |
| `root_cause` | session answer | | `root_cause` | run answer |
| `solution` | session answer | | `solution` | run answer |
| `created_by` | `system` | | `created_by` | `system` |
## 6. self_evaluation 结构 ## 6. self_evaluation 结构
`diagnosis_session.self_evaluation` 是 JSON 容器: `diagnosis_run.self_evaluation` 是运行级 JSON 容器:
```json ```json
{ {
@@ -235,78 +168,21 @@ category
} }
``` ```
边界: Chat 通常写入 `rule_evaluation` 和 `verifier_evaluation`;AIOps 写入 `aiops_rule_evaluation`。
- `rule_evaluation` 评估证据收集充分度。 ## 7. 当前边界和后续
- `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. 当前边界和后续
当前边界: 当前边界:
- `agent_step.session_id` 和 `tool_invocation.session_id` 通过 sessionId 关联,不强制外键。 - `chat_session` 只存会话元数据,不存完整正文历史。
- `tool_invocation.step_id` 可为空。 - `diagnosis_run` 存一次运行的长期审计状态。
- Milvus chunk 与 `api_document` 通过 metadata.docId 逻辑关联。 - `agent_step.run_id` 和 `tool_invocation.run_id` 是 Trace、Verifier、Eval 的运行边界。
- `case_library` 与 session 通过 `diagnosis_id=session_id` 关联。 - 当前实现主要使用逻辑关联,不依赖数据库外键。
- `case_library.diagnosis_id` 是过渡字段,新值按 `run_id` 解释,旧值可能按 `session_id` 解释。
- `diagnosis_session` 只作为历史兼容和回滚表保留。
后续可增强: 后续可增强:
1. 增加 run id,支持同 session 多次独立诊断。 1. 强化 `tool_invocation.step_id` 关联。
2. 强化 `tool_invocation.step_id` 关联。 2. 将 Gatekeeper 规则配置化时的规则元数据保存为可审计版本。
3. 将 Gatekeeper 规则配置化时的规则元数据保存为可审计版本。 3. 将 `case_library` 的 rootCause/solution 从完整 answer 中结构化抽取。
4. 将 `case_library` 的 rootCause/solution 从完整 answer 中结构化抽取。
@@ -391,7 +391,7 @@ Composer 位于 Verifier 之后,输入是 ChatService 过滤后的允许表达
## 7. Trace Persistence ## 7. Trace Persistence
`diagnosis_session.self_evaluation.verifier_evaluation` 持久化: `diagnosis_run.self_evaluation.verifier_evaluation` 持久化:
```json ```json
{ {
+25 -16
View File
@@ -1,6 +1,6 @@
# 反馈与自评估架构 # 反馈与自评估架构
**更新日期**:2026-07-08 **更新日期**:2026-07-10
**状态**:当前可运行架构 **状态**:当前可运行架构
**参考历史文档**:`archive/2026-07-05-legacy/confidence-feedback.md` **参考历史文档**:`archive/2026-07-05-legacy/confidence-feedback.md`
@@ -8,8 +8,8 @@
反馈架构包含两条闭环: 反馈架构包含两条闭环:
1. 系统自评估:基于工具调用、Gatekeeper、Verifier、Composer、AIOps 规则检查,写入 `diagnosis_session.self_evaluation`。 1. 系统自评估:基于当前 run 的工具调用、Gatekeeper、Verifier、Composer、AIOps 规则检查,写入 `diagnosis_run.self_evaluation`。
2. 用户反馈:用户标记 `useful` 或 `not_useful`,写入 `diagnosis_session.feedback`,其中 `useful` 会沉淀案例。 2. 用户反馈:用户标记 `useful` 或 `not_useful`,优先写入 `diagnosis_run.feedback`,其中 `useful` 会沉淀案例。
当前重要边界: 当前重要边界:
@@ -21,7 +21,7 @@
```mermaid ```mermaid
flowchart TD flowchart TD
Answer["Chat / AIOps final answer"] --> Session["diagnosis_session.answer"] Answer["Chat / AIOps final answer"] --> Run["diagnosis_run.answer"]
subgraph SelfEval["Self evaluation"] subgraph SelfEval["Self evaluation"]
Invocation["tool_invocation"] --> RuleEval["EvaluationService: rule_evaluation"] Invocation["tool_invocation"] --> RuleEval["EvaluationService: rule_evaluation"]
@@ -40,24 +40,24 @@ flowchart TD
RuleEval --> Merge["SelfEvaluationMergeService"] RuleEval --> Merge["SelfEvaluationMergeService"]
VerifierEval --> Merge VerifierEval --> Merge
AiOpsEval --> Merge AiOpsEval --> Merge
Merge --> SelfJson["diagnosis_session.self_evaluation"] Merge --> SelfJson["diagnosis_run.self_evaluation"]
subgraph UserFeedback["User feedback"] subgraph UserFeedback["User feedback"]
UI["Feedback bar"] --> API["POST /api/feedback"] UI["Feedback bar"] --> API["POST /api/feedback"]
API --> FeedbackService["FeedbackService"] API --> FeedbackService["FeedbackService"]
FeedbackService --> FeedbackField["diagnosis_session.feedback"] FeedbackService --> FeedbackField["diagnosis_run.feedback"]
FeedbackService --> Useful{"feedback == useful?"} FeedbackService --> Useful{"feedback == useful?"}
Useful -->|yes| CaseService["CaseLibraryService.createFromSession"] Useful -->|yes| CaseService["CaseLibraryService.createFromRun"]
CaseService --> Case["case_library"] CaseService --> Case["case_library"]
Useful -->|no| BadCase["Bad case by feedback=not_useful"] Useful -->|no| BadCase["Bad case by feedback=not_useful"]
end end
Session --> UI Run --> UI
``` ```
## 3. self_evaluation JSON ## 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"] Composer --> ComposerOutput["composer_output"]
Output --> Merge["SelfEvaluationMergeService.mergeVerifierEvaluation"] Output --> Merge["SelfEvaluationMergeService.mergeVerifierEvaluation"]
ComposerOutput --> Merge ComposerOutput --> Merge
Merge --> Session["diagnosis_session.self_evaluation.verifier_evaluation"] Merge --> Run["diagnosis_run.self_evaluation.verifier_evaluation"]
``` ```
Verifier 输出: Verifier 输出:
@@ -198,6 +198,7 @@ POST /api/feedback
Content-Type: application/json Content-Type: application/json
{ {
"runId": "run-xxx",
"sessionId": "xxx", "sessionId": "xxx",
"feedback": "useful" | "not_useful" "feedback": "useful" | "not_useful"
} }
@@ -209,6 +210,8 @@ Content-Type: application/json
{ {
"success": true, "success": true,
"message": "反馈已记录", "message": "反馈已记录",
"runId": "run-xxx",
"fallbackToLatestRun": false,
"caseId": "uuid 或 null" "caseId": "uuid 或 null"
} }
``` ```
@@ -217,10 +220,16 @@ Content-Type: application/json
| feedback | 行为 | | feedback | 行为 |
|---|---| |---|---|
| `useful` | 写入 `DiagnosisSession.feedback`,调用 `CaseLibraryService.createFromSession` | | `useful` | 写入 `DiagnosisRun.feedback`,调用 `CaseLibraryService.createFromRun` |
| `not_useful` | 写入 `DiagnosisSession.feedback`,不改变 session status | | `not_useful` | 写入 `DiagnosisRun.feedback`,不改变 run status |
| 其他值 | 返回 HTTP 400 | | 其他值 | 返回 HTTP 400 |
兼容行为:
- 请求带 `runId` 时,后端验证 `runId` 属于 `sessionId`。
- 请求缺少 `runId` 且存在 run-backed 数据时,后端绑定 latest run,并返回 `fallbackToLatestRun=true` 和实际 `runId`。
- 仅当没有 `diagnosis_run` 但存在历史 `diagnosis_session` 时,才使用历史 fallback;该路径不声明 latest-run fallback。
## 8. 案例沉淀 ## 8. 案例沉淀
`useful` 反馈会生成或复用 `case_library` 记录。 `useful` 反馈会生成或复用 `case_library` 记录。
@@ -230,7 +239,7 @@ Content-Type: application/json
| CaseLibrary 字段 | 来源 | | CaseLibrary 字段 | 来源 |
|---|---| |---|---|
| `caseId` | UUID | | `caseId` | UUID |
| `diagnosisId` | `DiagnosisSession.sessionId` | | `diagnosisId` | 新数据为 `DiagnosisRun.runId`;历史数据可能为 `DiagnosisSession.sessionId` |
| `sourceType` | `AUTO` | | `sourceType` | `AUTO` |
| `faultCategory` | 当前固定为 `GENERAL` | | `faultCategory` | 当前固定为 `GENERAL` |
| `title` | `query` 前 100 字符 | | `title` | `query` 前 100 字符 |
@@ -241,7 +250,7 @@ Content-Type: application/json
幂等性: 幂等性:
```text ```text
case_library.diagnosisId == sessionId case_library.diagnosisId == runId
-> existing case: return existing -> existing case: return existing
-> missing case: create new -> missing case: create new
``` ```
@@ -260,9 +269,9 @@ Trace API 会展示:
| 视角 | 数据来源 | | 视角 | 数据来源 |
|---|---| |---|---|
| 执行是否成功 | `diagnosis_session.status` | | 执行是否成功 | `diagnosis_run.status` |
| 证据是否充分 | `self_evaluation.rule_evaluation` / `verifier_evaluation` | | 证据是否充分 | `self_evaluation.rule_evaluation` / `verifier_evaluation` |
| 用户是否认可 | `diagnosis_session.feedback` | | 用户是否认可 | `diagnosis_run.feedback` |
## 10. 后续增强 ## 10. 后续增强
+4 -4
View File
@@ -36,7 +36,7 @@ flowchart TB
Tools --> Invocation["tool_invocation"] Tools --> Invocation["tool_invocation"]
Agent --> StepHook["AgentLoggingHook"] Agent --> StepHook["AgentLoggingHook"]
StepHook --> Step["agent_step"] StepHook --> Step["agent_step"]
Agent --> Session["diagnosis_session"] Agent --> Run["diagnosis_run"]
Invocation --> EvidenceRefs["retrieval_details.evidence_refs"] Invocation --> EvidenceRefs["retrieval_details.evidence_refs"]
EvidenceRefs --> Gatekeeper["ExecutorGatekeeperService"] EvidenceRefs --> Gatekeeper["ExecutorGatekeeperService"]
@@ -49,7 +49,7 @@ flowchart TB
Invocation --> AiOpsRule["AiOpsRuleEvaluationService"] Invocation --> AiOpsRule["AiOpsRuleEvaluationService"]
AiOpsRule --> AiOpsEval["self_evaluation.aiops_rule_evaluation"] AiOpsRule --> AiOpsEval["self_evaluation.aiops_rule_evaluation"]
Session --> TraceAPI["DiagnosisTraceService"] Run --> TraceAPI["DiagnosisTraceService"]
Step --> TraceAPI Step --> TraceAPI
Invocation --> TraceAPI Invocation --> TraceAPI
SelfEval --> TraceAPI SelfEval --> TraceAPI
@@ -210,7 +210,7 @@ Verifier 不再逐字核验 excerpt 真伪;这由 Gatekeeper 完成。Verifier
结果写入: 结果写入:
```text ```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 回放。 其中同时持久化 `executor_structured_output`、`gatekeeper_result`、`tool_trace_summary`、`prompt_audit` 和 `composer_output`,用于 Trace 回放。
@@ -229,7 +229,7 @@ AIOps 当前不走 Chat Verifier,而是用 `AiOpsRuleEvaluationService` 做轻
结果写入: 结果写入:
```text ```text
diagnosis_session.self_evaluation.aiops_rule_evaluation diagnosis_run.self_evaluation.aiops_rule_evaluation
``` ```
## 8. Eval Baseline ## 8. Eval Baseline
+6 -5
View File
@@ -34,14 +34,15 @@ flowchart TB
AiOpsFlow --> Trace AiOpsFlow --> Trace
Tools --> Trace Tools --> Trace
Trace --> Session["diagnosis_session"] Trace --> ChatSession["chat_session"]
Trace --> Run["diagnosis_run"]
Trace --> Step["agent_step"] Trace --> Step["agent_step"]
Trace --> Invocation["tool_invocation"] Trace --> Invocation["tool_invocation"]
Invocation --> Verifier["Verifier / Rule Evaluation"] Invocation --> Verifier["Verifier / Rule Evaluation"]
Verifier --> SelfEval["self_evaluation"] Verifier --> SelfEval["self_evaluation"]
Session --> TraceAPI["GET /api/diagnosis/{sessionId}/trace"] Run --> TraceAPI["GET /api/diagnosis/{sessionId}/trace?runId=..."]
Step --> TraceAPI Step --> TraceAPI
Invocation --> TraceAPI Invocation --> TraceAPI
SelfEval --> TraceAPI SelfEval --> TraceAPI
@@ -61,15 +62,15 @@ Planner 负责拆解,Executor 只负责调用知识库、日志和指标工具
AIOps 告警入口走 Supervisor 调度 Planner/Executor: AIOps 告警入口走 Supervisor 调度 Planner/Executor:
如果请求里有 alert payload,系统会进入 PAYLOAD_TARGETED 模式,报告必须聚焦这个告警,而不是被当前环境中的其他活跃告警带偏。 如果请求里有 alert payload,系统会进入 PAYLOAD_TARGETED 模式,报告必须聚焦这个告警,而不是被当前环境中的其他活跃告警带偏。
所有过程都会落到 diagnosis_session、agent_step、tool_invocation。 会话元数据会落到 chat_session,每次诊断运行会落到 diagnosis_run,步骤和工具明细通过 run_id 关联。
所以我可以用一个 sessionId 回放:模型怎么规划、调了哪些工具、工具返回什么、Gatekeeper 怎么验真、Verifier 怎么判定、Composer 最后怎么表达、用户最后是否反馈有用。 所以我可以用 sessionId + runId 精确回放:模型怎么规划、调了哪些工具、工具返回什么、Gatekeeper 怎么验真、Verifier 怎么判定、Composer 最后怎么表达、用户最后是否反馈有用。
``` ```
## 4. 五个亮点 ## 4. 五个亮点
| 亮点 | 怎么讲 | | 亮点 | 怎么讲 |
|---|---| |---|---|
| 可追踪 Agent | 每次诊断都有 `sessionId`,Trace API 可以回放 session、step、tool | | 可追踪 Agent | 每次诊断都有 `runId`,Trace API 可以回放 run、step、tool;同一 `sessionId` 可有多次独立 run |
| 显式工具证据链 | `lookup_knowledge`、日志、指标都记录到 `tool_invocation` | | 显式工具证据链 | `lookup_knowledge`、日志、指标都记录到 `tool_invocation` |
| RAG 工程化 | L0 降级为 hint,Spring AI VectorStore 做主检索,SDK fallback 保底 | | RAG 工程化 | L0 降级为 hint,Spring AI VectorStore 做主检索,SDK fallback 保底 |
| 质量门禁 | Chat Gatekeeper 验引用、Verifier 判可推导、Composer 控表达,AIOps rule evaluation 控制告警聚焦 | | 质量门禁 | Chat Gatekeeper 验引用、Verifier 判可推导、Composer 控表达,AIOps rule evaluation 控制告警聚焦 |
+65 -115
View File
@@ -1,69 +1,68 @@
# 会话与 Trace 生命周期 # 会话与 Trace 生命周期
**更新日期**:2026-07-08 **更新日期**:2026-07-10
**状态**:当前可运行架构 **状态**:当前可运行架构
**参考历史文档**:`archive/2026-07-05-legacy/session-management.md` **参考历史文档**:`archive/2026-07-05-legacy/session-management.md`
## 1. 定位 ## 1. 定位
旧版会话设计以 Redis 会话为主,MySQL 作为可选长期沉淀。当前 MVP 的可追踪诊断已经转为 MySQL Trace 三表为主: 当前 MVP 把“会话态”和“运行态”拆开:
```text ```text
diagnosis_session chat_session(sessionId)
-> agent_step -> diagnosis_run(runId)
-> tool_invocation -> agent_step(runId)
-> tool_invocation(runId)
``` ```
因此本文描述的是当前可运行链路: - `sessionId` 表示多轮会话目录和 Redis 上下文。
- `runId` 表示一次可回放诊断执行。
- `sessionId` 是一次诊断和后续 trace/feedback 的关联键。 - `DiagnosisTraceService` 聚合一个 run 的主记录、步骤和工具调用,形成可回放 Trace。
- `diagnosis_session` 保存会话级状态、问题、答案、自评估和反馈。 - `diagnosis_session` 只保留为历史兼容和回滚表。
- `agent_step` 保存每个 Agent 模型调用。
- `tool_invocation` 保存工具调用事实。
- `DiagnosisTraceService` 聚合三类记录,形成可回放 trace。
## 2. 生命周期总图 ## 2. 生命周期总图
```mermaid ```mermaid
flowchart TD flowchart TD
Start["request: chat / ai_ops"] --> Resolve["resolve sessionId"] Start["request: chat / ai_ops"] --> Resolve["resolve sessionId"]
Resolve --> Create["create or reset diagnosis_session"] Resolve --> Session["ensure chat_session metadata"]
Create --> Running["status = RUNNING"] Session --> Run["create diagnosis_run(runId)"]
Run --> Running["run.status = RUNNING"]
Running --> Agent["Agent workflow"] Running --> Agent["Agent workflow"]
Agent --> StepHook["AgentLoggingHook"] Agent --> Context["execution context(sessionId, runId)"]
StepHook --> Step["agent_step"] Context --> StepHook["AgentLoggingHook"]
Agent --> Tool["Evidence tools"] StepHook --> Step["agent_step(session_id, run_id)"]
Tool --> Invocation["tool_invocation"] Context --> Tool["Evidence tools"]
Tool --> Invocation["tool_invocation(session_id, run_id)"]
Invocation --> Gatekeeper["Gatekeeper evidence validation"] Invocation --> Gatekeeper["Gatekeeper evidence validation"]
Agent --> Final{"workflow result"} Agent --> Final{"workflow result"}
Final -->|success| Success["status = SUCCESS, answer saved"] Final -->|success| Success["run.status = SUCCESS, answer saved"]
Final -->|failed| Failed["status = FAILED"] Final -->|failed| Failed["run.status = FAILED"]
Success --> Evaluation["self_evaluation merge"] Success --> Evaluation["diagnosis_run.self_evaluation merge"]
Failed --> Evaluation Failed --> Evaluation
Evaluation --> Trace["GET /api/diagnosis/{sessionId}/trace"] Evaluation --> Trace["GET /api/diagnosis/{sessionId}/trace?runId=..."]
Success --> Feedback["POST /api/feedback"] Success --> Feedback["POST /api/feedback(sessionId, runId)"]
Feedback --> Case["useful -> case_library"] Feedback --> Case["useful -> case_library(run_id)"]
``` ```
## 3. sessionId 规则 ## 3. ID 规则
| 链路 | sessionId 来源 | | ID | 来源 | 含义 |
|---|---| |---|---|---|
| Chat | 如果请求带 sessionId,则复用;否则生成短 UUID | | `sessionId` | Chat request `Id`、AIOps payload `sessionId`,缺失时由服务生成 | 多轮会话目录和 Redis 上下文 |
| AIOps | 如果 payload 带 sessionId,则复用;否则生成 UUID | | `runId` | 每次有效 Chat/AIOps 执行创建 | 一次诊断运行和 Trace 回放边界 |
| Trace | URL path 中的 `{sessionId}` |
| Feedback | request body 中的 `sessionId` |
设计含义: 设计含义:
- 同一个 `sessionId` 可以贯穿诊断、trace 查询和用户反馈。 - 同一个 `sessionId` 可以贯穿多轮 Chat。
- 当前诊断开始时会重置当前 session 的运行态字段,例如 answer、duration、step/tool count。 - 每次有效 Chat/AIOps 执行都会创建新的 `runId`。
- `sessionId` 是业务关联键,不依赖数据库自增 ID 暴露给外部。 - Trace 和 Feedback 新客户端应传 `runId`;只传 `sessionId` 时兼容解析 latest run。
- latest run 排序使用 `diagnosis_run.created_at DESC, id DESC`,不使用 `updated_at`。
## 4. 状态流转 ## 4. 运行状态流转
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
@@ -77,14 +76,14 @@ stateDiagram-v2
字段边界: 字段边界:
| 字段 | 含义 | | 字段 | 所属表 | 含义 |
|---|---| |---|---|---|
| `status` | 执行状态:`PENDING` / `RUNNING` / `SUCCESS` / `FAILED` | | `status` | `diagnosis_run` | 单次运行执行状态 |
| `answer` | Agent 最终返回给用户的报告或答复 | | `answer` | `diagnosis_run` | 本次运行最终报告或答复 |
| `self_evaluation` | 系统自评估 JSON | | `self_evaluation` | `diagnosis_run` | 本次运行系统自评估 JSON |
| `feedback` | 用户反馈:`useful` / `not_useful` / null | | `feedback` | `diagnosis_run` | 本次运行用户反馈 |
`feedback` 不修改 `status`。一个执行成功但用户标记 `not_useful` 的 session,仍然应该是 `SUCCESS + feedback=not_useful`。 `feedback` 不修改 `status`。一个执行成功但用户标记 `not_useful` 的 run,仍然应该是 `SUCCESS + feedback=not_useful`。
## 5. agent_step 写入 ## 5. agent_step 写入
@@ -93,93 +92,49 @@ stateDiagram-v2
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
autonumber autonumber
participant Agent as ReactAgent participant Agent as Agent
participant Hook as AgentLoggingHook participant Hook as AgentLoggingHook
participant DB as agent_step participant DB as agent_step
Agent->>Hook: before_model(messages, sessionId) Agent->>Hook: before_model(messages, sessionId, runId)
Hook->>DB: insert step_index / agent_name / model_input Hook->>DB: insert step(session_id, run_id, model_input, step_index)
Agent-->>Agent: model call Agent->>Hook: after_model(output, sessionId, runId)
Agent->>Hook: after_model(messages, sessionId) Hook->>DB: update model_output, duration, token_count, has_tool_call
Hook->>DB: update model_output / thought / has_tool_call / duration / token_count
``` ```
当前记录: 新写入必须带 `run_id`,同时保留 `session_id` 便于粗粒度排查。
- `session_id`
- `step_index`
- `agent_name`
- `model_input`
- `model_output`
- `thought`
- `has_tool_call`
- `duration_ms`
- `token_count`
## 6. tool_invocation 写入 ## 6. tool_invocation 写入
工具调用记录真实工具事实,不记录模型猜测。 工具调用记录同样通过执行上下文拿到 `sessionId + runId`:
关键字段:
```text ```text
session_id ToolInvocationRecorder
step_id -> tool_invocation.session_id
tool_name -> tool_invocation.run_id
input_params -> retrieval_details / evidence_refs
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
``` ```
对 `lookup_knowledge`,`retrieval_details` 会承载 L0/L1、领域、证据状态、去重等检索细节。对日志、指标和知识库工具,`retrieval_details.evidence_refs` 会记录 Gatekeeper 可核验的最小证据引用: Verifier、Gatekeeper 和 EvaluationService 应按 `run_id` 读取工具调用,避免同一 session 的其他 run 参与评分或证据校验。
```json
{
"evidence_refs": [
{
"raw_path": "$.logs[0]",
"text": "最小证据文本"
}
]
}
```
当工具明确没有返回匹配证据时,可以记录 `raw_path=$.no_evidence`。该路径只表示“本次工具查询未检索到匹配证据”,不表示问题被排除。
## 7. Trace API 聚合 ## 7. Trace API 聚合
```text ```text
GET /api/diagnosis/{sessionId}/trace GET /api/diagnosis/{sessionId}/trace
GET /api/diagnosis/{sessionId}/trace?runId=run-...
``` ```
聚合逻辑: 聚合逻辑:
```text ```text
diagnosis_session by sessionId diagnosis_run by sessionId + runId
+ agent_step ordered by step_index + chat_session metadata when available
+ tool_invocation ordered by id + agent_step where run_id = runId, ordered by the Trace API
+ tool_invocation where run_id = runId order by id
-> DiagnosisTraceResponse -> DiagnosisTraceResponse
``` ```
Trace 视图回答的问题: 当 `runId` 缺失时,Trace API 为兼容旧客户端解析最新 run,并在响应中返回 resolved `runId`。当 `runId` 属于其他 `sessionId` 时,API 必须拒绝,不能泄漏其他会话的 Trace。
- 这次诊断是否成功?
- 哪些 Agent 参与了?
- 每一步模型输入输出是什么摘要?
- 调用了哪些工具?
- 工具返回了什么证据?
- Gatekeeper / Verifier / Composer / AIOps rule 是否通过?
- 用户是否反馈有用?
## 8. Chat 与 AIOps 差异 ## 8. Chat 与 AIOps 差异
@@ -189,22 +144,17 @@ Trace 视图回答的问题:
| 编排方式 | `SequentialAgent`: Planner -> Executor -> Gatekeeper -> Verifier -> Composer | `SupervisorAgent`: Planner + Executor | | 编排方式 | `SequentialAgent`: Planner -> Executor -> Gatekeeper -> Verifier -> Composer | `SupervisorAgent`: Planner + Executor |
| 自评估 | `rule_evaluation` + `verifier_evaluation` | `aiops_rule_evaluation` | | 自评估 | `rule_evaluation` + `verifier_evaluation` | `aiops_rule_evaluation` |
| 答案字段 | Chat 最终答复 | 告警分析报告 | | 答案字段 | Chat 最终答复 | 告警分析报告 |
| payload | 用户自然语言 + history | alert payload 或 auto-discovery | | runId 暴露 | `/api/chat` JSON response | `/api/ai_ops` SSE metadata message |
## 9. 清理与边界 ## 9. 清理与边界
当前会话持久化边界: - Redis 会话历史用于多轮上下文,不是长期审计记录。
- MySQL `diagnosis_run + agent_step + tool_invocation` 是主要可回放来源。
- MySQL Trace 记录是主要可回放来源。 - `chat_session.expires_at` 只是目录元数据;Redis 消息历史可独立过期。
- Chat 历史仍可作为请求上下文传入 Agent,但不是本文档的主持久化模型。 - `RetrievedDocTracker` 仍是 session 级运行时去重状态,诊断结束后清理。
- Redis 主会话存储是历史设计,不作为当前架构事实。
- `RetrievedDocTracker` 是 session 级运行时去重状态,诊断结束后清理。
## 10. 后续增强 ## 10. 后续增强
可考虑:
1. Trace API 增加更结构化的 `self_evaluation` 展示。 1. Trace API 增加更结构化的 `self_evaluation` 展示。
2. `agent_step` 与 `tool_invocation.step_id` 建立更严格关联。 2. `agent_step` 与 `tool_invocation.step_id` 建立更严格关联。
3. 对多轮同 session 诊断增加 run id,避免复用 session 时历史记录混杂。 3. 旧 `diagnosis_session` 只读观察期结束后,再评估数据库层面的约束收紧或归档策略。
4. 为 Trace 增加导出能力,服务面试演示和回归分析。
+25 -8
View File
@@ -67,10 +67,23 @@ Invoke-RestMethod `
-Body $body -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.success = true`
- `data.sessionId = mvp-demo-payment-timeout-001` - `data.sessionId = mvp-demo-payment-timeout-001`
- `data.runId` 为本次诊断运行的唯一 ID
- `data.answer` 包含诊断答复 - `data.answer` 包含诊断答复
## 4. 查询 Trace ## 4. 查询 Trace
@@ -78,13 +91,15 @@ Invoke-RestMethod `
```powershell ```powershell
Invoke-RestMethod ` Invoke-RestMethod `
-Method Get ` -Method Get `
-Uri "http://localhost:9900/api/diagnosis/$sessionId/trace" -Uri "http://localhost:9900/api/diagnosis/$sessionId/trace?runId=$runId"
``` ```
期望结果: 期望结果:
- `code = 200` - `code = 200`
- `data.runId` 等于 `$runId`
- `data.session.sessionId` 等于 Chat session id - `data.session.sessionId` 等于 Chat session id
- `data.run.runId` 等于 `$runId`
- `data.steps` 包含 planner / executor / verifier 等步骤 - `data.steps` 包含 planner / executor / verifier 等步骤
- `data.toolInvocations` 包含 `lookup_knowledge`、`query_logs`、`query_metrics` 等证据工具 - `data.toolInvocations` 包含 `lookup_knowledge`、`query_logs`、`query_metrics` 等证据工具
- `data.session.selfEvaluation` 包含 verifier 或 rule evaluation - `data.session.selfEvaluation` 包含 verifier 或 rule evaluation
@@ -96,6 +111,7 @@ Invoke-RestMethod `
```powershell ```powershell
$feedback = @{ $feedback = @{
sessionId = $sessionId sessionId = $sessionId
runId = $runId
feedback = "useful" feedback = "useful"
} | ConvertTo-Json } | ConvertTo-Json
@@ -109,7 +125,8 @@ Invoke-RestMethod `
期望结果: 期望结果:
- `success = true` - `success = true`
- 后续 Trace 中 `data.session.feedback = useful` - `runId = $runId`
- 后续精确 Trace 中 `data.session.feedback = useful`
- useful 反馈会尝试沉淀 `case_library` - useful 反馈会尝试沉淀 `case_library`
## 6. AIOps 告警诊断 Demo ## 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 告警分析报告 - 后续流式输出包含 AIOps 告警分析报告
- 报告聚焦输入的 `HighCPUUsage/payment-service` - 报告聚焦输入的 `HighCPUUsage/payment-service`
- 同一 session 的 Trace 中 `data.session.agentFlow = AI_OPS` - 精确 Trace 中 `data.session.agentFlow = AI_OPS`
- `data.session.answer` 包含最终告警报告 - `data.session.answer` 包含最终告警报告
- `data.toolInvocations` 包含证据工具调用 - `data.toolInvocations` 包含证据工具调用
查询 AIOps Trace: 查询 AIOps Trace 时优先使用 SSE metadata 中的 runId:
```powershell ```powershell
Invoke-RestMethod ` Invoke-RestMethod `
-Method Get ` -Method Get `
-Uri "http://localhost:9900/api/diagnosis/$aiopsSessionId/trace" -Uri "http://localhost:9900/api/diagnosis/$aiopsSessionId/trace?runId=$aiopsRunId"
``` ```
## 7. Demo 主线 ## 7. Demo 主线
@@ -155,7 +172,7 @@ Invoke-RestMethod `
Chat 主线: Chat 主线:
```text ```text
一个 session id 一个 session id + 一个 run id
-> 用户问题 -> 用户问题
-> 多 Agent 执行 -> 多 Agent 执行
-> 证据工具 -> 证据工具
@@ -168,7 +185,7 @@ Chat 主线:
AIOps 主线: AIOps 主线:
```text ```text
一个 session id 一个 session id + 一个 run id
-> 告警 payload -> 告警 payload
-> AIOps Planner / Executor -> AIOps Planner / Executor
-> 证据工具 -> 证据工具
+7 -7
View File
@@ -2,7 +2,7 @@
## 1. 目标 ## 1. 目标
验证旧版 `/api/ai_ops` 入口可以作为可追踪的告警触发诊断入口,并且 payload 模式下报告聚焦输入告警。 验证 `/api/ai_ops` 入口可以作为可追踪的告警触发诊断入口,并且 payload 模式下报告聚焦输入告警。
## 2. 输入 ## 2. 输入
@@ -25,14 +25,14 @@
## 3. 验收标准 ## 3. 验收标准
1. SSE 流输出 `session` 消息,且包含请求中的 session id。 1. SSE 流首条输出 `type=metadata` 的 `message` 事件,且包含请求中的 session id 和本次 AIOps run id。
2. AIOps 执行创建或更新 `diagnosis_session`,并写入 `agent_flow = AI_OPS`。 2. AIOps 执行创建 `diagnosis_run`,并写入 `agent_flow = AI_OPS`。
3. 持久化的 session query 包含告警名、服务名、等级、时间范围和描述。 3. 持久化的 run query 包含告警名、服务名、等级、时间范围和描述。
4. 如果生成最终报告,`diagnosis_session.answer` 包含该报告。 4. 如果生成最终报告,`diagnosis_run.answer` 包含该报告。
5. `GET /api/diagnosis/{sessionId}/trace` 返回 AIOps session、按顺序排列的 agent steps 和 tool invocations。 5. `GET /api/diagnosis/{sessionId}/trace?runId=...` 返回 AIOps run、按顺序排列的 agent steps 和 tool invocations。
6. payload 模式下,报告主线聚焦 `HighCPUUsage/payment-service`。 6. payload 模式下,报告主线聚焦 `HighCPUUsage/payment-service`。
7. 其他活跃告警最多作为相关风险或上下文出现,不应展开成完整独立根因章节。 7. 其他活跃告警最多作为相关风险或上下文出现,不应展开成完整独立根因章节。
8. `self_evaluation.aiops_rule_evaluation` 存在,并能反映报告完整性、payload 聚焦和证据工具覆盖情况。 8. `diagnosis_run.self_evaluation.aiops_rule_evaluation` 存在,并能反映报告完整性、payload 聚焦和证据工具覆盖情况。
## 4. 已知边界 ## 4. 已知边界
+4 -4
View File
@@ -13,7 +13,7 @@
关键主张不是“模型回答了一次”,而是: 关键主张不是“模型回答了一次”,而是:
```text ```text
系统能展示用了什么证据、答案如何被检查、如何用 sessionId 回放整次诊断。 系统能展示用了什么证据、答案如何被检查、如何用 sessionId + runId 精确回放这次诊断。
``` ```
## 2. Demo 流程 ## 2. Demo 流程
@@ -23,7 +23,7 @@
3. 打开 `mvp/demo/output/chat-response.json`。 3. 打开 `mvp/demo/output/chat-response.json`。
4. 打开 `mvp/demo/output/trace-response.json`。 4. 打开 `mvp/demo/output/trace-response.json`。
5. 指出证据工具和 verifier evaluation。 5. 指出证据工具和 verifier evaluation。
6. 提交 feedback,并展示它挂在同一个 session 上。 6. 提交 feedback,并展示它挂在当前 run 上。
7. 打开 `evidence-pipeline-scenarios.md`,说明 PASS / LOW_CONFID / REJECT / no-evidence 的固定回归矩阵。 7. 打开 `evidence-pipeline-scenarios.md`,说明 PASS / LOW_CONFID / REJECT / no-evidence 的固定回归矩阵。
## 3. 命令 ## 3. 命令
@@ -59,7 +59,7 @@ mvp/demo/output/chat-response.json
话术: 话术:
```text ```text
这是用户看到的答案。这里的 sessionId 是稳定的,所以我后面可以追踪这一次回答是怎么来的。 这是用户看到的答案。这里的 sessionId 是稳定的,同时响应里会返回 runId,所以我后面可以精确追踪这一次回答是怎么来的。
``` ```
### 4.2 证据 Trace ### 4.2 证据 Trace
@@ -112,7 +112,7 @@ mvp/demo/output/feedback-response.json
话术: 话术:
```text ```text
feedback 会挂在同一个 diagnosis session 上。 feedback 会挂在当前 diagnosis run 上。
这让后续挖掘 useful case 或 not_useful bad case 成为可能。 这让后续挖掘 useful case 或 not_useful bad case 成为可能。
``` ```
+6 -4
View File
@@ -12,14 +12,16 @@
## 3. 验收标准 ## 3. 验收标准
1. Chat 返回成功答复,且 session id 与请求一致。 1. Chat 返回成功答复,且 session id 与请求一致,并返回本次诊断的 run id。
2. Trace API 返回 session 元数据、最终答案、按顺序排列的 agent steps 和 tool invocations。 2. Trace API 使用 `sessionId + runId` 返回会话元数据、运行摘要、最终答案、按顺序排列的 agent steps 和 tool invocations。
3. Trace 中有足够证据说明用了哪些工具,以及 verifier / self-evaluation 是否已持久化。 3. Trace 中有足够证据说明用了哪些工具,以及 verifier / self-evaluation 是否已持久化。
4. 可以使用同一个 session id 提交反馈。 4. 可以使用同一个 session id 和本次 run id 提交反馈。
5. 后续 Trace 查询能看到已持久化的 feedback 值。 5. 后续精确 Trace 查询能看到已持久化的 feedback 值。
## 4. 需要检查的 Trace 字段 ## 4. 需要检查的 Trace 字段
- `data.runId`
- `data.run.runId`
- `data.session.query` - `data.session.query`
- `data.session.answer` - `data.session.answer`
- `data.session.selfEvaluation` - `data.session.selfEvaluation`
@@ -78,9 +78,14 @@ $chat = Invoke-RestMethod @chatRequest
$chatPath = Join-Path $OutputDir "chat-response.json" $chatPath = Join-Path $OutputDir "chat-response.json"
$chat | ConvertTo-Json -Depth 30 | Set-Content -Encoding UTF8 -Path $chatPath $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 = @{ $traceRequest = @{
Method = "Get" Method = "Get"
Uri = "$BaseUrl/api/diagnosis/$SessionId/trace" Uri = "$BaseUrl/api/diagnosis/$SessionId/trace?runId=$([System.Uri]::EscapeDataString($runId))"
} }
$trace = Invoke-RestMethod @traceRequest $trace = Invoke-RestMethod @traceRequest
@@ -89,6 +94,7 @@ $trace | ConvertTo-Json -Depth 80 | Set-Content -Encoding UTF8 -Path $tracePath
$feedbackBody = @{ $feedbackBody = @{
sessionId = $SessionId sessionId = $SessionId
runId = $runId
feedback = "useful" feedback = "useful"
} | ConvertTo-Json } | ConvertTo-Json
@@ -136,6 +142,7 @@ $summaryPath = Join-Path $OutputDir "interview-demo-summary.json"
$summary = [ordered]@{ $summary = [ordered]@{
sessionId = $SessionId sessionId = $SessionId
runId = $runId
baseUrl = $BaseUrl baseUrl = $BaseUrl
chatSuccess = $chat.data.success chatSuccess = $chat.data.success
verdict = $verdict verdict = $verdict
@@ -26,15 +26,22 @@ $chat = Invoke-RestMethod `
$chat | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -Path "$OutputDir/chat-response.json" $chat | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -Path "$OutputDir/chat-response.json"
Write-Host "已保存 Chat 响应: $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 ` $trace = Invoke-RestMethod `
-Method Get ` -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" $trace | ConvertTo-Json -Depth 50 | Set-Content -Encoding UTF8 -Path "$OutputDir/trace-response.json"
Write-Host "已保存 Trace 响应: $OutputDir/trace-response.json" Write-Host "已保存 Trace 响应: $OutputDir/trace-response.json"
$feedbackBody = @{ $feedbackBody = @{
sessionId = $SessionId sessionId = $SessionId
runId = $runId
feedback = "useful" feedback = "useful"
} | ConvertTo-Json } | ConvertTo-Json
+4 -3
View File
@@ -53,7 +53,7 @@ mvp/demo/output/interview-demo-summary.json
```text ```text
这里我用固定 sessionId 跑一个支付接口超时问题。 这里我用固定 sessionId 跑一个支付接口超时问题。
固定 sessionId 的好处是,后面 trace 和 feedback 都能关联到同一次诊断。 固定 sessionId 的好处是保留多轮上下文;每次诊断还会返回 runId,后面 trace 和 feedback 都用这个 runId 精确关联到同一次运行。
``` ```
## 3. 展示用户答案 ## 3. 展示用户答案
@@ -68,6 +68,7 @@ mvp/demo/output/chat-response.json
```text ```text
data.sessionId data.sessionId
data.runId
data.answer data.answer
``` ```
@@ -76,7 +77,7 @@ data.answer
```text ```text
这是用户看到的答案。 这是用户看到的答案。
但这个项目的重点不是这段文字,而是这段文字是否有证据链。 但这个项目的重点不是这段文字,而是这段文字是否有证据链。
接下来我用同一个 sessionId 查 trace。 接下来我用同一个 sessionId 加 runId 查 trace。
``` ```
## 4. 展示 Trace ## 4. 展示 Trace
@@ -182,7 +183,7 @@ caseId
现场话术: 现场话术:
```text ```text
用户反馈 useful 会写回同一个 diagnosis_session。 用户反馈 useful 会写回当前 diagnosis_run。
后端会把这次诊断自动沉淀到 case_library,后续可以做案例检索或 bad case 分析。 后端会把这次诊断自动沉淀到 case_library,后续可以做案例检索或 bad case 分析。
这里 status 和 feedback 是分开的: 这里 status 和 feedback 是分开的:
+5 -4
View File
@@ -6,14 +6,15 @@
| JSON path | 检查点 | 面试讲点 | | 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.query` | 是否包含支付超时问题 | Trace 记录了原始用户意图 |
| `data.session.answer` | 是否包含最终诊断答案 | 最终答案没有脱离 Trace | | `data.session.answer` | 是否包含最终诊断答案 | 最终答案没有脱离 Trace |
| `data.session.selfEvaluation` | 是否包含 verifier 或 rule evaluation | 答案经过质量门,不只是模型原始输出 | | `data.session.selfEvaluation` | 是否包含 verifier 或 rule evaluation | 答案经过质量门,不只是模型原始输出 |
| `data.session.selfEvaluation.verifier_evaluation.gatekeeper_result.rule_set_version` | 如果是 Chat V2 链路,是否记录 Gatekeeper 规则版本 | 安全规则可审计、可回归 | | `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.version` | 如果是 Chat V2 链路,是否记录 Prompt 审计版本 | Prompt 变更可解释、可回归 |
| `data.session.selfEvaluation.verifier_evaluation.prompt_audit.prompts[*].version` | 是否记录 planner / executor / verifier / composer 版本 | 便于定位 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 步骤 ## 2. Agent 步骤
@@ -48,10 +49,10 @@
## 5. 好的结果长什么样 ## 5. 好的结果长什么样
```text ```text
同一个 session id 同一个 session id + run id
-> 最终答案 -> 最终答案
-> 持久化 agent steps -> 持久化 agent steps
-> 持久化 evidence tool calls -> 持久化 evidence tool calls
-> verifier / self-evaluation -> verifier / self-evaluation
-> feedback attached to the same session -> feedback attached to the same run
``` ```
+9 -5
View File
@@ -5,14 +5,15 @@
## 定位 ## 定位
`agent_step` 记录一次诊断过程中每个 Agent 步骤的模型输入、输出、耗时和 Token 消耗。页面展示执行链路时应优先按 `step_index` 排序。 `agent_step` 记录一次诊断运行中每个 Agent 步骤的模型输入、输出、耗时和 Token 消耗。`run_id` 是执行隔离边界;Trace 页面展示顺序以 Trace API 返回顺序为准。
## 字段 ## 字段
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---| |---|---|---|---|
| `id` | BIGINT | 是 | 自增主键 | | `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 开始 | | `step_index` | INT | 是 | 步骤序号,从 0 开始 |
| `agent_name` | VARCHAR(32) | 是 | Agent 名称,例如 planner、executor、verifier、composer | | `agent_name` | VARCHAR(32) | 是 | Agent 名称,例如 planner、executor、verifier、composer |
| `model_input` | TEXT | 否 | 模型输入摘要;`V006` 已从 JSON 改为 TEXT | | `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 类型筛选 | | `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`,但当前允许为空且不强制外键。 - `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 提前,通常意味着编排或记录顺序有问题。 - Verifier 应在 Executor 循环完成后出现;如果 `step_index` 中 Verifier 提前,通常意味着编排或记录顺序有问题。
+14 -8
View File
@@ -1,6 +1,6 @@
# MVP 数据表索引 # MVP 数据表索引
**更新日期**:2026-07-09 **更新日期**:2026-07-10
**状态**:当前表文档入口 **状态**:当前表文档入口
本目录保存当前 MVP 使用的数据表说明。详细结构以 Flyway migration 和实体类为准;本目录用于面试讲解、排查索引和快速理解数据流。 本目录保存当前 MVP 使用的数据表说明。详细结构以 Flyway migration 和实体类为准;本目录用于面试讲解、排查索引和快速理解数据流。
@@ -9,26 +9,32 @@
| 表 | 用途 | 文档 | | 表 | 用途 | 文档 |
|---|---|---| |---|---|---|
| `diagnosis_session` | 会话级主记录,保存 query、状态、最终答案和自评估 | [诊断会话表-diagnosis_session.md](诊断会话表-diagnosis_session.md) | | `chat_session` | 会话目录元数据,保存同一个 `sessionId` 的多轮会话状态快照 | [聊天会话表-chat_session.md](聊天会话表-chat_session.md) |
| `agent_step` | Agent 步骤记录,按 `step_index` 回放执行链路 | [Agent步骤表-agent_step.md](Agent步骤表-agent_step.md) | | `diagnosis_run` | 运行级主记录,保存一次 Chat/AIOps 诊断的 query、状态、答案、自评估和反馈 | [诊断运行表-diagnosis_run.md](诊断运行表-diagnosis_run.md) |
| `tool_invocation` | 工具调用记录,支撑 Trace、Verifier 和评测 | [工具调用表-tool_invocation.md](工具调用表-tool_invocation.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) | | `api_document` | 知识库文档元数据,和向量库 chunk 通过 `doc_id` 关联 | [文档元数据表-api_document.md](文档元数据表-api_document.md) |
| `knowledge_domain` | 知识域元数据,支撑 RAG domain hint 和检索策略 | [知识域表-knowledge_domain.md](知识域表-knowledge_domain.md) | | `knowledge_domain` | 知识域元数据,支撑 RAG domain hint 和检索策略 | [知识域表-knowledge_domain.md](知识域表-knowledge_domain.md) |
| `case_library` | 用户反馈沉淀出的高质量诊断案例 | [案例库表-case_library.md](案例库表-case_library.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 ```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 diagnosis_session.session_id
-> agent_step.session_id -> historical compatibility / rollback only
-> tool_invocation.session_id
-> case_library.diagnosis_id
api_document.doc_id api_document.doc_id
-> vector chunk metadata.docId / doc_id -> vector chunk metadata.docId / doc_id
@@ -5,14 +5,15 @@
## 定位 ## 定位
`tool_invocation` 记录 Agent 显式调用工具的事实,包括工具名、入参、输出摘要、检索层级、证据引用和失败信息。它是 Trace、Verifier、评测和人工排查的共同数据源。 `tool_invocation` 记录 Agent 在一次诊断运行中显式调用工具的事实,包括工具名、入参、输出摘要、检索层级、证据引用和失败信息。它是 Trace、Verifier、评测和人工排查的共同数据源。
## 字段 ## 字段
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---| |---|---|---|---|
| `id` | BIGINT | 是 | 自增主键 | | `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` | | `step_id` | BIGINT | 否 | 可关联 `agent_step.id` |
| `tool_name` | VARCHAR(64) | 是 | 工具名称,例如 `lookup_knowledge`、日志查询、指标查询 | | `tool_name` | VARCHAR(64) | 是 | 工具名称,例如 `lookup_knowledge`、日志查询、指标查询 |
| `input_params` | JSON | 是 | 工具入参 | | `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_tool_name` | `tool_name` | 按工具类型排查 |
| `idx_retrieval_layer` | `retrieval_layer` | 观察 RAG L0/L1 行为 | | `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`,但当前不强制。 - `tool_invocation.step_id` 可关联 `agent_step.id`,但当前不强制。
## 关键 JSON ## 关键 JSON
+5 -4
View File
@@ -5,7 +5,7 @@
## 定位 ## 定位
`case_library` 保存高质量诊断案例,用于后续相似案例推荐和知识沉淀。当前自动沉淀路径来自 `useful` 用户反馈:系统把 `diagnosis_session` 中的 query 和 answer 映射为案例内容。 `case_library` 保存高质量诊断案例,用于后续相似案例推荐和知识沉淀。当前自动沉淀路径来自 `useful` 用户反馈:新数据把 `diagnosis_run` 中的 query 和 answer 映射为案例内容。
## 字段 ## 字段
@@ -13,7 +13,7 @@
|---|---|---|---| |---|---|---|---|
| `id` | BIGINT | 是 | 自增主键 | | `id` | BIGINT | 是 | 自增主键 |
| `case_id` | VARCHAR(64) | 是 | 案例唯一 ID | | `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` | | `source_type` | VARCHAR(16) | 否 | 来源类型:`AUTO` 或 `MANUAL` |
| `fault_category` | VARCHAR(32) | 否 | 故障类别,实体侧使用 `FaultCategory` | | `fault_category` | VARCHAR(32) | 否 | 故障类别,实体侧使用 `FaultCategory` |
| `fault_source` | VARCHAR(128) | 否 | 故障源,例如服务、系统或省份 | | `fault_source` | VARCHAR(128) | 否 | 故障源,例如服务、系统或省份 |
@@ -35,16 +35,17 @@
| `idx_error_code` | `error_code` | 按错误码精确匹配 | | `idx_error_code` | `error_code` | 按错误码精确匹配 |
| `idx_fault_source` | `fault_source` | 按故障源筛选 | | `idx_fault_source` | `fault_source` | 按故障源筛选 |
| `idx_fault_target` | `fault_target(100)` | 按故障目标筛选 | | `idx_fault_target` | `fault_target(100)` | 按故障目标筛选 |
| `idx_diagnosis_id` | `diagnosis_id` | 追溯来源会话 | | `idx_diagnosis_id` | `diagnosis_id` | 追溯来源运行或历史会话 |
| `idx_reference_count` | `reference_count` | 推荐排序 | | `idx_reference_count` | `reference_count` | 推荐排序 |
| `idx_created_at` | `created_at` | 时间排序 | | `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_id`。
## 注意点 ## 注意点
- 旧文档里提到的 `diagnosis_record` 已被 `V007` 删除,不再是当前主模型。 - 旧文档里提到的 `diagnosis_record` 已被 `V007` 删除,不再是当前主模型。
- 查询新自动案例时优先按 `run_id` 追溯;遇到旧值时再按历史 `session_id` 解释。
- 当前自动沉淀仍比较粗:`root_cause` 和 `solution` 都可能来自完整 answer。后续可从结构化结论中拆分根因、证据和修复建议。 - 当前自动沉淀仍比较粗:`root_cause` 和 `solution` 都可能来自完整 answer。后续可从结构化结论中拆分根因、证据和修复建议。
@@ -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`。
@@ -1,18 +1,18 @@
# 诊断会话表:diagnosis_session # 诊断会话表:diagnosis_session
**状态**:当前主表 **状态**:历史兼容和回滚表
**来源**:`V005__create_session_storage.sql`、`V008__add_answer_to_diagnosis_session.sql`、`DiagnosisSession` **来源**:`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 | 是 | 自增主键 | | `id` | BIGINT | 是 | 自增主键 |
| `session_id` | VARCHAR(64) | 是 | 会话唯一 ID,Trace API 和反馈接口使用它 | | `session_id` | VARCHAR(64) | 是 | 旧版会话唯一 ID,也是兼容 run 回填来源 |
| `query` | TEXT | 是 | 用户原始问题或 AIOps 输入摘要 | | `query` | TEXT | 是 | 用户原始问题或 AIOps 输入摘要 |
| `status` | VARCHAR(16) | 否 | `PENDING`、`RUNNING`、`SUCCESS`、`FAILED` | | `status` | VARCHAR(16) | 否 | `PENDING`、`RUNNING`、`SUCCESS`、`FAILED` |
| `agent_flow` | VARCHAR(32) | 否 | `CHAT` 或 `AI_OPS` | | `agent_flow` | VARCHAR(32) | 否 | `CHAT` 或 `AI_OPS` |
@@ -37,11 +37,12 @@
## 关系 ## 关系
- `agent_step.session_id` 逻辑关联 `diagnosis_session.session_id`。 - 迁移时每条 `diagnosis_session` 会生成一条兼容 `diagnosis_run`。
- `tool_invocation.session_id` 逻辑关联 `diagnosis_session.session_id`。 - 历史 `agent_step.run_id` 和 `tool_invocation.run_id` 会尽量从兼容 `diagnosis_run` 回填。
- `case_library.diagnosis_id` 在自动生成案例时保存 `diagnosis_session.session_id`。 - 历史自动案例可能仍使用 `case_library.diagnosis_id = diagnosis_session.session_id`。
## 注意点 ## 注意点
- 当前没有数据库外键,Trace 聚合依赖 `session_id`。 - 新执行不应再把 query、answer、self_evaluation、feedback、统计计数写入本表。
- `self_evaluation` 是扩展容器,里面可能包含 `rule_evaluation`、`verifier_evaluation`、`aiops_rule_evaluation`。 - 新 Trace 聚合优先读取 `diagnosis_run + agent_step.run_id + tool_invocation.run_id`。
- 历史 fallback 仅在没有 run-backed 数据时读取本表。
@@ -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,但旧混合数据不能被还原成真实多轮边界。
@@ -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.
@@ -51,9 +51,9 @@
## 6. Demo, Trace UI, Documentation, and Verification ## 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. - [x] 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. - [x] 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. - [x] 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/`. - [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/`.
- [ ] 6.5 Run or explicitly evaluate the relevant baseline diff command and document whether drift is expected or a regression. - [x] 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.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.
+75 -19
View File
@@ -9,6 +9,7 @@ class SuperBizAgentApp {
this.chatHistories = this.loadChatHistories(); // 所有历史对话 this.chatHistories = this.loadChatHistories(); // 所有历史对话
this.isCurrentChatFromHistory = false; // 标记当前对话是否是从历史记录加载的 this.isCurrentChatFromHistory = false; // 标记当前对话是否是从历史记录加载的
this.lastTraceSessionId = this.loadLastTraceSessionId(); this.lastTraceSessionId = this.loadLastTraceSessionId();
this.lastTraceRunId = this.loadLastTraceRunId();
this.initializeElements(); this.initializeElements();
this.bindEvents(); this.bindEvents();
@@ -310,13 +311,14 @@ class SuperBizAgentApp {
id: this.sessionId, id: this.sessionId,
title: title, title: title,
messages: [...this.currentChatHistory], messages: [...this.currentChatHistory],
lastRunId: this.sessionId === this.lastTraceSessionId ? this.lastTraceRunId : '',
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
}; };
// 添加到历史记录列表的开头 // 添加到历史记录列表的开头
this.chatHistories.unshift(chatHistory); this.chatHistories.unshift(chatHistory);
this.rememberTraceSessionId(this.sessionId); this.rememberTraceTarget(this.sessionId, null);
// 限制历史记录数量(最多保存50条) // 限制历史记录数量(最多保存50条)
if (this.chatHistories.length > 50) { if (this.chatHistories.length > 50) {
@@ -344,6 +346,9 @@ class SuperBizAgentApp {
const history = this.chatHistories[existingIndex]; const history = this.chatHistories[existingIndex];
history.messages = [...this.currentChatHistory]; history.messages = [...this.currentChatHistory];
history.updatedAt = new Date().toISOString(); 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'); const firstUserMessage = this.currentChatHistory.find(msg => msg.type === 'user');
@@ -444,7 +449,7 @@ class SuperBizAgentApp {
// 加载历史对话 // 加载历史对话
this.sessionId = history.id; this.sessionId = history.id;
this.rememberTraceSessionId(this.sessionId); this.rememberTraceTarget(this.sessionId, history.lastRunId || null);
this.currentChatHistory = [...history.messages]; this.currentChatHistory = [...history.messages];
this.isCurrentChatFromHistory = true; // 标记为从历史记录加载 this.isCurrentChatFromHistory = true; // 标记为从历史记录加载
@@ -487,16 +492,39 @@ class SuperBizAgentApp {
} }
} }
loadLastTraceRunId() {
try {
return localStorage.getItem('lastTraceRunId') || '';
} catch (e) {
return '';
}
}
rememberTraceSessionId(sessionId) { rememberTraceSessionId(sessionId) {
this.rememberTraceTarget(sessionId, null);
}
rememberTraceTarget(sessionId, runId) {
if (!sessionId) { if (!sessionId) {
return; return;
} }
this.lastTraceSessionId = sessionId; this.lastTraceSessionId = sessionId;
this.lastTraceRunId = runId || '';
try { try {
localStorage.setItem('lastTraceSessionId', sessionId); localStorage.setItem('lastTraceSessionId', sessionId);
if (runId) {
localStorage.setItem('lastTraceRunId', runId);
} else {
localStorage.removeItem('lastTraceRunId');
}
} catch (e) { } catch (e) {
// localStorage may be unavailable in private or restricted contexts. // 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(); this.updateTraceWorkbenchLink();
} }
@@ -508,9 +536,30 @@ class SuperBizAgentApp {
const sessionId = this.lastTraceSessionId const sessionId = this.lastTraceSessionId
|| (this.currentChatHistory.length > 0 ? this.sessionId : '') || (this.currentChatHistory.length > 0 ? this.sessionId : '')
|| (recentHistory ? recentHistory.id : ''); || (recentHistory ? recentHistory.id : '');
this.traceWorkbenchLink.href = sessionId if (!sessionId) {
? `trace.html?sessionId=${encodeURIComponent(sessionId)}` this.traceWorkbenchLink.href = 'trace.html';
: '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; const chatResponse = data.data;
if (chatResponse && chatResponse.success) { if (chatResponse && chatResponse.success) {
// 保存后端返回的 sessionId,用于 feedback 提交 // 保存后端返回的 sessionId/runId,用于 feedback 提交和 Trace 精确定位
if (chatResponse.sessionId) { if (chatResponse.sessionId) {
this.lastSessionId = chatResponse.sessionId; this.lastSessionId = chatResponse.sessionId;
this.rememberTraceSessionId(chatResponse.sessionId); this.lastRunId = chatResponse.runId || '';
this.rememberTraceTarget(chatResponse.sessionId, chatResponse.runId || null);
} }
// 成功:添加实际响应消息(即使 answer 为空也显示) // 成功:添加实际响应消息(即使 answer 为空也显示)
const answer = chatResponse.answer || '(无回复内容)'; const answer = chatResponse.answer || '(无回复内容)';
@@ -784,7 +834,9 @@ class SuperBizAgentApp {
console.log('[SSE调试] 解析JSON成功:', sseMessage); console.log('[SSE调试] 解析JSON成功:', sseMessage);
if (sseMessage && typeof sseMessage.type === 'string') { 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 || ''; const content = sseMessage.data || '';
fullResponse += content; fullResponse += content;
console.log('[SSE调试] 添加内容:', content); console.log('[SSE调试] 添加内容:', content);
@@ -897,7 +949,7 @@ class SuperBizAgentApp {
// assistant 消息末尾加反馈栏(流式消息完成后由 handleStreamComplete 添加) // assistant 消息末尾加反馈栏(流式消息完成后由 handleStreamComplete 添加)
if (type === 'assistant' && !isStreaming) { if (type === 'assistant' && !isStreaming) {
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId)); messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId, this.lastRunId || ''));
} }
messageDiv.appendChild(messageContentWrapper); messageDiv.appendChild(messageContentWrapper);
@@ -919,7 +971,7 @@ class SuperBizAgentApp {
} }
// 创建反馈栏,sessionId 闭包绑定,避免多轮对话时错位 // 创建反馈栏,sessionId 闭包绑定,避免多轮对话时错位
createFeedbackBar(sessionId) { createFeedbackBar(sessionId, runId = '') {
const bar = document.createElement('div'); const bar = document.createElement('div');
bar.className = 'feedback-bar'; bar.className = 'feedback-bar';
@@ -933,8 +985,8 @@ class SuperBizAgentApp {
notUsefulBtn.title = '无用'; notUsefulBtn.title = '无用';
notUsefulBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`; notUsefulBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId)); usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId, runId));
notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId)); notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId, runId));
bar.appendChild(usefulBtn); bar.appendChild(usefulBtn);
bar.appendChild(notUsefulBtn); bar.appendChild(notUsefulBtn);
@@ -942,7 +994,7 @@ class SuperBizAgentApp {
} }
// 提交反馈 // 提交反馈
async submitFeedback(feedback, barElement, sessionId) { async submitFeedback(feedback, barElement, sessionId, runId = '') {
if (!sessionId) return; if (!sessionId) return;
barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = true); barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = true);
@@ -951,7 +1003,7 @@ class SuperBizAgentApp {
const response = await fetch(`${this.apiBaseUrl}/feedback`, { const response = await fetch(`${this.apiBaseUrl}/feedback`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, feedback }) body: JSON.stringify({ sessionId, runId: runId || undefined, feedback })
}); });
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
@@ -1055,7 +1107,7 @@ class SuperBizAgentApp {
} }
// 流式完成后追加反馈栏 // 流式完成后追加反馈栏
if (messageContentWrapper && !messageContentWrapper.querySelector('.feedback-bar')) { 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) { for (const jsonStr of matches) {
try { try {
const sseMessage = JSON.parse(jsonStr); 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.lastSessionId = sseMessage.data;
this.rememberTraceSessionId(sseMessage.data); this.rememberTraceTarget(sseMessage.data, null);
} else if (sseMessage.type === 'content') { } else if (sseMessage.type === 'content') {
fullResponse += sseMessage.data || ''; fullResponse += sseMessage.data || '';
} else if (sseMessage.type === 'done') { } else if (sseMessage.type === 'done') {
@@ -1306,9 +1360,11 @@ class SuperBizAgentApp {
try { try {
const sseMessage = JSON.parse(rawData); const sseMessage = JSON.parse(rawData);
if (sseMessage && sseMessage.type) { 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.lastSessionId = sseMessage.data;
this.rememberTraceSessionId(sseMessage.data); this.rememberTraceTarget(sseMessage.data, null);
} else if (sseMessage.type === 'content') { } else if (sseMessage.type === 'content') {
fullResponse += sseMessage.data || ''; fullResponse += sseMessage.data || '';
if (loadingMessageElement) { if (loadingMessageElement) {
+27 -9
View File
@@ -36,7 +36,7 @@ class TraceWorkbench {
bindEvents() { bindEvents() {
this.sessionForm.addEventListener('submit', (event) => { this.sessionForm.addEventListener('submit', (event) => {
event.preventDefault(); event.preventDefault();
this.loadTrace(this.sessionIdInput.value.trim()); this.loadTrace(this.sessionIdInput.value.trim(), '');
}); });
this.sessionIdInput.addEventListener('focus', () => { this.sessionIdInput.addEventListener('focus', () => {
@@ -66,9 +66,10 @@ class TraceWorkbench {
bootstrapFromUrl() { bootstrapFromUrl() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const sessionId = params.get('sessionId') || this.getLastTraceSessionId(); const sessionId = params.get('sessionId') || this.getLastTraceSessionId();
const runId = params.get('runId') || '';
if (sessionId) { if (sessionId) {
this.sessionIdInput.value = sessionId; this.sessionIdInput.value = sessionId;
this.loadTrace(sessionId); this.loadTrace(sessionId, runId);
return; return;
} }
this.renderEmpty(); this.renderEmpty();
@@ -107,6 +108,7 @@ class TraceWorkbench {
id: history.id, id: history.id,
title: history.title || '未命名对话', title: history.title || '未命名对话',
updatedAt: history.updatedAt || history.createdAt || '', updatedAt: history.updatedAt || history.createdAt || '',
runId: history.lastRunId || '',
source: 'history' source: 'history'
})); }));
} }
@@ -237,7 +239,7 @@ class TraceWorkbench {
chooseSession(sessionId) { chooseSession(sessionId) {
this.sessionIdInput.value = sessionId; this.sessionIdInput.value = sessionId;
this.closeSessionOptions(); this.closeSessionOptions();
this.loadTrace(sessionId); this.loadTrace(sessionId, '');
} }
shortSessionId(sessionId) { shortSessionId(sessionId) {
@@ -264,18 +266,23 @@ class TraceWorkbench {
}); });
} }
async loadTrace(sessionId) { async loadTrace(sessionId, runId = '') {
if (!sessionId) { if (!sessionId) {
this.setState('请先输入会话 ID。', 'error'); this.setState('请先输入会话 ID。', 'error');
return; return;
} }
this.setState(`正在加载 ${sessionId}...`, 'loading'); const runSuffix = runId ? ` / ${runId}` : '';
this.updateUrl(sessionId); this.setState(`正在加载 ${sessionId}${runSuffix}...`, 'loading');
this.updateUrl(sessionId, runId);
this.loadButton.disabled = true; this.loadButton.disabled = true;
try { 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); const data = await this.handleResponse(response);
this.trace = data; this.trace = data;
this.selectedToolId = data.toolInvocations && data.toolInvocations.length this.selectedToolId = data.toolInvocations && data.toolInvocations.length
@@ -284,11 +291,14 @@ class TraceWorkbench {
this.activeFilter = 'all'; this.activeFilter = 'all';
try { try {
localStorage.setItem('lastTraceSessionId', sessionId); localStorage.setItem('lastTraceSessionId', sessionId);
if (data.runId) {
localStorage.setItem('lastTraceRunId', data.runId);
}
} catch (error) { } catch (error) {
// ignore storage failures // ignore storage failures
} }
this.renderTrace(); this.renderTrace();
this.setState(`已加载 ${sessionId}。`, ''); this.setState(`已加载 ${sessionId}${data.runId ? ` / ${data.runId}` : ''}。`, '');
} catch (error) { } catch (error) {
this.trace = null; this.trace = null;
this.renderEmpty(); this.renderEmpty();
@@ -324,9 +334,14 @@ class TraceWorkbench {
return value; return value;
} }
updateUrl(sessionId) { updateUrl(sessionId, runId = '') {
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.searchParams.set('sessionId', sessionId); url.searchParams.set('sessionId', sessionId);
if (runId) {
url.searchParams.set('runId', runId);
} else {
url.searchParams.delete('runId');
}
window.history.replaceState({}, '', url.toString()); window.history.replaceState({}, '', url.toString());
} }
@@ -399,6 +414,9 @@ class TraceWorkbench {
if (session.sessionId) { if (session.sessionId) {
subtitleParts.push(`会话 ${session.sessionId}`); subtitleParts.push(`会话 ${session.sessionId}`);
} }
if (this.trace && this.trace.runId) {
subtitleParts.push(`运行 ${this.trace.runId}`);
}
if (session.status) { if (session.status) {
subtitleParts.push(`状态 ${this.displayStatus(session.status)}`); subtitleParts.push(`状态 ${this.displayStatus(session.status)}`);
} }