feat(trace): finish run-aware demo verification
This commit is contained in:
@@ -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 和评测脚本证明诊断链路可解释、可回放、可对比。
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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 结果。
|
||||
|
||||
+70
-194
@@ -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 中结构化抽取。
|
||||
|
||||
@@ -391,7 +391,7 @@ Composer 位于 Verifier 之后,输入是 ChatService 过滤后的允许表达
|
||||
|
||||
## 7. Trace Persistence
|
||||
|
||||
`diagnosis_session.self_evaluation.verifier_evaluation` 持久化:
|
||||
`diagnosis_run.self_evaluation.verifier_evaluation` 持久化:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -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. 后续增强
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 控制告警聚焦 |
|
||||
|
||||
@@ -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` 只读观察期结束后,再评估数据库层面的约束收紧或归档策略。
|
||||
|
||||
+25
-8
@@ -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
|
||||
-> 证据工具
|
||||
|
||||
@@ -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. 已知边界
|
||||
|
||||
|
||||
@@ -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 成为可能。
|
||||
```
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 是分开的:
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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 提前,通常意味着编排或记录顺序有问题。
|
||||
|
||||
+14
-8
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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。后续可从结构化结论中拆分根因、证据和修复建议。
|
||||
|
||||
@@ -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
|
||||
|
||||
**状态**:当前主表
|
||||
**状态**:历史兼容和回滚表
|
||||
**来源**:`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 数据时读取本表。
|
||||
|
||||
@@ -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.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.
|
||||
|
||||
@@ -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 = `<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));
|
||||
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) {
|
||||
|
||||
@@ -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)}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user