docs(mvp): organize mvp documentation
This commit is contained in:
@@ -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/` |
|
||||
|
||||
Reference in New Issue
Block a user