docs(mvp): organize mvp documentation

This commit is contained in:
zhuyongxin
2026-07-09 13:40:08 +08:00
parent 9c9a0024d4
commit 841437fa06
57 changed files with 452 additions and 675 deletions
@@ -0,0 +1,17 @@
# 2026-07-09 MVP 文档清理归档
本目录保存本次清理中从当前入口移出的历史设计材料。这些文档仍有追溯价值,但不再代表当前可运行实现。
## 归档内容
| 目录 | 内容 | 归档原因 |
|---|---|---|
| `discuss/` | 早期 Executor Prompt、L0、RAG 讨论稿 | 已被当前 architecture、OpenSpec change 和 issue 取代 |
| `plan/` | `session-storage-design.md` | 会话存储已实现,当前表以 Flyway 和 `mvp/tables/` 为准 |
| `notes/` | 早期工程决策和 Demo Trace 验收笔记 | 相关内容已沉淀到 architecture、demo、eval 和 devflow |
## 使用原则
- 当前架构以 `mvp/architecture/` 为准。
- 当前表结构以 `mvp/tables/`、Flyway migration 和实体类为准。
- 当前问题入口以 `mvp/issues/README.md` 为准。
@@ -0,0 +1,79 @@
# 执行者 System Prompt
## 角色定位
你是诊断流程的**执行者**。你的任务非常明确:严格遵循规划者下发的任务清单,按步骤调用工具完成任务,并输出最终结果。
---
## 核心行为准则
### 1. 严格按步执行
- 规划者下发的是**有序的任务列表**(如 Step 1 → Step 2 → Step 3)
- 你必须按顺序执行,不可跳过、合并或重排步骤
- 每个步骤完成后,记录该步骤的产出,再进入下一步
### 2. 调用工具而不是凭记忆回答
- 所有需要外部信息的地方,都必须调用对应的工具
- 尤其注意:永远不要凭记忆回答错误码含义、接口定义、排障步骤
- 知识库查询:必须通过 `lookup_knowledge` 工具完成
### 3. 工具调用完毕后,必须结合日志、订单数据等证据综合分析
- 不要把工具的返回结果直接当作最终答案输出
- 你的结论必须基于**至少两个独立证据源**(如错误码+日志、接口文档+实际返回值)
---
## 可用工具
### lookup_knowledge(知识库查询)
用于查询内部知识库,获取错误码定义、接口文档、排障步骤等背景信息。
| 参数 | 说明 |
|------|------|
| `query_text` | 查询关键词。可以是错误码(ERR_TIMEOUT)、服务名(payment-gateway)、模糊问题(支付为什么失败) |
**内部机制**:
工具内部自动执行「先精确匹配(L0),未命中则语义检索(L1)」的两阶段检索逻辑,你无需关心哪一层。返回结果中包含 `match_type` 字段标记来源类型。
**返回字段**:
- `primary`:主要信息(L0 命中文档内容 或 L1 返回的 Top-1 片段)
- `primary.match_type`:`exact_l0`(精确匹配)或 `semantic_l1`(语义搜索)
- `primary.source`:信息来源的文件路径
**使用规则**:
- 当你查到了错误码、接口名、服务名时:**必须**调用此工具
- 当需要查排障步骤、业务流程、最佳实践时:**必须**调用此工具
- 对当前结果没有十足把握时:**建议**调用此工具验证
---
## 任务执行规范
### 1. 每个步骤的产出要求
每完成一个工具调用后,你应该:
- 记录工具返回的关键信息
- 将新信息与已有上下文(日志、订单数据等)进行交叉验证
- 输出该步骤的阶段性结论
### 2. 最终输出的报告格式
```yaml
## 诊断结论
**问题根因**:XXX
**证据链**:
1. 订单状态返回错误码 ERR_TIMEOUT
2. 知识库 lookup_knowledge("ERR_TIMEOUT") 返回:支付网关响应超时(>5秒)
3. 日志确认:14:32:15 请求耗时 5.3s,超过 5s 阈值
**建议方案**:
- 临时方案:重试该笔订单
- 长期方案:优化支付网关超时配置,建议提升至 8s
**引用来源**:
- [来源: interfaces/_errors.md]
@@ -0,0 +1,109 @@
Coding Agent 执行清单:L0+L1 混合检索 MVP 实现
你可以直接将以下完整的指令文档复制给你的 Coding Agent(如 Claude Code、Cursor),让它严格按照此规范实现。
📋 任务总览
在现有的 Milvus 向量检索(L1)基础之上,新增一层基于 Markdown 文件头的精确匹配检索(L0),构建一个“先精确、后语义”的混合检索工具 lookup_knowledge。
一、文件头规范定义
所有存放在 knowledge_base/ 目录下的 .md 知识库文档,必须在文件最顶部添加 YAML Frontmatter(被 --- 包裹),包含以下字段:
---
title: 支付网关错误码定义 # 【必填】文档标题
keywords: [ERR_TIMEOUT, 超时, 支付网关] # 【必填】核心关键词数组,用于精确匹配
summary: 记录了支付网关所有核心错误码的含义及排查方向。 # 【必填】文档一句话摘要,用于辅助匹配
sections: # 【可选】大文件的章节锚点,用于渐进式读取
超时排查: "## 1. 超时类错误"
限流排查: "## 2. 限流类错误"
---
# 这里是 Markdown 正文内容...
约束:
文件头必须在文件的最顶部,前面不能有空行。
keywords 仅需包含错误码、服务名、专有名词等适合精确匹配的词,不需要长句。
二、索引模块:启动加载与热更新
解析依赖:使用 python-frontmatter 库解析 MD 文件头。
启动扫描:项目启动时,递归扫描 knowledge_base/ 目录下所有 .md 文件,提取元数据。
内存结构:将提取的元数据组装为一个全局列表 KNOWLEDGE_INDEX,结构如下:
KNOWLEDGE_INDEX = [
{
"file": "knowledge_base/payment/errors.md",
"title": "支付网关错误码定义",
"keywords": ["ERR_TIMEOUT", "超时", "支付网关"],
"summary": "记录了...",
"sections": {"超时排查": "## 1. 超时类错误"}
}
]
热更新监听:使用 watchdog 库监听 knowledge_base/ 目录。当 .md 文件被新增或修改时,重新解析该文件头,并增量更新内存中的 KNOWLEDGE_INDEX 字典。
三、工具函数实现:lookup_knowledge
实现一个名为 lookup_knowledge 的工具供 Agent 调用。
1. 函数签名
def lookup_knowledge(query_text: str, section_title: str = None) -> dict:
2. 执行逻辑(严格按顺序执行)
Step 1: Layer 0 精确匹配(前置导航)
遍历 KNOWLEDGE_INDEX,将 query_text 与每个条目做大小写不敏感的匹配:
匹配规则:检查 query_text 是否包含 keywords 数组中的任一词汇;或者 query_text 是否与 summary 有一定的文本重合度(防自然语言漏匹配)。
命中处理:
如果命中,获取该条目的 file 路径。
如果传入了 section_title:通过正则表达式,从文件正文中截取 sections[section_title] 对应的标题及其下方段落内容返回。
如果未传入 section_title:直接 open() 读取文件内容,截取前 2000 字符返回。
标记 match_type: "exact_L0"。
Step 2: Layer 1 语义检索补充(原 RAG)
触发条件:无论 L0 是否命中,都调用现有的 Milvus 向量检索逻辑(BGE-M3 embedding + Milvus search),获取 Top-1 的相关 Chunk。
目的:作为补充上下文,提供语义关联信息。
标记:match_type: "semantic_L1"。
Step 3: 结果组装与返回
将 L0 和 L1 的结果组装成统一格式返回给 Agent。如果两层均无结果,found 置为 False。
3. 返回格式规范
{
"found": true,
"primary": {
"content": "文档前2000字或指定section内容...",
"source": "knowledge_base/payment/errors.md",
"match_type": "exact_L0"
},
"supplement": {
"content": "Milvus检索到的Top-1语义片段...",
"source": "其他文档路径",
"match_type": "semantic_L1"
}
}
(注:如果 L0 未命中,primary 字段为 null,仅返回 supplement。)
四、Agent 工具注册定义
将 lookup_knowledge 注册为 Agent 可用的工具,工具描述 JSON 如下:
{
"name": "lookup_knowledge",
"description": "查询知识库文档。系统会先尝试通过关键词精确匹配完整文档,并自动补充语义相关的片段。如果已知具体的文档章节,可传入 section_title 获取特定段落。",
"parameters": {
"type": "object",
"properties": {
"query_text": {
"type": "string",
"description": "查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'"
},
"section_title": {
"type": "string",
"description": "可选。如果primary结果返回了sections目录,可通过指定章节标题来获取该章节的详细内容,避免读取大文件超出长度限制。"
}
},
"required": ["query_text"]
}
}
五、实施与验收标准
请 Coding Agent 按以下步骤实施并自测:
安装依赖:pip install python-frontmatter watchdog
按照规范实现文件头解析与 watchdog 监听逻辑。
改造现有 Agent 代码,按上述逻辑实现 lookup_knowledge。
验收用例 1(L0 命中):创建带文件头的 MD,调用 lookup_knowledge("ERR_TIMEOUT"),验证返回的 primary 是否为完整 MD 内容,supplement 是否为 Milvus 的检索结果。
验收用例 2(L0 未命中):调用 lookup_knowledge("如何处理系统异常"),验证 primary 是否为 null,supplement 是否正常返回语义结果。
验收用例 3(热更新):在程序运行期间修改 MD 的文件头 keywords,再次查询验证内存索引是否已更新。
@@ -0,0 +1,168 @@
MVP 开发计划:知识库查询系统
一、需求概述
构建一个最小化但可运行的知识库查询系统,让诊断 Agent 在排查问题时,能够按需查询知识文档(如接口定义、错误码解释、排障指南)。
二、核心机制
整个系统围绕两个核心概念:索引目录 和 查询工具。
索引目录(_index.yaml):知识库的“地图”,记录每个文档的路径、摘要和关键词。
查询工具(lookup_knowledge):Agent 调用的函数,根据关键词匹配索引目录,返回对应文档内容。
三、知识库目录结构
知识库中的所有文档存放在 knowledge_base/ 目录下,按以下结构组织:
```
knowledge_base/
├── _index.yaml # MVP 阶段手动编写
├── interfaces/ # 接口文档
│ └── payment-gateway/
│ └── _errors.md # 支付网关特有错误码
└── troubleshooting/ # 排障指南
└── gateway-timeout.md # 支付网关超时排查
```
每个文档头部必须包含 YAML 元数据(front matter):
```
---
title: 支付网关错误码定义
type: error_definition
keywords: [ERR_TIMEOUT, 超时, timeout, ERR_BALANCE, 余额不足]
---
```
# 内容正文
四、_index.yaml 格式(MVP)
_index.yaml 内容示例:
files:
- file: "interfaces/payment-gateway/_errors.md"
summary: "支付网关特有错误码:ERR_TIMEOUT(超时)、ERR_BALANCE(余额不足)"
keywords: ["ERR_TIMEOUT", "超时", "timeout", "ERR_BALANCE", "余额不足"]
- file: "troubleshooting/gateway-timeout.md"
summary: "支付网关超时的排查步骤和解决方法"
keywords: ["超时", "timeout", "网关", "支付失败"]
说明:
file:相对于 knowledge_base/ 的路径
summary:一句话文档摘要
keywords:该文档相关的关键词(用于匹配查询)
五、lookup_knowledge 函数规范
5.1 函数签名
def lookup_knowledge(query_text: str) -> dict:
"""
功能:查询知识库,返回匹配的文档内容
参数:
query_text: str - 查询关键词(如错误码、接口名、问题描述)
返回:
dict - {"found": bool, "content": str, "source": str}
found: 是否找到匹配文档
content: 文档内容(前 2000 字符)
source: 匹配到的文件路径
"""
5.2 执行逻辑
读取 knowledge_base/_index.yaml 文件的 files 列表
遍历每个条目,检查 query_text 中的关键词是否出现在该条目的 summary 或 keywords 中
如果找到匹配:
根据 file 路径读取对应 Markdown 文件
返回内容的前 2000 字符
如果未找到匹配:
返回 {"found": False, "content": "", "source": ""}
5.3 关键约束
MVP 阶段不做向量搜索,仅做关键词匹配
关键词匹配规则:query_text 中包含的任何词,与 keywords 数组中的任何词相同即视为匹配
返回内容限制在 2000 字符以内,避免浪费 Token
不区分大小写(ERR_TIMEOUT 和 err_timeout 应匹配)
六、Agent 集成规范
6.1 工具注册
将 lookup_knowledge 注册为 Agent 的可用工具之一,工具定义如下:
{
"name": "lookup_knowledge",
"description": "查询知识库文档。传入你想查的关键词(如错误码、接口名、问题描述),返回对应的文档内容。",
"parameters": {
"type": "object",
"properties": {
"query_text": {
"type": "string",
"description": "查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'"
}
},
"required": ["query_text"]
}
}
6.2 System Prompt 指示
在传给 LLM 的 System Prompt 中,加入以下指示:
## 知识查询规则
当你诊断过程中拿到具体信息(如错误码、接口名)后,如需查询其定义或背景知识,请使用 `lookup_knowledge` 工具。典型触发时机:
- 查到了错误码,需要了解其含义
- 确认了接口名,需要查看接口文档
- 需要排障指南
示例:查到错误码 ERR_TIMEOUT → 调用 lookup_knowledge("ERR_TIMEOUT")
七、验收标准
7.1 功能测试
测试编号测试场景输入预期输出TC-001查询已知错误码"ERR_TIMEOUT"返回 _errors.md 中 ERR_TIMEOUT 的定义TC-002查询已知关键词"支付网关超时"返回 gateway-timeout.md 内容TC-003查询不存在的内容"未知错误码XYZ"返回 {"found": False}TC-004内容长度限制很长的文档返回内容不超过 2000 字符
7.2 集成测试
完成一次完整诊断流程:
用户提问: "订单123为什么支付失败"
→ Agent 查订单状态 → 发现错误码 ERR_TIMEOUT
→ Agent 调用 lookup_knowledge("ERR_TIMEOUT") → 获取错误码定义
→ Agent 结合日志输出诊断报告
八、实施步骤
Step 1:准备知识库
创建 knowledge_base/ 目录
创建至少 2 个 Markdown 文档(含 YAML 头部)
手动编写 _index.yaml(不超过 10 个条目)
Step 2:实现工具函数
在 Agent 代码中实现 lookup_knowledge 函数
实现从 _index.yaml 读取和关键词匹配逻辑
实现从文件系统读取 Markdown 内容
Step 3:集成到 Agent
将 lookup_knowledge 注册为 Agent 的工具
在 System Prompt 中加入知识查询规则
验证工具是否能正常被 LLM 调用
Step 4:端到端验证
跑通至少一个完整诊断流程
验证查询结果正确性
验证未匹配时的兜底逻辑
九、不纳入 MVP 的范围(后续再做)
不支持向量检索(后续用 BGE-M3 + Milvus)
不支持自动生成 _index.yaml(后续用脚本自动生成)
不支持多轮对话中的知识缓存(后续用 Redis)
不支持文档版本管理(后续用 Git)
十、代码示例(参考,非强制)
以下是 lookup_knowledge 的核心逻辑伪代码,供理解参考:
读取 _index.yaml
解析为 files 列表
for each file in files:
if query_text 中的任意关键词 匹配 file.keywords 中的任意条目:
读取 file.path 指向的 Markdown 文件
返回 content 的前 2000 字符
标记 found=True
如果没有匹配:
返回 found=False
@@ -0,0 +1,268 @@
# MVP Agent 工程决策记录
本文记录 MVP 实现过程中已经落地的一些关键修复、取舍和工程判断。目标不是写流水账,而是沉淀面试时可以讲清楚的 Agent 工程思路。
---
## 1. 统一流式与非流式 Chat 主链路
### 背景
早期 `/api/chat` 和 `/api/chat_stream` 是两条不同实现:
- 非流式接口会走复杂度判断,并可能进入 Planner / Executor / Verifier 多 Agent 流程。
- 流式接口直接创建单个 ReactAgent,然后 `agent.stream()` 输出 token。
这导致两个接口表面都是 chat,实际能力不一致:流式接口不会进入 verifier、不会沉淀完整诊断链路,也不容易和 `diagnosis_session`、`tool_invocation` 对齐。
### 决策
将两个接口统一到同一条核心链路:
```text
getOrCreateSession
-> 读取会话历史
-> ChatService.executeChatWithStrategy(...)
-> 写回会话历史
```
接口差异只保留在传输层:
- `/api/chat` 返回完整 JSON。
- `/api/chat_stream` 通过 SSE 分块发送最终答案。
### 取舍
这样会牺牲原来的 token 级实时流式体验,但换来业务行为一致、诊断链路一致、Verifier 和 evidence trace 一致。
对 MVP 来说,优先保证“同一个问题不因接口不同而进入不同智能链路”,比 token 级流式更重要。
---
## 2. 会话 ID 与诊断链路统一
### 背景
原实现中:
- `ChatController` 用前端传入的 `Id` 在 JVM 内存里维护历史消息。
- `ChatService` 每次执行又生成新的 8 位 sessionId,作为 `diagnosis_session` 和工具调用追踪 ID。
这会造成前端会话、后端诊断会话、工具证据链三者分裂。
### 决策
将前端 chat session id 作为后端诊断链路的主 session id:
- Redis `SessionContext` 保存聊天历史。
- `diagnosis_session.session_id` 复用同一个 id。
- `RunnableConfig.metadata.sessionId` 和 `SessionContextHolder` 也使用同一个 id。
- `tool_invocation`、`agent_step`、verifier evaluation 都可按同一 session id 串起来。
### 企业级意义
Agent 系统最怕“答得出来但查不清”。统一 session id 后,一次用户请求可以完整追踪:
```text
用户问题 -> Agent 步骤 -> 工具调用 -> Verifier 判断 -> 最终答案 -> 用户反馈
```
这是可观测、可审计、可复盘的基础。
---
## 3. 引入统一 ToolInvocationRecorder
### 背景
Verifier 需要结构化证据链,但原实现只有 `lookup_knowledge` 主动写入 `tool_invocation`。
`query_logs`、`query_metrics` 虽然返回 JSON,但没有统一落库,导致 verifier 看不到日志、指标等 evidence tool 的稳定记录。
### 决策
新增 `ToolInvocationRecorder`,作为所有 evidence tool 的统一落库入口。
当前接入:
- `lookup_knowledge`
- `query_logs`
- `query_metrics`
记录字段包括:
- tool name
- input params
- output preview
- output length
- success
- error message
- duration
- trace id / domain details
### 企业级意义
这一步把 Agent 从“模型说它查过”推进到“系统能证明它查过”。
后续 verifier 不应该依赖模型自由文本回忆工具调用,而应该消费结构化 trace summary。
---
## 4. Verifier 作为事实约束层
### 背景
普通 Agent 很容易在工具调用后直接生成答案,但企业场景更关心:
- 关键结论有没有证据
- 证据是直接证据还是间接支持
- 哪些事实缺口需要人工介入
- 工具失败时是否诚实降级
### 决策
保留 Planner / Executor / Verifier 三角色:
- Planner 负责拆解问题。
- Executor 负责执行查询与形成初稿。
- Verifier 负责基于 `tool_trace_summary` 做事实核查。
Verifier 输出结构化 JSON,包括:
- verdict
- groundedness_score
- critical_fact_count
- facts_checked
- rationale
### 取舍
Verifier 会增加一次模型调用成本,但换来可解释性和质量约束。对企业级 Agent 来说,这是值得的。
---
## 5. 从手写编排切换到 SupervisorAgent
### 背景
之前 `ChatService.executeChatComplex()` 中构建了 `SupervisorAgent`,但实际仍然手写调用:
```text
planner -> executor -> verifier
```
这会造成代码与设计不一致,维护者容易误以为当前已经由 Supervisor 调度。
### 决策
复杂问题真正切换到 `SupervisorAgent.invoke(...)`。
Supervisor 负责路由:
```text
chat_supervisor -> chat_planner
chat_supervisor -> chat_executor
chat_supervisor -> chat_verifier
chat_supervisor -> FINISH
```
外层仍保留:
- verifier 输出解析
- PASS / LOW_CONFID / REJECT 判定
- retry context
- fallback
- evaluation 入库
### 验证
新增离线专项测试 `ChatServiceSupervisorAgentTest`,使用 scripted `ChatModel` 验证真实 SupervisorAgent 路由顺序,不依赖真实 LLM、MySQL、Redis。
### 企业级意义
这让项目不只是“自己写 if/else 多 Agent”,而是使用框架原生 multi-agent orchestration,同时保留业务层的质量门控。
---
## 6. 文档上传路径语义统一
### 背景
上传文档时,`DocumentManagementService.saveToLocal()` 返回带 `knowledge_base` 前缀的路径。
而 `KnowledgeIndexService.readDocument()` 又执行:
```java
Paths.get(knowledgeBasePath, filePath)
```
这可能拼出:
```text
knowledge_base/knowledge_base/...
```
最终表现为 L0 命中文档,但读取原文失败。
### 决策
统一路径语义:
- 新上传文档存相对 `knowledge.base-path` 的路径,例如 `payment/runbook.md`。
- `readDocument()` 兼容新旧路径:
- 相对路径
- 已带 base path 的旧相对路径
- 绝对路径
### 企业级意义
知识库检索不能只看“命中”,还要保证命中后的内容可读、可引用、可追踪。
这是 RAG / Agent 系统里很典型的工程细节:检索质量问题不一定来自模型,也可能来自路径、元数据、索引和原文之间的语义不一致。
---
## 7. MVP 阶段的优先级取舍
当前主动暂缓的问题:
- 敏感配置外置与密钥轮换
- CORS / Redis 反序列化安全边界
- 默认 `mvn test` 离线化
原因不是这些不重要,而是当前目标是先跑通并讲清楚 MVP Agent 工程闭环。
短期优先目标:
```text
可演示 -> 可观测 -> 可验证 -> 可复盘
```
安全和完整测试体系属于企业落地必须项,但可以在 MVP 主链路稳定后作为下一阶段补齐。
---
## 8. 后续建议
下一阶段建议聚焦“可复现 MVP Demo”:
1. 增加 `local-demo` 或 `mvp-demo` profile。
2. 准备固定诊断 case,例如“支付接口超时”。
3. 提供一键初始化知识库样例。
4. 提供一键触发复杂诊断请求的脚本。
5. 增加 trace 查询接口:
```text
GET /api/diagnosis/{sessionId}/trace
```
该接口聚合:
- diagnosis_session
- agent_step
- tool_invocation
- verifier evaluation
- final answer
- feedback
这样 MVP 就能从“功能实现”升级为“企业级 Agent 工程作品”。
@@ -0,0 +1,39 @@
# MVP Demo Profile 与 Trace 查询接口
## 背景
MVP 已经能跑多 Agent 诊断、工具调用、Verifier 和反馈,但对外展示时仍然缺少一个稳定的复盘入口。面试官或评审如果想确认一次 Agent 回答是否可信,不能只看最终答案,还需要看到用户原始问题、Agent 步骤顺序、工具调用证据、Verifier / self-evaluation、最终答案和用户反馈。
## 决策
新增 `mvp-demo` profile 和 trace 查询接口:
```text
GET /api/diagnosis/{sessionId}/trace
```
接口聚合:
- `diagnosis_session`
- `agent_step`
- `tool_invocation`
- `self_evaluation`
- `feedback`
同时在 `mvp/demo` 下沉淀端到端验收 case,把启动、提问、查 trace、提交 feedback 串成一条可演示路径。
## 取舍
`mvp-demo` profile 不是完整离线 mock 环境,仍然复用当前真实 DB / Redis / Milvus / LLM 配置,只显式打开日志和指标 mock。原因是当前阶段目标是展示企业级 Agent 工程闭环,不是隐藏真实集成复杂度。
这让 MVP 的讲述从“我实现了一个聊天接口”升级为:
```text
我实现了一条可执行、可观测、可验收、可复盘的 Agent 诊断链路。
```
## 面试表达
- 我没有把 trace 塞进 chat 返回值,而是做成独立只读观测接口,保持执行链路和观测链路解耦。
- Trace API 复用已经沉淀的 `diagnosis_session`、`agent_step`、`tool_invocation` 三张表,没有引入新的 schema 风险。
- Demo profile 只做最小 overlay,让日志和指标工具可重复,保留真实基础设施集成,方便说明 MVP 与生产化之间的差距。
@@ -0,0 +1,296 @@
# 会话存储方案设计
**日期**: 2026-06-26
**类型**: 架构设计
**状态**: 已实现 (2026-06-26)
---
## 一、背景与目标
### 1.1 现状问题
当前仅有一张 `diagnosis_record` 表,存在以下问题:
| 问题 | 说明 |
|------|------|
| **语义耦合** | `fault_category`、`error_code`、`root_cause`、`solution` 等字段耦合在"告警分析"领域语义,ChatService 通用问答场景用不上 |
| **Agent 维度缺失** | 只有一个 `tool_calls` JSON 字段,存不下两个 Agent 的多轮决策链 |
| **检索质量不可追溯** | 没有记录 L0/L1 命中层、截断信息、召回内容长度 |
| **指标不完整** | 有 `duration` 和 `confidence`,但缺 token 用量、自评信号、采纳率 |
### 1.2 存储范围
需要覆盖四个层面的数据:
```
诊断级元数据
├── 单次诊断的唯一 ID、查询问题、状态
├── 会话级决策链
│ ├── agent_step:每个 Agent 的每一步(输入、输出、延迟、Token)
│ └── tool_invocation:每次工具调用(参数、结果、耗时)
├── 检索质量明细
│ └── 每次 lookup_knowledge 的命中层(L0/L1)、内容长度、是否截断
└── 自评估信号
└── LLM 对结论的置信度自评
```
### 1.3 设计目标
- **可观测**:Debug 时能回溯完整决策链
- **可评估**:能统计 L0/L1 命中率、平均 Token 消耗、工具采纳率等指标
- **可演进**:覆盖当前两个 Agent(ChatService / AiOpsService),未来新增 Agent 也能接入
---
## 二、存储选型分析
### 2.1 方案对比
| 维度 | SQL + JSON 列 | NoSQL 文档库 |
|------|:------------:|:-----------:|
| 基础设施 | 已有的 MySQL,零新增 | 需新部署 MongoDB 等 |
| 层级查询 | `WHERE session_id=? AND agent_name=?` 高效 | 需二级索引 |
| 指标聚合 | `AVG(token_count) GROUP BY agent_name` 原生支持 | 聚合管道,学习成本 |
| 非结构化内容 | JSON 列(MySQL 8+ 支持良好) | 天然支持 |
| MVP 迭代速度 | JPA Entity + Flyway 快速迭代 | 新 ORM 学习成本 |
### 2.2 结论
**采用 MySQL + JSON 列**。结构化字段做查询和聚合,JSON 列存非结构化载荷。MVP 阶段数据量可控,等后续 > 百万级或需要更灵活 schema 时再评估 NoSQL。
---
## 三、存储模型
### 3.1 整体关系
```
diagnosis_session (1)
│
└── agent_step (0:N) —— 单次诊断的每一步 Agent 决策
│
└── tool_invocation (0:N) —— 每步中的工具调用
```
### 3.2 表设计
#### 表 1:diagnosis_session(诊断会话)
```sql
CREATE TABLE diagnosis_session (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) UNIQUE NOT NULL COMMENT '会话唯一 ID',
-- 请求
query TEXT NOT NULL COMMENT '用户原始问题',
status VARCHAR(16) DEFAULT 'PENDING' COMMENT 'PENDING / RUNNING / SUCCESS / FAILED',
agent_flow VARCHAR(32) COMMENT 'CHAT / AI_OPS',
-- 汇总指标
total_duration_ms INT COMMENT '总耗时(毫秒)',
total_token_count INT COMMENT '总 Token 消耗',
step_count INT COMMENT 'Agent 步数',
tool_call_count INT COMMENT '工具调用次数',
-- 自评估信号(模型对结论的置信度自评)
self_evaluation JSON COMMENT '{"confidence": 0-100, "reasoning": "...", "evidence_count": 3}',
-- 用户反馈
feedback VARCHAR(16) COMMENT 'useful / not_useful / null',
-- 元数据
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_created_at (created_at),
INDEX idx_status (status),
INDEX idx_agent_flow (agent_flow)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='诊断会话表';
```
#### 表 2:agent_step(Agent 决策步骤)
```sql
CREATE TABLE agent_step (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL COMMENT '关联 diagnosis_session',
step_index INT NOT NULL COMMENT '当前 Agent 的第几步(从0开始)',
agent_name VARCHAR(32) NOT NULL COMMENT 'intelligent_assistant / planner / executor / supervisor',
-- 模型调用(输入输出摘要,非完整消息体)
model_input JSON COMMENT '模型输入摘要 [{role, content_truncated}, ...]',
model_output JSON COMMENT '模型输出摘要 {text, tool_calls, ...}',
thought TEXT COMMENT 'Agent 思考过程文本',
has_tool_call BOOLEAN DEFAULT FALSE COMMENT '本轮是否调用了工具',
-- 性能指标
duration_ms INT COMMENT '本轮耗时',
token_count INT COMMENT '本轮 Token 消耗',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_step (session_id, step_index),
INDEX idx_agent_name (agent_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Agent 决策步骤表';
```
#### 表 3:tool_invocation(工具调用明细)
```sql
CREATE TABLE tool_invocation (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL COMMENT '关联 diagnosis_session',
step_id BIGINT COMMENT '关联 agent_step.id(可为空,不强制外键)',
tool_name VARCHAR(64) NOT NULL COMMENT 'lookup_knowledge / queryPrometheusAlerts / 等',
-- 调用信息
input_params JSON NOT NULL COMMENT '工具入参',
output_preview TEXT COMMENT '输出前500字符(可观测用,不存完整输出)',
output_length INT COMMENT '输出总字符数',
-- 检索质量(仅 lookup_knowledge 时有意义)
retrieval_layer VARCHAR(8) COMMENT 'L0 / L1 / L0+L1',
l0_match_count INT COMMENT 'L0 匹配数',
l1_match_count INT COMMENT 'L1 匹配数',
is_truncated BOOLEAN DEFAULT FALSE COMMENT '返回内容是否被截断',
retrieval_details JSON COMMENT '{"l0_titles":[], "l1_scores":[], "has_supplement": true}',
-- 性能 & 状态
duration_ms INT COMMENT '工具执行耗时',
success BOOLEAN DEFAULT TRUE COMMENT '是否成功',
error_message TEXT COMMENT '失败原因',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_id (session_id),
INDEX idx_tool_name (tool_name),
INDEX idx_retrieval_layer (retrieval_layer)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工具调用明细表';
```
---
## 四、数据流设计
### 4.1 完整链路
```
用户请求
│
▼
1. 创建 diagnosis_session(status=RUNNING)
│
▼
2. Agent Loop(可能多轮)
│
├── beforeModel()
│ └── AgentLoggingHook 记录 model_input + 开始时间 → 写入 agent_step(先创建,duration 待填)
│
├── afterModel()
│ └── AgentLoggingHook 记录 model_output + token_count + 工具调用决策 → 更新 agent_step
│
├── 工具执行(如 lookup_knowledge)
│ └── LookupKnowledgeTool 记录 tool_invocation(L0/L1 明细、耗时、是否截断)
│
└── 循环直到模型不再调用工具
│
▼
3. 诊断完成 → 更新 diagnosis_session
├── status = SUCCESS / FAILED
├── 汇总指标:total_duration_ms / total_token_count / step_count / tool_call_count
└── self_evaluation(可选,由 LLM 自评)
```
### 4.2 变更点
| 模块 | 当前行为 | 改造后 |
|------|---------|--------|
| `AgentLoggingHook` | 只打日志到 stdout | 同时写入 `agent_step` 表 |
| `LookupKnowledgeTool` | 只打日志到 stdout | 同时写入 `tool_invocation` 表 |
| `ChatService` / `AiOpsService` | 执行前后无持久化 | 创建 + 更新 `diagnosis_session` |
---
## 五、可观测能力
### 5.1 查询场景
| 需求 | SQL | 说明 |
|------|-----|------|
| 某次诊断用了哪些工具 | `SELECT * FROM tool_invocation WHERE session_id=?` | 按 session 关联 |
| lookup_knowledge 的 L0/L1 命中率 | `SELECT retrieval_layer, COUNT(*) FROM tool_invocation WHERE tool_name='lookup_knowledge' GROUP BY retrieval_layer` | 聚合检索层分布 |
| 某个 Agent 的平均思考耗时 | `SELECT AVG(duration_ms) FROM agent_step WHERE agent_name=?` | 按 Agent 分组 |
| 某次诊断的完整决策链 | `SELECT * FROM agent_step WHERE session_id=? ORDER BY step_index` | 按步骤号排序 |
| 被截断的检索占比 | `SELECT COUNT(*) FROM tool_invocation WHERE is_truncated=true AND tool_name='lookup_knowledge'` | 条件计数 |
| 高置信度但用户反馈 negative | `SELECT * FROM diagnosis_session WHERE JSON_EXTRACT(self_evaluation, '$.confidence') > 80 AND feedback='not_useful'` | JSON 条件查询 |
### 5.2 评估指标
| 指标 | 计算方式 | 数据来源 |
|------|---------|---------|
| 平均诊断耗时 | `AVG(total_duration_ms)` | diagnosis_session |
| 平均 Token 消耗 | `AVG(total_token_count)` | diagnosis_session |
| 工具采纳率 | `tools_accepted / tools_proposed` | self_evaluation |
| L0 命中率 | `l0_match_count > 0 的比例` | tool_invocation |
| 截断率 | `is_truncated=true 的比例` | tool_invocation |
| 用户满意度 | `feedback='useful' 的比例` | diagnosis_session |
---
## 六、与现有表的关系
### 6.1 diagnosis_session vs 现有 diagnosis_record
- **`diagnosis_record`** 保持不动,继续用于"告警分析"场景的领域字段(root_cause、solution 等)
- **`diagnosis_session`** 是通用会话存储,覆盖 ChatService 和 AiOpsService
- 两者通过 `session_id` 可关联
### 6.2 迁移策略
| 阶段 | 动作 |
|:----:|------|
| MVP | 新建三张表,新代码写入新表 |
| V1.1 | 评估是否将 diagnosis_record 合并回 diagnosis_session(加 fault 相关字段到 JSON) |
| V1.2 | 数据量 > 10 万时评估是否需要归档或迁移 |
---
## 七、未完成事项
- [ ] AI Ops Supervisor 的 Agent 执行步骤如何对应 agent_step 表(Supervisor 内嵌的子 Agent 步骤归到同一个 session 还是独立)
- [ ] self_evaluation 的 confidence 自评通过什么方式获取(单独的 LLM 调用还是在 prompt 中要求输出)
- [ ] feedback 字段和前端的交互方式
- [ ] Tool_invocation 的 output_preview 截断策略(当前建议 500 字符)
---
## 八、实现变更记录
### 8.1 与设计文档的差异
| 设计 | 实现 | 原因 |
|------|------|------|
| AgentLoggingHook 为 @Component | 改为 POJO(构造注入 Repository + agentName) | 需要为 ChatService / AiOpsService 创建多个 Hook 实例(不同 agentName) |
| sessionId 通过 RunnableConfig 的 metadata 携带 | 通过 `RunnableConfig.builder().addMetadata("sessionId", id)` 构建 | 确认框架 API 原生支持,线程安全 |
| Token 从 ChatResponse 获取 | 增加了 `TokenTrackingChatModel` 包装器拦截 ChatModel.call() | 框架的 `_TOKEN_USAGE_` 仅在 stream 路径可用,call 路径需自行拦截 |
| sessionId 汇总后回填 | `backfillSessionMetrics()` 从 agent_step 表统计 | 避免在 Hook 中维护累加状态 |
### 8.2 新增文件(超出原设计)
| 文件 | 用途 |
|------|------|
| `TokenTrackingChatModel.java` | ChatModel 包装器,拦截 call() 获取实际 token 用量 |
| `TokenUsageHolder.java` | ThreadLocal 传递 token 数给 Hook |
| `QuestionComplexity.java` | 问题复杂度判断,路由单 Agent / 多 Agent |
| `SessionContextHolder.java` | ThreadLocal 传递 sessionId(同步路径兜底) |
### 8.3 删除文件
| 文件 | 原因 |
|------|------|
| `DiagnosisRecord.java` / `DiagnosisRecordRepository.java` / `DiagnosisStatus.java` | 被新三表替代,V007 Flyway 迁移删除 |
| `.docs/mvp/` | 内容合并到根目录 `mvp/` |