docs: reorganize MVP interview documentation

This commit is contained in:
aruo
2026-07-05 15:29:28 +08:00
parent b22f2d22c8
commit 88e0a6c944
51 changed files with 4352 additions and 1318 deletions
@@ -0,0 +1,28 @@
# 旧版架构文档归档
**归档日期**:2026-07-05
本目录保存 `mvp/architecture` 下的旧版架构文档。它们包含早期 MVP 设计、旧 RAG 方案、会话存储设计、行动记忆和实施计划等历史材料。
这些文档不再作为当前实现依据。当前架构请阅读:
- `mvp/architecture/README.md`
- `mvp/architecture/current-mvp-architecture.md`
- `mvp/architecture/rag-architecture.md`
## 归档文件
| 文件 | 说明 |
|---|---|
| `agent-architecture.md` | 早期完整 Agent 设想,包含较多超出当前 MVP 的 SubAgent 设计 |
| `agent-architecture-mvp.md` | 早期 MVP Agent 设计 |
| `knowledge-retrieval-architecture.md` | 旧版 L0 + L1 检索架构,包含 L0 唯一命中跳过 L1 的旧逻辑 |
| `knowledge-retrieval-usage.md` | 旧版知识库检索使用说明 |
| `current-mvp-architecture.md` | 归档前的当前架构快照 |
| `implementation-plan.md` | 早期实施计划 |
| `implementation-detail.md` | 早期完整实施计划 |
| `session-management.md` | 会话管理旧设计 |
| `session-dedup-knowledge-map.md` | 会话去重和知识域地图设计 |
| `confidence-feedback.md` | 证据评分和用户反馈旧设计 |
| `action-memory-relevance.md` | 行动记忆和检索质量归一化旧设计 |
@@ -0,0 +1,275 @@
# 行动记忆与检索质量归一化
Executor 行动记忆 + 归一化质量等级设计,解决 ISS-002 Executor 无约束重复检索问题。
---
## 一、问题背景
ISS-001 修复文档级去重后,Executor 在单次会话中仍调用 `lookup_knowledge` 20+ 次。根因:
1. **行动记忆缺失**:Executor 不知道自己已检索过哪些域
2. **质量信号缺失**:检索结果没有给 LLM 判断"结果够不够"的信号
3. **Prompt 缺少合法出口**:原 prompt 要求"所有外部信息都必须调用工具",LLM 不敢停止检索
---
## 二、整体架构
```
lookup_knowledge(query)
│
├─ Step 1: L0 精确匹配(keywords 索引)
├─ Step 2: L1 语义检索(Milvus 向量)
├─ Step 3: computeRelevance()
│ ├─ 归一化:L2 → similarity [0,1]
│ └─ 判定:PRECISE / HIGHLY_RELEVANT / REFERENCE
├─ Step 4: RetrievedDocTracker 检查
│ ├─ 文档级去重 → isDocRetrieved(sessionId, docKey)
│ ├─ 域级检查 → isDomainRetrieved(sessionId, domain)
│ └─ 记录 → markRetrieved(sessionId, domain, docKey)
└─ Step 5: 返回 LookupResult
├─ primary / supplement(原始内容,不含分数)
├─ relevanceLevel(PRECISE / HIGHLY_RELEVANT / REFERENCE)
├─ completenessHint(兜底信号)
└─ retrievedDomainsThisSession(行动记忆)
```
### 设计原则
| 原则 | 说明 |
|------|------|
| **Agent 边界清晰** | 不给 Executor 注入 knowledge map,Executor 只知道做了什么,不用知道有什么 |
| **分数封装** | L0/L1 原始分数不在 LookupResult 中返回 LLM,只在归一化层内部使用 |
| **原始分数只入库** | 原始 L2 距离写进 `tool_invocation.retrieval_details` JSON 用于可观测 |
| **软约束 + 硬拦截** | Prompt 约束(软)+ 工具层域级去重(硬)两层防御 |
---
## 三、归一化质量等级
### L2 距离归一化
BGE-M3 输出为 L2 归一化单位向量(实测范数=1.00000002),L2 距离数学硬上界 = 2.0。
```
similarity = 1 - min(l2Score, maxL2Distance) / maxL2Distance
```
| L2 距离 | similarity | 等级 |
|---------|-----------|------|
| 0.0 | 1.0 | PRECISE |
| 0.383 | 0.8085 | HIGHLY_RELEVANT |
| 0.5 | 0.75 | HIGHLY_RELEVANT |
| 0.6031 | 0.6984 | REFERENCE |
| 1.0 | 0.5 | REFERENCE 边界 |
| 2.0+ | 0.0 | 不视为有效结果 |
### 三等级判定
| 等级 | 条件 | completenessHint | LLM 行为 |
|------|------|-----------------|---------|
| PRECISE | L0 matchCount == 1 | "知识库中不存在比上述结果更精准的文档" | 直接使用,禁止再检索 |
| HIGHLY_RELEVANT | L0 命中 + similarity ≥ 0.75,或仅 L1 similarity ≥ 0.75 | "当前结果已高度相关,继续检索不太可能找到更精准的文档" | 可综合推理,大概率不需要继续查 |
| REFERENCE | 其余命中(similarity ≥ 0.5) | "当前结果为相关参考,如需更精准信息请明确缺少的具体维度" | 可参考,如需更精准请指出缺少的维度后定向补充 |
### 阈值配置
```yaml
retrieval:
normalization:
max-l2-distance: 2.0 # L2 距离上界
highly-relevant-threshold: 0.75 # similarity ≥ 0.75 → HIGHLY_RELEVANT
reference-threshold: 0.5 # similarity ≥ 0.5 → REFERENCE
```
---
## 四、行动记忆
### RetrievedDocTracker 数据结构
```java
// 从单层升级为双层:session → domain → filePath 集合
ConcurrentHashMap<String, Map<String, Set<String>>> sessionRetrievals;
```
### API
| 方法 | 作用 |
|------|------|
| `markRetrieved(sessionId, domain, filePath)` | 记录一次检索 |
| `isDocRetrieved(sessionId, filePath)` | 文档级去重 |
| `isDomainRetrieved(sessionId, domain)` | 域级检查 |
| `getRetrievedDomains(sessionId)` | 获取已检索域列表 |
| `clearSession(sessionId)` | 清理会话记录 |
### LookupResult 返回
```java
LookupResult.builder()
.found(true)
.primary(primaryResult)
.supplement(supplementResult)
.relevanceLevel("HIGHLY_RELEVANT") // PRECISE / HIGHLY_RELEVANT / REFERENCE
.completenessHint("当前结果已高度相关...") // 兜底信号
.retrievedDomainsThisSession(["infrastructure", "api"]) // 行动记忆
.message("...")
.build();
```
---
## 五、Executor Prompt 约束
### 4 条检索约束
1. **判断重复**:基于 `retrievedDomainsThisSession` 判断语义重叠
2. **重复了怎么办**:禁止换关键词重查;先指缺少的维度,再定向补充
3. **合法出口**:"不查全不会被追责,重复检索才会被惩罚"
4. **利用质量信号**:PRECISE → 停止;HIGHLY_RELEVANT + 域已检索 → 禁止;REFERENCE → 指出缺少维度
### 关键变化
原有 prompt:"所有需要外部信息的地方,都必须调用对应的工具"
→ 改为:"需要外部信息时调用工具,但须遵守下方的检索约束"
---
## 六、数据库变更
### V010
```sql
ALTER TABLE tool_invocation
ADD COLUMN relevance_level VARCHAR(20) COMMENT 'PRECISE/HIGHLY_RELEVANT/REFERENCE/DEDUPED',
ADD COLUMN dedup_reason VARCHAR(32) COMMENT 'doc_retrieved/domain_retrieved/null';
```
### retrieval_details JSON 扩展
```json
{
"l0_titles": ["MySQL 数据库连接池配置", "Redis 缓存配置指南"],
"l1_scores": [0.383, 0.4502, 0.7011],
"l1_top_score": 0.383,
"l1_top_similarity": 0.8085,
"relevance_level": "HIGHLY_RELEVANT",
"completeness_hint": "当前结果已高度相关,继续检索不太可能找到更精准的文档",
"retrieved_domains": ["infrastructure"]
}
```
扩展字段使用方式:
| 字段 | 用途 |
|------|------|
| `l1_top_score` | 原始 L2 距离最小值(可观测性) |
| `l1_top_similarity` | 归一化后的相似度 [0,1] |
| `relevance_level` | 归一化质量等级 |
| `completeness_hint` | 兜底信号 |
| `retrieved_domains` | 已检索域列表 |
| `dedup_reason` | 去重原因(如有) |
---
## 七、使用场景
### 场景 1:正常检索
```
用户:数据库连接池怎么配置?
Executor 内部:
1. lookup_knowledge("数据库连接池配置")
→ relevanceLevel=HIGHLY_RELEVANT (similarity=0.8085)
→ completenessHint="当前结果已高度相关..."
→ retrievedDomainsThisSession=["infrastructure"]
2. 基于已有信息直接回答,不再检索
```
### 场景 2:行动记忆阻止重复
```
Executor 步骤列表:
- 查数据库连接池配置
- 查 HikariCP 参数
- 查连接池耗尽排查
实际行为:
1. lookup("数据库连接池") → relevance=HIGHLY_RELEVANT, domains=["infrastructure"]
2. lookup("HikariCP 参数") → retrievedDomainsThisSession=["infrastructure"]
LLM 判断:infrastructure 域已检索过,禁止换关键词重查
→ 基于已有信息回答,指出缺少的具体维度
3. lookup("连接池耗尽") → 同域,被 prompt 约束拦截或工具层去重拦截
```
### 场景 3:PRECISE 精确匹配
```
用户:ERR_TIMEOUT 是什么?
Executor 内部:
1. lookup_knowledge("ERR_TIMEOUT")
→ L0 matchCount=1(唯一精确匹配)
→ relevanceLevel=PRECISE
→ completenessHint="知识库中不存在比上述结果更精准的文档"
2. 直接使用,不再检索
```
### 场景 4:REFERENCE + 定向补充
```
用户:如何排查生产故障?
Executor 内部:
1. lookup_knowledge("故障排查")
→ relevanceLevel=REFERENCE (similarity=0.6)
→ retrievedDomainsThisSession=["troubleshooting"]
2. LLM 判断:信息不足,缺少"日志分析"维度的具体步骤
3. lookup_knowledge("日志分析步骤")
→ 定向补充,不盲目换关键词
```
---
## 八、可观测性
### 查询质量分布
```sql
SELECT relevance_level, COUNT(*) AS cnt
FROM tool_invocation
WHERE tool_name = 'lookup_knowledge'
GROUP BY relevance_level;
```
### 去重原因分布
```sql
SELECT dedup_reason, COUNT(*) AS cnt
FROM tool_invocation
WHERE tool_name = 'lookup_knowledge'
GROUP BY dedup_reason;
```
### 归一化分数分布
```sql
SELECT
JSON_EXTRACT(retrieval_details, '$.l1_top_similarity') AS similarity,
COUNT(*) AS cnt
FROM tool_invocation
WHERE tool_name = 'lookup_knowledge'
AND retrieval_details IS NOT NULL
GROUP BY similarity
ORDER BY similarity;
```
---
## 九、扩展方向(Phase 2)
- **域级硬限流**:`isDomainRetrieved` 已就绪,在 LookupKnowledgeTool 入口直接拦截同域调用,不依赖 LLM 遵守 prompt
- **DEDUPED 等级**:去重时单独标记为 DEDUPED 等级,与 REFERENCE 区分
- **分数反馈调优**:基于 feedback 数据优化归一化阈值
@@ -0,0 +1,529 @@
# Agent 架构设计(MVP 版)
## 一、MVP 全景
```
用户输入
↓
┌──────────────────────────────────────────┐
│ 意图识别(Intent Recognition) 🆕 │
│ "用户想干什么?" │
│ │
│ 诊断意图 → 路由到诊断 Skill │
│ 文档意图 → 路由到文档问答 │
│ 案例意图 → 路由到案例查询 │
│ 闲聊 → 快速响应(不启动 Agent) │
│ 模糊/无关 → 提示用户,直接中断 │
└──────────────┬───────────────────────────┘
│ 诊断意图
↓
┌──────────────────────────────────────────┐
│ Supervisor Agent(调度者) │
│ "谁来干?什么时候停?" │
└──────────────┬───────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Planner Agent(规划者) │
│ 分析问题 → 制定策略 → 生成报告 │
└──────────────┬───────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Executor Agent(执行者) │
│ 调用工具收集证据 │
└──────────────┬───────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Verifier Agent(验证者) │
│ 事实核查 → 判定通过/修正/驳回 │
└──────────────┬───────────────────────────┘
↓
诊断报告输出
↓
用户反馈(有用/无用)
↓
案例沉淀 + BadCase 优化
```
---
## 二、意图识别(入口层)🆕
### 2.1 设计理念
```
定位:独立模块,不嵌入任何单一 Agent
分层策略(不是二选一,而是组合):
L0: 正则规则 —— 0 成本,毫秒级 ✅ MVP
├─ 处理 80%+ 的结构化查询
├─ 正则匹配订单号/traceId/错误码格式
└─ 关键词匹配("报错"/"异常"/"失败")
L1: 小模型 Agent —— 低成本,百毫秒级 ✅ MVP
├─ L0 未命中时触发
├─ 处理灵活的模糊表达("系统有点慢"、"怎么查不到了")
├─ 不启动全链路 Agent,只做意图分类
└─ 判断为诊断意图 → 路由到诊断 Skill
L2: 兜底策略 —— 极少使用
├─ L0+L1 都无法判断 → 意图不明 → 中断
└─ Phase 2 增强
```
### 2.2 意图分类与路由
```
┌────────────────────────────────────────────────────┐
│ 意图 │ 说明 │ 路由 │
├────────────────────────────────────────────────────┤
│ 诊断意图 │ 包含结构化标识或错误描述 │ → 诊断Skill│
│ 文档问答 │ "XX接口的参数有哪些" │ → 直接RAG │
│ 案例查询 │ "之前有类似的问题吗" │ → 案例检索 │
│ 闲聊 │ "你好"/"谢谢" │ → 快速响应 │
│ 意图不明 │ 无法识别 │ → 中断+提示 │
└────────────────────────────────────────────────────┘
关键原则:
- 只有诊断意图才启动 Agent 全链路
- 非诊断意图走轻量路径或直接中断
```
### 2.3 L0:正则规则(MVP,处理 80%)
```
为什么先做 L0?
→ 0 成本(不调 LLM),毫秒级响应
→ 结构化查询占比最大(订单号、traceId、错误码、关键词)
→ L0 命中直接路由,不需要走后续逻辑
规则配置(可扩展):
┌────────────────────────────────────────────────┐
│ 规则 │ 意图 │ 方式 │
├────────────────────────────────────────────────┤
│ 匹配 \d{12,} │ 诊断 │ 正则 │
│ 匹配 trace[-_]?\w{8,} │ 诊断 │ 正则 │
│ 包含"报错‖失败‖异常‖挂了‖超时" │ 诊断 │ 关键词│
│ 包含"文档‖接口‖参数‖字段‖API" │ 文档 │ 关键词│
│ 包含"案例‖之前‖类似‖历史" │ 案例 │ 关键词│
│ 长度 <= 5 字符 │ 闲聊 │ 规则 │
└────────────────────────────────────────────────┘
命中 → 直接路由,不调 L1
未命中 → 进入 L1
```
### 2.4 L1:小模型 Agent(MVP,处理剩余 20%)
```
为什么用小模型 Agent 而非嵌入到 Supervisor?
→ 意图识别是独立职责,不应耦合到任何业务 Agent
→ 轻量 Agent:单一职责,只分类不执行
→ 成本低(~50 token),延迟低(~200ms)
何时触发:L0 规则未命中
System Prompt:
"你是意图分类器,判断用户想做什么。
只返回一个词:[诊断 / 文档查询 / 案例查询 / 闲聊 / 意图不明]
诊断:用户描述了故障、报错、异常
文档查询:用户询问接口文档、字段含义
案例查询:用户询问历史案例、类似问题
闲聊:简单的问候、感谢
意图不明:无法判断用户意图"
输入:用户原始输入
输出:意图类型 + 置信度
```
### 2.5 L2:兜底策略
```
L0+L1 都无法判断 → L2 兜底
中断规则:
├─ 意图不明 → 提示用户 + 中断
│ "无法判断您的意图,请提供订单号或错误码"
├─ 闲聊 → 快速响应 + 中断
│ "我是故障诊断助手,请描述您遇到的问题"
└─ 不启动 Agent,直接返回
路由规则:
├─ 诊断意图 → 启动 Supervisor + 4 Agent 全链路
├─ 文档意图 → 不启动 Agent,直接 RAG 检索
└─ 案例意图 → 不启动 Agent,直接查询 case_library
```
### 2.5 架构位置
```
用户输入
↓
┌──────────────────────────────────────────┐
│ 意图识别模块(独立) │
│ │
│ L1: 小模型 Agent ─→ 诊断意图? │
│ │ 文档意图? │
│ │ 案例意图? │
│ │ 闲聊? │
│ ↓ │
│ L2: 兜底 ─────────→ 意图不明 → 中断 │
│ 闲聊 → 快速响应 │
└───────────────┬──────────────────────────┘
│ 诊断意图
↓
Supervisor → Planner → Executor → Verifier
```
### 2.6 MVP vs Phase 2
```
MVP L0(正则)+ L1(小模型Agent) 覆盖 95%+ 场景
Phase 2 L2(兜底增强) 细化中断提示,支持多轮澄清
```
---
## 三、4 个 Agent 设计
### 2.1 Supervisor Agent(调度者)
**职责**:总指挥,协调工作流
```
调度规则:
├─ 接收任务 → 发给 Planner 分析
├─ Planner 完成 → 发给 Executor 执行
├─ Executor 完成 → 发给 Verifier 校验
└─ Verifier PASS → 输出报告 / REJECT → 返回 Planner 重新规划
不做:
- 不直接调用工具
- 不直接生成报告
```
### 2.2 Planner Agent(规划者 + 分诊)
**职责**:分析问题、制定策略、生成报告草稿
```
分析规则:
├─ 有 errorCode + 接口 URL → EXTERNAL_API(外部接口故障)
├─ 有堆栈信息 → INTERNAL_ERROR(系统内部错误)
├─ 有数据库错误码(如 1213)→ DATABASE(数据库问题)
└─ 其他 → 通用排查
规划流程:
1. 确定 fault_category
2. 制定排查步骤(每步:工具名 + 参数 + 预期)
3. 生成决策:EXECUTE(继续执行)| FINISH(生成报告)
Replanner 职责:
├─ Executor 每次返回结果后 → 评估证据是否充分
├─ 需要补充?→ 调整步骤,继续执行
├─ 证据齐全?→ FINISH,生成报告草稿
└─ 连续 3 次失败?→ 降级
禁止:
- 编造数据
- 引用未经工具返回的内容
```
### 2.3 Executor Agent(执行者)
**职责**:调用工具收集证据
```
工具清单:
├─ queryOrder:查询订单/业务数据(MySQL 只读)
├─ searchDoc:检索接口文档(混合检索 Milvus + MySQL)
├─ recommendCase:推荐相似案例(精确匹配 + 语义检索)
└─ getCurrentTime:获取当前时间
执行规则:
├─ 每次只执行 Planner 指定的一个步骤
├─ 返回结构化的执行结果
├─ 失败时返回错误详情(便于 Planner 调整)
└─ 禁止编造结果
扩展预留:
// 代码中 Executor 是接口,后续可扩展为 SubAgent
public interface Executor {
ExecutionResult execute(Step step);
}
```
### 2.4 Verifier Agent(验证者)
**职责**:验证诊断报告,防止编造
```
验证流程:
1️⃣ 事实核查(最重要)
├─ 报告中的错误码 → 在 tool_calls 中存在?
├─ 根因结论 → 有日志/文档证据支撑?
├─ 修复方案 → 引用了文档或案例?
└─ 发现编造数据 → 直接 REJECT
2️⃣ 完整性检查
├─ 根因分析章节不能为空
├─ 证据链章节不能为空
└─ 修复方案章节不能为空
判决结果:
├─ PASS:报告成立,直接输出
├─ REVISE:小问题可修正,返回 Planner 微调
└─ REJECT:编造数据或严重错误,返回 Planner 重新分析
```
---
## 四、RAG 两层加载策略
### 4.1 设计理念
```
问题:
❌ 全前置:启动时把所有文档塞给 Agent → 信息过载,推理变慢
❌ 纯被动:等到需要才查 → Planner 没有全局视野,可能跑偏
❌ 固定步骤:每次都调 → 内部错误查接口文档浪费
正确做法:两层互补
L1 预加载(Planner 启动时)
→ 通用领域知识:系统架构、通用错误码、业务流程
→ 给 Planner 全局视野,避免方向性错误
L2 按需加载(Executor 执行中)
→ 具体接口文档:字段定义、错误码含义、调用规范
→ 给 Executor 精准证据,定位具体问题
```
### 4.2 两层对比
| | L1 预加载 | L2 按需加载 |
|------|---------|-----------|
| 触发时机 | 意图识别后,Planner 启动前 | Executor 拿到具体信息后 |
| 内容 | 通用知识(架构、流程、高频错误码) | 具体接口文档(字段、错误码含义) |
| 目的 | 让 Planner 有全局视野 | 让 Executor 有精准证据 |
| 成本 | 固定,每次诊断 1 次 | 按需,最多 2-3 次 |
| 谁负责 | Supervisor 注入 | Executor 自主调用 |
### 4.3 实现方式
```
L1 预加载:
Supervisor 在启动 Planner 前:
searchDoc(keyword="系统架构 通用错误码 业务流程")
→ 注入到 Planner 的 System Prompt 中
→ Planner 拥有"领域背景知识"
L2 按需加载:
Executor 执行 Step 2 时:
拿到 errorCode=40003, faultSource="广东"
→ 自主调用 searchDoc(errorCode="40003", faultSource="广东")
→ 获取该接口的具体字段定义和错误码说明
→ 作为证据写入诊断报告
```
---
## 五、Skill 设计(1 个)
### /diagnose-by-orderid(按订单号诊断)
```
输入:orderId
工作流(6 步):
Step 1: 查询订单信息
工具:queryOrder
失败:ABORT(订单不存在则终止)
Step 2: 检索接口文档
工具:searchDoc
参数:errorCode + faultSource
失败:SKIP(标注"文档缺失")
Step 3: 查询日志
工具:queryLogs(Mock)
参数:traceId
失败:SKIP(标注"日志缺失")
Step 4: 检索相似案例
工具:recommendCase
参数:errorCode + faultCategory
失败:SKIP(标注"无相似案例")
Step 5: 生成诊断报告
汇总所有证据,按模板生成报告
Step 6: Verifier 验证
事实核查 → 判决
门禁规则:
├─ Step 1 失败 → 终止,返回"订单不存在"
├─ Step 2-4 失败 → 跳过,标注缺失信息
├─ 任意步骤超时 30s → 终止
└─ Verifier REJECT → 返回 Planner 重新规划
```
---
## 六、Harness 控制层(精简版)
### 4.1 5 个 Quality Gates
```
输入门禁(2 个):
├─ Gate 1: 输入参数非空校验
└─ Gate 2: 5 分钟内同一订单 → 返回缓存
执行门禁(1 个):
└─ Gate 3: 工具调用超时(10 秒)
输出门禁(2 个):
├─ Gate 4: 报告章节完整性(3 章节不全 → 不通过)
└─ Gate 5: 置信度阈值(< 60 → 标记"低置信度")
```
### 4.2 中断规则
```
自动中断:
├─ 工具连续失败 3 次 → 终止,降级输出
└─ 全局超时 30 秒 → 终止
条件降级:
├─ 文档检索为空 → 跳过继续
├─ 案例推荐为空 → 跳过继续
└─ 日志查询失败 → 跳过继续
降级输出:
"无法自动诊断,请人工介入"
+ 已收集的证据(订单信息 + 部分日志 + 已知错误码)
```
---
## 七、技术实现
### 5.1 基于 Spring AI Alibaba
```java
// Supervisor - 框架提供
SupervisorAgent supervisor = SupervisorAgent.builder()
.name("diagnosis_supervisor")
.model(chatModel)
.subAgents(List.of(planner, executor, verifier))
.build();
// Planner
ReactAgent planner = ReactAgent.builder()
.name("planner_agent")
.model(chatModel)
.systemPrompt(plannerPrompt)
.outputKey("planner_plan")
.build();
// Executor(代码中预留 SubAgent 扩展接口)
ReactAgent executor = ReactAgent.builder()
.name("executor_agent")
.model(chatModel)
.systemPrompt(executorPrompt)
.methodTools(diagnosisTools)
.tools(new ToolCallback[]{queryOrder, searchDoc, recommendCase, getCurrentTime})
.build();
// Verifier
ReactAgent verifier = ReactAgent.builder()
.name("verifier_agent")
.model(chatModel)
.systemPrompt(verifierPrompt)
.outputKey("verifier_result")
.build();
```
### 5.2 工具注册
```java
@Component
public class DiagnosisTools {
@Tool(description = "查询订单/业务数据(只读)")
public OrderInfo queryOrder(@ToolParam(description = "订单号") String orderId) {
// MySQL 只读 + SQL 注入防护
}
@Tool(description = "检索接口文档")
public List<DocChunk> searchDoc(
@ToolParam(description = "错误码") String errorCode,
@ToolParam(description = "省份/服务名") String faultSource
) {
// 混合检索:精确匹配 + 向量检索
}
@Tool(description = "推荐相似历史案例")
public List<CaseResult> recommendCase(
@ToolParam(description = "错误码") String errorCode,
@ToolParam(description = "故障类别") String faultCategory
) {
// 精确匹配 MySQL + 语义检索 Milvus
}
}
```
---
## 八、闭环机制
```
诊断报告输出
↓
用户反馈(useful / not_useful)
↓
├─ useful → 自动生成 case_library
└─ not_useful → 记录 BadCase
↓
每周 BadCase 分析
↓
Prompt / Skill 优化
↓
准确率验证(测试集重跑)
```
---
## 九、MVP vs 扩展方向
| 维度 | MVP | 扩展方向 |
|------|-----|---------|
| Agent | 4 个 Agent | SubAgent 模式(专科医生) |
| Skill | 1 个 | 渐进式披露(3 层知识) |
| 工具 | @Tool 注解 | MCP 独立 Server |
| 回退 | 2 级(失败→降级) | 4 级路由 |
| Gates | 5 个 | 15 个全流程门禁 |
| 隔离 | 单 JVM | K8s Pod 进程隔离 |
| 进化 | 案例自动生成 | 模式识别 + Prompt 自优化 |
---
## 十、面试话术(精简版)
> "我用 Spring AI Alibaba 实现了一个故障诊断 Agent 系统。
>
> 入口层是**意图识别**:先判断用户想干什么——诊断故障、查文档、查案例还是闲聊。
> 非诊断意图直接走轻量路径,只有诊断意图才启动 Agent 全链路,节省资源。
>
> 4 Agent 协作:Supervisor 调度、Planner 制定策略、
> Executor 调用工具收集证据、Verifier 验证报告防止编造。
>
> 诊断流程封装成了 Skill,标准化 6 个步骤和异常处理。
> Harness 层 5 个门禁保证质量——最关键的是输出门禁,
> Verifier 会对比报告数据和工具返回数据,发现编造就驳回。
>
> 闭环机制:用户反馈 → BadCase 分析 → Prompt 优化。
> 案例自动沉淀,系统越用越智能。"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,157 @@
# 证据评分与用户反馈架构
## 一、整体架构
```
用户对话
↓
ChatService.executeChat / executeChatComplex
↓ SUCCESS 后写入 answer,异步触发
EvaluationService.evaluate(sessionId, answer)
└─ 读取 tool_invocation 事实 → 规则引擎 → 写 selfEvaluation
用户提交反馈
↓
POST /api/feedback { sessionId, feedback: "useful" | "not_useful" }
↓
FeedbackService.submitFeedback
├─ 写 DiagnosisSession.feedback
├─ useful → CaseLibraryService.createFromSession → 写 case_library
└─ not_useful → 仅写 feedback,status 不变
```
---
## 二、评分规则(evidence_score)
### 定位
`evidence_score` 衡量的是**证据收集充分度**,不是答案准确性。
- 能证明的:Agent 是否有尝试收集证据、检索是否命中
- 不能证明的:答案是否有幻觉、推理是否正确
### 数据来源
规则引擎只消费 `tool_invocation` 表的事实记录,不依赖 LLM 判断。
### 规则定义
| 规则名 | 条件 | delta |
|---|---|---|
| `no_tool_call` | 无任何工具调用 | 直接 0 分,不参与加权 |
| `execution_failed` | status = FAILED | 直接 0 分,不参与加权 |
| `has_successful_tool_call` | 至少 1 次成功调用 | +30 |
| `l0_exact_match` | 任意调用有 L0 精确匹配命中 | +35 |
| `l1_semantic_match` | 无 L0 命中但有 L1 语义匹配 | +20 |
| `retrieval_no_hit` | 有检索调用但无任何命中 | -10 |
| `all_tool_calls_failed` | 全部调用失败 | -20 |
> L0 和 L1 互斥取高优先级(L0 命中时跳过 L1 分支)。
### selfEvaluation 字段格式
```json
{
"evidence_score": 65,
"source": "rule",
"factors": [
{"name": "has_successful_tool_call", "delta": 30, "description": "有成功的工具调用(20次)"},
{"name": "l0_exact_match", "delta": 35, "description": "L0 精确匹配命中"}
]
}
```
| 字段 | 说明 |
|---|---|
| `evidence_score` | 0-100 整数 |
| `source` | 当前固定为 `"rule"`;预留 `"llm"` 供后续扩展 |
| `factors` | 命中的规则列表,含 name / delta / description |
| `llm_opinion` | 预留字段(未实现),LLM 观点叠加时在此扩展 |
### 已知边界
- 非检索工具(DateTimeTools、QueryMetricsTools 等)不写 `tool_invocation`,这类 session 的 evidence_score = 0,属于设计边界
- 评分为异步写入(`@Async`),失败时 `selfEvaluation` 保持 null,前端需处理 null
---
## 三、反馈机制
### API
```
POST /api/feedback
Content-Type: application/json
{
"sessionId": "xxx",
"feedback": "useful" | "not_useful"
}
```
**响应**
```json
{
"success": true,
"message": "反馈已记录",
"caseId": "uuid 或 null"
}
```
### 后端行为
| feedback 值 | 操作 |
|---|---|
| `useful` | 写 `DiagnosisSession.feedback = "useful"`,生成 `CaseLibrary` 记录,返回 caseId |
| `not_useful` | 写 `DiagnosisSession.feedback = "not_useful"`,status 不变 |
| 其他值 | 返回 HTTP 400 |
### 重要设计决策
**BAD_CASE 不改 status 字段**
`status` 表示执行状态(RUNNING/SUCCESS/FAILED),是独立维度,不能被质量标签覆盖。
查询 BadCase 使用:`WHERE feedback = 'not_useful'`
**useful 触发案例沉淀规则**
| CaseLibrary 字段 | 来源 |
|---|---|
| caseId | UUID |
| diagnosisId | DiagnosisSession.sessionId |
| sourceType | AUTO |
| faultCategory | GENERAL(暂时,后续人工补充) |
| title | query 前 100 字符 |
| rootCause / solution | DiagnosisSession.answer(完整答案) |
| createdBy | "system" |
**幂等性**:同一 sessionId 重复提交 useful,返回已有 caseId,不重复插入 case_library。
---
## 四、数据库变更
### V008(新增)
```sql
ALTER TABLE diagnosis_session ADD COLUMN answer LONGTEXT COMMENT 'Agent 返回给用户的完整答案';
```
### diagnosis_session 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
| `answer` | LONGTEXT | Agent 完整回答,useful 案例沉淀的内容来源 |
| `self_evaluation` | JSON | 证据评分结果,格式见上 |
| `feedback` | VARCHAR(16) | useful / not_useful / null |
| `status` | VARCHAR(16) | 执行状态,不受 feedback 影响 |
---
## 五、扩展方向(Phase 2)
- **LLM 观点层**:在 `selfEvaluation` 的 `llm_opinion` 字段叠加 LLM 结构化观点(has_root_cause、has_solution 等),作为独立 factors,不改变现有规则逻辑
- **案例结构化字段**:useful 触发时自动提取 faultCategory / errorCode,替代暂时的 GENERAL
- **重复召回问题**:Executor Prompt 约束或工具层 session 维度去重(见 [ISS-001](../issues/ISS-001-duplicate-retrieval.md))
@@ -0,0 +1,220 @@
# Current MVP Architecture Snapshot
**Updated**: 2026-07-05
This document records the current runnable MVP architecture. Older architecture notes in this folder still represent design history; this file should be read as the current snapshot for demos, interviews, and next-step planning.
## 1. Positioning
The MVP is an Agent engineering project for traceable troubleshooting, not a generic chatbot.
Core goals:
- Support normal chat-based diagnosis.
- Support AIOps alert-triggered diagnosis.
- Keep tool calls explicit and traceable.
- Keep RAG retrieval observable through `lookup_knowledge`.
- Persist enough execution evidence for replay, evaluation, and interview explanation.
## 2. Runtime Architecture
```text
HTTP API
-> ChatService / AiOpsService
-> Agent orchestration
-> Supervisor / Planner / Executor / Verifier
-> Tools
-> lookup_knowledge
-> query_logs
-> query_metrics
-> other diagnosis tools
-> Persistence
-> diagnosis_session
-> agent_step
-> tool_invocation
-> Trace API
-> DiagnosisTraceService
```
Current entry points:
- `ChatService`: user-driven troubleshooting and follow-up diagnosis.
- `AiOpsService`: alert-driven diagnosis, including payload mode and auto-discovery mode.
- `DiagnosisTraceService`: trace view of session, steps, tool calls, and self-evaluation.
## 3. Chat Diagnosis Flow
```text
User question
-> ChatService
-> simple response or diagnosis flow
-> Planner creates investigation direction
-> Executor calls tools for evidence
-> lookup_knowledge
-> query_logs
-> query_metrics
-> Verifier checks final diagnosis quality
-> self_evaluation.verifier_evaluation
-> diagnosis trace
```
The chat path uses the LLM verifier as the main quality gate. The verifier result is persisted under `diagnosis_session.self_evaluation.verifier_evaluation`.
## 4. AIOps Diagnosis Flow
```text
AIOps request
-> AiOpsService
-> payload mode or auto-discovery mode
-> build alert-focused diagnosis prompt
-> append recommended lookup_knowledge query when payload exists
-> Agent diagnosis flow
-> Supervisor / Planner / Executor
-> evidence tools
-> final report
-> AiOpsRuleEvaluationService
-> self_evaluation.aiops_rule_evaluation
-> diagnosis trace
```
AIOps keeps two modes:
- Payload mode: the request already contains alert fields such as alert name, service, metric, severity, and symptom. The system builds a recommended knowledge query from these fields.
- Auto-discovery mode: the system follows the original alert-discovery behavior and lets the Agent collect alert context through tools.
The AIOps verifier is currently lightweight and rule-based. It checks:
- Whether the final report exists.
- Whether the result stays focused on the alert payload when payload exists.
- Whether evidence tools were used, especially `lookup_knowledge`, `query_logs`, and `query_metrics`.
## 5. RAG Architecture
```text
lookup_knowledge
-> L0 domain/entity hint
-> matched domain
-> matched keywords/entities
-> metadata filter signal
-> VectorSearchService
-> Spring AI VectorStore path
-> Milvus SDK fallback path
-> evidence post-processing
-> score / rawScore / scoreLabel
-> source metadata
-> title / breadcrumb / content evidence block
-> tool_invocation record
```
Important decisions:
- `lookup_knowledge` remains an explicit Agent tool. It is not replaced by an implicit chat Advisor because the project needs visible Agent decision-making.
- L0 is retained but downgraded. It is a domain/entity hint and explainability signal, not the final recall decision.
- L1 retrieval now goes through `VectorSearchService`.
- Spring AI `VectorStore` is the preferred retrieval path.
- The original Milvus SDK path is retained as fallback and compatibility path.
- `title`, `breadcrumb`, and `content` participate in embedding text so chunk context is less likely to be lost.
- Retrieval output keeps compatibility fields: `score`, `rawScore`, and `scoreLabel`.
Vector retrieval modes:
```text
retrieval.vector-store.mode=auto # Prefer Spring AI VectorStore, fallback to SDK
retrieval.vector-store.mode=spring-ai # Use Spring AI VectorStore only
retrieval.vector-store.mode=sdk # Use original Milvus SDK path
```
## 6. Persistence And Trace
Current trace-related persistence:
```text
diagnosis_session
-> final_report
-> self_evaluation
-> verifier_evaluation
-> aiops_rule_evaluation
agent_step
-> role
-> step input/output
-> execution order
tool_invocation
-> tool_name
-> query
-> retrieval_layer
-> retrieval_details
-> evidence blocks
-> duration
```
Trace API aggregates these records into a session-level view:
- Agent step sequence.
- Tool calls and retrieval details.
- Final diagnosis report.
- Chat verifier status.
- AIOps rule verifier status.
## 7. Quality Gates
Current quality gates:
- Chat verifier: LLM-based final answer verification for normal diagnosis.
- AIOps rule verifier: lightweight deterministic checks for alert-focused diagnosis.
- Diagnosis eval baseline: fixture-based evaluation for trace and evidence behavior.
- RAG retrieval baseline: golden query set with offline baseline report.
- Live RAG acceptance: post-reindex script for validating retrieval against the running stack.
These gates are intentionally layered. The MVP proves the Agent chain can produce evidence, persist it, and be inspected after execution.
## 8. Current Completion State
Completed for the current MVP stage:
- Explicit `lookup_knowledge` Agent tool.
- L0 + L1 retrieval shape retained.
- L0 downgraded to domain/entity hint.
- Spring AI VectorStore retrieval path integrated.
- Milvus SDK fallback retained.
- RAG evidence post-processing added.
- Breadcrumb/title/content embedding text improved.
- RAG offline baseline and live acceptance script added.
- AIOps payload query augmentation added.
- AIOps lightweight verifier added.
- Trace summary includes both chat verifier and AIOps verifier signals.
Deferred future enhancements:
- LLM QueryTransformer / MultiQuery.
- BM25, RRF, and reranker.
- Neighbor chunk or section-level context expansion.
- VectorStore write path migration.
- Full LLM-based AIOps verifier.
- More complete golden set for recall, MRR, and nDCG metrics.
## 9. Key Code References
- `src/main/java/com/superbiz/agent/service/ChatService.java`
- `src/main/java/com/superbiz/agent/service/AiOpsService.java`
- `src/main/java/com/superbiz/agent/service/AiOpsRuleEvaluationService.java`
- `src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`
- `src/main/java/com/superbiz/agent/service/VectorSearchService.java`
- `src/main/java/com/superbiz/agent/service/VectorIndexService.java`
- `src/main/java/com/superbiz/agent/service/SpringAiVectorStoreSidecarService.java`
- `src/main/java/com/superbiz/agent/service/DiagnosisTraceService.java`
- `src/main/java/com/superbiz/agent/service/ToolInvocationRecorder.java`
- `src/main/java/com/superbiz/agent/service/SelfEvaluationMergeService.java`
## 10. Supporting Materials
- `mvp/issues/rag-refactor-plan.md`
- `eval/rag-retrieval/README.md`
- `scripts/eval_rag_live_acceptance.py`
- `interview/rag-refactor-story.md`
- `interview/rag-vectorstore-interview-notes.md`
- `interview/rag-retrieval-quality-report.md`
- `interview/rag-breadcrumb-embedding-acceptance.md`
- `interview/aiops-query-augmentation.md`
- `interview/aiops-lightweight-verifier.md`
@@ -0,0 +1,715 @@
# SuperBizAgent MVP 完整实施计划(AI 执行)
## 协作分工
```
用户角色:规划者 + 验证者 + 架构师
AI 角色: 执行者 + 编码者 + 记录者
用户负责:
├─ 确认架构设计
├─ 验收每个阶段产出
├─ 调整优先级和方向
└─ 最终验收和部署决策
AI 负责:
├─ 编写全部代码
├─ 编写全部测试
├─ 执行测试验证
├─ 记录实施过程
├─ 遇到问题提出方案供用户决策
└─ 自动化构建和本地验证
```
---
## 总览:3 个 Phase,13 天
```
Phase 1: 基础设施(5天)
├─ Day 1-2: 数据库 + 实体 + 会话管理
├─ Day 3: 代码结构重构
└─ Day 4-5: 文档管理(CRUD + Milvus)
Phase 2: 核心功能(5天)
├─ Day 6-7: 意图识别 + RAG 两层加载
├─ Day 8-9: 4 Agent 协作 + Skill
└─ Day 10: 工具层开发
Phase 3: 闭环优化(3天)
├─ Day 11: Verifier + Harness
├─ Day 12: 反馈机制 + 案例沉淀
└─ Day 13: 端到端测试 + 验收
```
---
## Phase 1:基础设施(5天)
### Day 1-2:数据库 + 实体 + 会话
#### 任务 1.1:MySQL 表结构(Flyway 迁移)
```sql
产出文件:
src/main/resources/db/migration/
├── V001__create_diagnosis_record.sql
├── V002__create_case_library.sql
└── V003__create_api_document.sql
依据文档:
- docs/tables/diagnosis_record.md
- docs/tables/case_library.md
- docs/tables/api_document.md
关键点:
- 使用 Flyway 版本管理
- 索引:trace_id, error_code, fault_category
- JSON 字段:steps_executed, evidence_chain
- 时间字段:created_at, updated_at 自动维护
验收标准:
✓ 执行 mvn flyway:migrate 成功
✓ 3 张表创建成功
✓ 索引完整
✓ 约束正确
```
#### 任务 1.2:JPA 实体类
```java
产出文件:
src/main/java/com/superbiz/agent/domain/entity/
├── DiagnosisRecord.java
├── CaseLibrary.java
└── ApiDocument.java
技术栈:
- Spring Data JPA
- Lombok (@Data, @Builder)
- Hibernate @JdbcTypeCode(SqlTypes.JSON)
验收标准:
✓ 字段与 DDL 一致
✓ 枚举映射正确
✓ JSON 字段序列化正常
✓ 编译通过
```
#### 任务 1.3:Repository 层
```java
产出文件:
src/main/java/com/superbiz/agent/repository/
├── DiagnosisRecordRepository.java
├── CaseLibraryRepository.java
└── ApiDocumentRepository.java
常用查询:
- findByOrderId
- findByTraceId
- findByErrorCodeAndFaultCategory
- findTopByOrderByCreatedAtDesc
验收标准:
✓ 继承 JpaRepository
✓ 单元测试覆盖(@DataJpaTest + H2)
✓ 分页查询正确
```
#### 任务 1.4:Redis 会话管理
```java
产出文件:
src/main/java/com/superbiz/agent/session/
├── SessionManager.java # 接口
├── RedisSessionManager.java # Redis 实现
├── SessionContext.java # 会话上下文
└── SessionConfiguration.java # 配置类
功能:
- 替换内存 HashMap
- TTL:30 分钟
- JSON 序列化(Jackson)
- 按 sessionId 存取删
验收标准:
✓ 单元测试通过
✓ Redis 连接成功
✓ 序列化/反序列化正确
✓ TTL 生效
```
---
### Day 3:代码结构重构
#### 任务 3.1:包名重构
```
重构前:org.example
重构后:com.superbiz.agent
操作:
1. IDEA Refactor → Rename Package
2. 全局搜索替换 import
3. pom.xml 更新 mainClass
验收标准:
✓ 编译通过
✓ 无遗漏的 org.example
✓ 启动成功
```
#### 任务 3.2:分层结构优化
```
目标结构:
src/main/java/com/superbiz/agent/
├── controller/ # REST 接口
├── service/ # 业务逻辑
├── repository/ # 数据访问
├── domain/
│ ├── entity/ # JPA 实体
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ └── enums/ # 枚举
├── agent/ # Agent 层
│ ├── supervisor/
│ ├── planner/
│ ├── executor/
│ └── verifier/
├── tool/ # 工具层
├── harness/ # Harness 控制
│ ├── gate/
│ └── interrupt/
├── skill/ # Skill 定义
├── session/ # 会话管理
├── intent/ # 意图识别
├── rag/ # RAG 加载
└── config/ # 配置
验收标准:
✓ 目录结构清晰
✓ 职责单一
✓ 编译通过
```
#### 任务 3.3:DTO 抽离
```java
产出文件:
src/main/java/com/superbiz/agent/domain/dto/
├── DiagnosisRequest.java
├── DiagnosisResponse.java
├── DocumentUploadRequest.java
├── CaseQueryRequest.java
└── ...
要求:
- Controller 不直接依赖 Entity
- MapStruct 做对象转换
- 校验注解 @Valid + @NotNull
- 统一响应包装类 Result<T>
验收标准:
✓ Controller 不 import Entity
✓ 原有接口兼容
✓ 编译通过
```
---
### Day 4-5:文档管理
#### 任务 4.1:文档上传
```java
产出文件:
controller/DocumentController.java
service/DocumentService.java
service/TextExtractor.java
service/VectorService.java
接口:POST /api/documents/upload
功能:
1. 接收文件(Word/PDF/Markdown)
2. 提取纯文本
3. 分块(chunk_size=500, overlap=50)
4. 向量化(DashScopeEmbedding)
5. 写 MySQL + Milvus
验收标准:
✓ 上传成功返回 document_id
✓ MySQL 记录正确
✓ Milvus 向量正确
✓ 单元测试覆盖
```
#### 任务 4.2:文档查询
```java
接口:
- GET /api/documents/{id}
- GET /api/documents?province=XX&category=YY
验收标准:
✓ 分页查询
✓ 过滤生效
✓ 性能可接受(< 100ms)
```
#### 任务 4.3:文档删除同步
```java
接口:DELETE /api/documents/{id}
功能:
- 删除 MySQL 记录
- 同步删除 Milvus 向量
- 事务一致性
验收标准:
✓ MySQL + Milvus 同步删除
✓ 事务回滚正确
```
#### 任务 4.4:混合检索实现
```java
产出文件:
tool/DocumentSearchTool.java
策略:
1. 精确匹配(MySQL)
2. 语义检索(Milvus)
3. RRF 融合排序
验收标准:
✓ 精确匹配优先
✓ 语义检索补漏
✓ 返回 Top 3
✓ 单元测试覆盖
```
---
## Phase 2:核心功能(5天)
### Day 6-7:意图识别 + RAG
#### 任务 6.1:意图识别模块
```java
产出文件:
intent/IntentClassifier.java
intent/L0RulesMatcher.java
intent/L1AgentClassifier.java
intent/IntentResult.java
L0 规则匹配:
- 正则:订单号、traceId、错误码
- 关键词:报错、异常、失败
- 返回:诊断/文档/案例/闲聊
L1 小模型 Agent:
- 输入:用户原始输入
- Prompt:分类意图
- 输出:意图 + 置信度
验收标准:
✓ L0 命中率 80%+
✓ L1 准确率 90%+
✓ 延迟 < 200ms
✓ 单元测试覆盖
```
#### 任务 6.2:RAG 两层加载
```java
产出文件:
rag/RagLoader.java
rag/L1PreloadService.java
rag/L2OnDemandService.java
L1 预加载:
- 触发时机:意图识别后,Planner 启动前
- 内容:通用领域知识(架构、流程、高频错误码)
- 注入:Planner System Prompt
L2 按需加载:
- 触发时机:Executor 拿到 errorCode 后
- 内容:具体接口文档
- 调用:searchDoc
验收标准:
✓ L1 预加载成功
✓ L2 按需调用成功
✓ 单元测试覆盖
```
---
### Day 8-9:4 Agent 协作 + Skill
#### 任务 8.1:4 Agent 定义
```java
产出文件:
agent/supervisor/SupervisorAgent.java
agent/planner/PlannerAgent.java
agent/executor/ExecutorAgent.java
agent/verifier/VerifierAgent.java
配置文件:
src/main/resources/prompts/
├── supervisor-system.md
├── planner-system.md
├── executor-system.md
└── verifier-system.md
技术栈:
- Spring AI Alibaba
- SupervisorAgent + ReactAgent
- @Tool 注解
验收标准:
✓ 4 Agent 注册成功
✓ 协作流程跑通
✓ Supervisor 调度正确
```
#### 任务 8.2:Skill 实现
```java
产出文件:
skill/SkillDefinition.java
skill/DiagnoseByOrderIdSkill.java
skill/SkillRegistry.java
工作流(6 步):
1. queryOrder
2. searchDoc (L2 按需)
3. queryLogs (Mock)
4. recommendCase
5. 生成报告
6. Verifier 验证
验收标准:
✓ 6 步流程正确
✓ 失败处理正确(ABORT/SKIP)
✓ 单元测试覆盖
```
---
### Day 10:工具层开发
#### 任务 10.1:queryOrder 工具
```java
产出文件:
tool/QueryOrderTool.java
功能:
- 只读查询 MySQL
- 返回订单信息 + 错误信息
- SQL 注入防护
验收标准:
✓ 查询正确
✓ 超时控制(10s)
✓ 单元测试覆盖
```
#### 任务 10.2:searchDoc 工具
```java
产出文件:
tool/SearchDocTool.java
功能:
- 调用混合检索
- 返回 Top 3 文档片段
验收标准:
✓ 调用成功
✓ 结果格式正确
✓ 单元测试覆盖
```
#### 任务 10.3:recommendCase 工具
```java
产出文件:
tool/RecommendCaseTool.java
功能:
- 精确匹配:error_code + fault_category
- 语义检索:description 向量相似度
- RRF 融合
验收标准:
✓ 推荐准确
✓ 返回 Top 3
✓ 单元测试覆盖
```
#### 任务 10.4:getCurrentTime 工具
```java
产出文件:
tool/GetCurrentTimeTool.java
功能:
- 返回当前时间戳
- 格式化输出
验收标准:
✓ 返回正确
```
---
## Phase 3:闭环优化(3天)
### Day 11:Verifier + Harness
#### 任务 11.1:Verifier Agent
```java
产出文件:
agent/verifier/VerifierAgent.java
验证逻辑:
1. 事实核查(报告数据 vs 工具返回数据)
2. 完整性检查(3 章节不能为空)
判决:
- PASS:通过
- REVISE:需修正
- REJECT:驳回
验收标准:
✓ 事实核查正确
✓ 编造检测生效
✓ 单元测试覆盖
```
#### 任务 11.2:Harness 5 Gates
```java
产出文件:
harness/gate/InputGates.java
harness/gate/ExecutionGates.java
harness/gate/OutputGates.java
门禁清单:
- Gate 1: 输入参数非空
- Gate 2: 5 分钟内重复 → 缓存
- Gate 3: 工具超时(10s)
- Gate 4: 报告完整性
- Gate 5: 置信度阈值(60)
验收标准:
✓ 5 Gates 生效
✓ 中断机制正确
✓ 单元测试覆盖
```
---
### Day 12:反馈机制 + 案例沉淀
#### 任务 12.1:反馈接口
```java
产出文件:
controller/FeedbackController.java
service/FeedbackService.java
接口:POST /api/diagnosis/{id}/feedback
参数:useful / not_useful
功能:
- 更新 diagnosis_record.feedback
- useful → 自动生成 case_library
验收标准:
✓ 反馈记录成功
✓ 案例生成正确
✓ 单元测试覆盖
```
#### 任务 12.2:案例自动生成
```java
产出文件:
service/CaseGenerationService.java
触发条件:
- feedback = useful
- confidence >= 80
生成逻辑:
- 提取关键信息
- 生成 case_library 记录
- 向量化 solution_steps
验收标准:
✓ 案例生成正确
✓ 向量化成功
✓ 单元测试覆盖
```
---
### Day 13:端到端测试 + 验收
#### 任务 13.1:Mock 5 个场景
```
场景 1:外部接口故障(广东社保 40003)
场景 2:内部空指针异常
场景 3:数据库连接超时
场景 4:意图不明(闲聊)
场景 5:缓存命中(重复诊断)
验收标准:
✓ 5 个场景全部跑通
✓ 诊断报告正确
✓ 反馈闭环完整
```
#### 任务 13.2:性能测试
```
指标:
- 诊断延迟 < 10s(P95)
- 意图识别 < 200ms
- 文档检索 < 500ms
- 并发 10 QPS 稳定
验收标准:
✓ 性能达标
✓ 无内存泄漏
✓ 无明显瓶颈
```
#### 任务 13.3:文档更新
```
产出文件:
docs/
├── API.md # 接口文档
├── DEPLOYMENT.md # 部署指南
└── TEST_REPORT.md # 测试报告
验收标准:
✓ 文档完整
✓ 部署可复现
✓ 测试报告详实
```
---
## 测试要求
### 单元测试
```
框架:JUnit 5 + Mockito
覆盖率:
- Repository: 100%
- Service: 80%+
- Tool: 80%+
- Agent: 70%+
- Controller: 70%+
```
### 集成测试
```
框架:@SpringBootTest
覆盖:
- Redis 集成
- MySQL 集成
- Milvus 集成
- Agent 协作
```
### E2E 测试
```
工具:RestAssured
场景:5 个 Mock 场景
```
---
## 实施记录格式
每完成一个任务,AI 在此文档追加:
```markdown
---
## [完成] 任务 X.X:任务名称
**执行时间**:2026-XX-XX HH:mm
**产出文件**:
- path/to/file1.java (126 行)
- path/to/file2.java (89 行)
**关键决策**:
- 决策点:选择方案 A,因为...
- 权衡点:备选方案 B 的劣势是...
**遇到的问题**:
- 问题:XXX
- 解决方案:YYY
- 影响范围:ZZZ
**测试结果**:
✓ 单元测试:8/8 通过
✓ 集成测试:3/3 通过
✓ 代码覆盖率:85%
**验收状态**:⏳ 等待用户确认 / ✅ 已通过
**用户反馈**:(用户确认后填写)
```
---
## 当前进度
```
Phase 1: 基础设施(5天) [ ] 0%
├─ Day 1-2: 数据库 + 实体 [ ] 未开始
├─ Day 3: 代码结构重构 [ ] 未开始
└─ Day 4-5: 文档管理 [ ] 未开始
Phase 2: 核心功能(5天) [ ] 0%
├─ Day 6-7: 意图识别 + RAG [ ] 未开始
├─ Day 8-9: Agent + Skill [ ] 未开始
└─ Day 10: 工具层 [ ] 未开始
Phase 3: 闭环优化(3天) [ ] 0%
├─ Day 11: Verifier + Harness [ ] 未开始
├─ Day 12: 反馈 + 案例 [ ] 未开始
└─ Day 13: E2E 测试 [ ] 未开始
总体进度:0/13 天
```
---
## 下一步
等待用户确认:
1. ✅ 这个完整计划是否符合预期?
2. 有没有需要调整的优先级?
3. 有没有需要增删的任务?
4. 确认后开始执行 Phase 1 Day 1-2。
@@ -0,0 +1,211 @@
# 实施规划
## Phase 1:核心功能(第1周)
### 实现内容
```
✅ diagnosis_record 表
✅ case_library 表
✅ api_document 表
✅ Redis 会话管理
✅ 单次诊断流程
```
### 不实现
```
❌ conversation_history 表(先不加)
❌ 会话同步(先不做)
❌ 追问功能(先不支持)
```
### 验收标准
```
- 用户输入订单号 → 返回诊断报告
- 诊断记录持久化到 MySQL
- 可以查询历史诊断
- 可以统计诊断成功率
- 文档可以导入、查询、删除
- 案例可以推荐
```
---
## Phase 2:追问功能(第2周)
### 实现内容
```
✅ 支持多轮对话(基于 Redis 上下文)
✅ conversation_history 表(可选)
✅ 会话上下文管理
```
### 验收标准
```
- 用户可以追问细节
- Agent 能基于上下文回答
- 追问不创建新的诊断记录
```
---
## Phase 3:优化分析(第3周)
### 实现内容
```
✅ 会话同步(Redis → MySQL)
✅ BadCase 分析
✅ 追问频率统计
✅ 案例质量评分
```
### 验收标准
```
- 重要会话自动同步到 MySQL
- 可以分析用户追问模式
- 可以优化 Prompt 和功能
```
---
## 技术债务清单
### 待优化项(Phase 4+)
```
1. api_document 增强
- 软删除(archived_at)
- 启用开关(enabled)
- 批次管理(batch_id)
- 状态细化(PARSING/SPLITTING/INDEXING...)
2. case_library 增强
- 复杂评分(useful_count + score)
- 标签分类(tags)
- 版本管理
- 案例合并
3. 性能优化
- Redis 缓存有效文档列表
- 分页查询优化
- 索引优化
4. 监控告警
- 诊断成功率监控
- 诊断耗时监控
- 文档索引状态监控
```
---
## 数据迁移计划
### 如果已有旧数据
```
1. diagnosis_record 迁移
- 旧字段 → 新字段映射
- order_id → business_id
- province → fault_source
- api_url → fault_target
2. 执行迁移脚本
UPDATE diagnosis_record SET
business_id = order_id,
fault_category = 'EXTERNAL_API',
fault_source = province,
fault_target = api_url
WHERE fault_category IS NULL;
3. 验证数据一致性
```
---
## 部署检查清单
### Phase 1 部署前
```
□ MySQL 数据库已创建
□ 三张核心表已创建(diagnosis_record/case_library/api_document)
□ Redis 已配置并可连接
□ Milvus Collection 已创建
□ 向量化服务(DashScope)配置正确
□ 文件上传目录已创建并有写权限
□ 应用配置文件检查完成
```
### 配置文件示例
```yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/diagnosis_system
username: root
password: xxx
redis:
host: localhost
port: 6379
database: 0
milvus:
host: localhost
port: 19530
collection-name: api_doc_collection
dashscope:
api-key: sk-xxx
file:
upload:
path: /data/uploads
```
---
## 回滚方案
### 数据库回滚
```sql
-- 保留旧表备份
CREATE TABLE diagnosis_record_backup_20240622 AS SELECT * FROM diagnosis_record;
-- 回滚时恢复
DROP TABLE diagnosis_record;
RENAME TABLE diagnosis_record_backup_20240622 TO diagnosis_record;
```
### Milvus 回滚
```
- Milvus 数据无法回滚
- 建议:重要操作前先备份 Collection
- 或者:保留原始文件,可重新索引
```
---
## 监控指标
### 核心指标
```
1. 诊断成功率
- 目标:> 85%
- 告警:< 80%
2. 诊断耗时
- 目标:P95 < 10s
- 告警:P95 > 15s
3. 文档索引成功率
- 目标:> 95%
- 告警:< 90%
4. 案例推荐准确率
- 目标:> 70%
- 评估:用户反馈
```
@@ -0,0 +1,421 @@
# 知识库检索架构(L0 + L1)
**更新日期**: 2026-06-25
---
## 一、概述
`LookupKnowledgeTool` 实现两阶段混合检索:
- **L0 精确匹配**:基于内存索引的关键词匹配(< 10ms),索引从数据库加载
- **L1 语义检索**:基于 Milvus 向量数据库的相似度搜索(200-500ms)
---
## 二、完整流程
```
用户查询
│
▼
┌─────────────────────────────┐
│ L0: 关键词精确匹配 │ (< 10ms)
│ • 从内存索引做关键词匹配 │
│ • 索引来源: ApiDocument DB│
└──────────┬──────────────────┘
│
▼
┌──────┴──────┐
│ matches=1 │ ← 唯一匹配(高置信度)
└──────┬──────┘
│
▼
┌──────────────┐ ┌──────────────────┐
│ 跳过 L1 │ │ L0 返回正文摘要 │
│ 置信度: high │ │ buildCompactSummary│
└──────────────┘ └──────────────────┘
┌──────┴──────┐
│ matches=0 │ ← 无匹配
└──────┬──────┘
│
▼
┌──────────────┐ ┌──────────────────┐
│ 触发 L1 │ │ L0 无结果 │
│ L1 语义检索 │ │ 仅有 L1 补充结果 │
└──────────────┘ └──────────────────┘
┌──────┴──────┐
│ matches>=2 │ ← 多匹配
└──────┬──────┘
│
▼
┌──────────────┐
│ 触发 L1 │
│ L1 语义检索 │
└──────┬──────┘
│
┌─────┴─────┐
│ ║ │
▼ ▼
L1 有结果 L1 无结果
│ │
▼ ▼
元数据摘要 正文摘要
(不读文件) (读文件)
```
---
## 三、L0 返回内容策略
根据匹配场景决定 L0 返回给 LLM 的上下文内容量。
### 3.1 唯一匹配(高置信度,matches=1)
**策略**: `buildCompactSummary()`
L1 被跳过,LLM 只有 L0 信息来源,需要提供足够的正文内容。
```
文档: 支付网关错误码定义
摘要: 记录了支付网关所有核心错误码的含义及排查方向
章节:
- 超时类错误
- 业务类错误
- 签名类错误
---
**含义**:支付网关请求超时
**常见原因**:网络延迟、第三方服务响应慢
...
```
| 组成部分 | 说明 | 大小 |
|---------|------|------|
| title + summary | 从内存索引获取 | ~50-100 字符 |
| 章节标题列表 | 从文件解析 `##` 标题 | ~50-200 字符 |
| 正文片段 | 去 frontmatter/标题行/空行,短文档 800/长文档 500 字符截断 | ~300-800 字符 |
| **总计** | | **~400-1000 字符** |
### 3.2 多匹配 + L1 有结果
**策略**: `buildMetadataOnlySummary()`
L1 已有语义内容片段,L0 仅需告知 LLM 命中了哪些文档。**不读文件**,仅用内存索引。
```
文档: 支付网关错误码定义
摘要: 记录了支付网关所有核心错误码的含义及排查方向
关键词: ERR_TIMEOUT, 超时, 支付网关
来源: api/payment-errors.md
```
| 组成部分 | 说明 | 大小 |
|---------|------|------|
| title + summary + keywords | 全部从内存索引获取 | ~100-200 字符 |
| **总计** | | **~100-200 字符** |
### 3.3 多匹配 + L1 无结果
**策略**: `buildCompactSummary()`(同 3.1)
L1 未返回结果, L0 作为兜底提供正文内容。
---
## 四、决策矩阵
```
needFullContent = highConfidence || !hasL1
```
| 场景 | matches | L1 结果 | needFullContent | L0 策略 | 是否读文件 | 上下文大小 |
|------|:-------:|:--------:|:---------------:|---------|:---------:|:--------:|
| 唯一匹配 | 1 | 未执行 | true | `buildCompactSummary` | 是 | ~600 字符 |
| 多匹配 + L1 有结果 | 2+ | 有 | false | `buildMetadataOnlySummary` | **否** | ~150 字符 |
| 多匹配 + L1 无结果 | 2+ | 无 | true | `buildCompactSummary` | 是 | ~600 字符 |
| 无匹配 | 0 | 有 | — | 无 L0,仅 L1 | 否 | 0 |
---
## 五、代码结构
```
LookupKnowledgeTool
├── lookupKnowledge(query) # 入口:编排 L0 + L1
├── buildResult(l0, l1, confidence) # 组装结果,选择摘要策略
├── buildCompactSummary(entry) # 元数据 + 章节 + 正文片段(读文件)
├── buildMetadataOnlySummary(entry) # 仅元数据(不读文件)
├── countMdHeadings(content) # 统计章节数(日志用)
└── extractFirstMeaningfulLine(...) # 提取首个有意义文本行(日志用)
```
### 关键逻辑(buildResult)
```java
boolean needFullContent = highConfidence || !hasL1;
String content = needFullContent
? buildCompactSummary(first)
: buildMetadataOnlySummary(first);
```
---
## 六、日志输出示例
### 多匹配场景(matches=2, L1 有结果)
```
[L0 精确匹配] 完成: matches=2, time=3ms
[置信度判断] highConfidence=false, reason=多个或零个匹配
[L1 语义检索] L0非唯一匹配,触发L1语义检索...
[L1 语义检索] 完成: matches=1, time=245ms
----------------------------------------
<<< [工具返回] lookup_knowledge
<<< [L0 主结果] 标题: 支付网关错误码定义
<<< [L0 主结果] 摘要: 记录了支付网关所有核心错误码的含义及排查方向 ← 仅元数据
<<< [L0 主结果] 内容: 126 字符, 0 个章节 ← 约150字符
<<< [L1 补充] 相似度: 0.8234
<<< [L1 补充] 内容片段: 支付网关请求超时... ← L1 提供具体内容
```
### 唯一匹配场景(matches=1, 跳过 L1)
```
[L0 精确匹配] 完成: matches=1, time=2ms
[置信度判断] highConfidence=true, reason=唯一匹配
[L1 语义检索] L0唯一匹配,跳过L1检索
----------------------------------------
<<< [工具返回] lookup_knowledge
<<< [L0 主结果] 标题: 支付网关错误码定义
<<< [L0 主结果] 摘要: 记录了支付网关所有核心错误码的含义及排查方向
<<< [L0 主结果] 内容: 725 字符, 3 个章节 ← 约700字符
```
---
## 七、MVP 效率评估 & 改进方向
### 7.1 当前效率评估
| 维度 | 评分 | 说明 |
|------|:----:|------|
| L0 匹配速度 | ★★★★★ | 内存索引,< 10ms,几乎没有优化空间 |
| L1 检索速度 | ★★★★☆ | Milvus 向量检索,200-500ms,取决于数据量 |
| L0 匹配准确率 | ★★☆☆☆ | 子串匹配,无排序无评分,匹配即返回 |
| L1 检索准确率 | ★★★☆☆ | 语义相似度,但分块缺少上下文信息 |
| 召回率(查全) | ★★★☆☆ | L0+L1 两阶段覆盖大多数场景,但缺乏融合重排 |
| 上下文利用率 | ★★★★☆ | 根据场景动态控制 L0 内容量,已优化 |
| **综合** | **★★★☆☆** | **MVP 可用,但检索质量有提升空间** |
### 7.2 关键瓶颈
#### 瓶颈 1:分块丢失上下文(✅ 已修复—见下方 7.5)
当前每个 Chunk 只记录最近的 `##` 标题:
```json
{
"content": "**含义**:支付网关请求超时\n**常见原因**:网络延迟",
"title": "超时类错误",
"chunkIndex": 2
}
```
LLM 收到这个片段时**不知道**它属于"支付网关错误码定义"这个文档,也不知道具体错误码名称是 ERR_TIMEOUT。如果同时检索了多个文档的片段,LLM 容易混淆。
#### 瓶颈 2:L0 关键词匹配过于简单
当前 `KnowledgeIndexService.matchesKeywords()` 只做子串包含匹配,没有:
- 排序/评分(多个匹配时按什么顺序?)
- 权重(标题匹配 > 正文匹配)
- 部分匹配("timeout" 匹配 "ERR_TIMEOUT")
#### 瓶颈 3:L0 和 L1 无交叉融合
两阶段检索结果只是简单的"1位L0 + 1位L1"拼接,没有:
- RRF 或加权融合重排
- 重复内容去重
- 根据相关性选择 top-K
---
### 7.3 改进方向分析
#### 方向 A:面包屑导航(Chunk 携带层级上下文)
**做法**:分块时记录完整的标题层级路径作为 `breadcrumb`。
当前分块 metadata:
```json
{ "title": "超时类错误" }
```
改进后:
```json
{
"title": "超时类错误",
"breadcrumb": "支付网关错误码定义 > 超时类错误 > ERR_TIMEOUT",
"heading_h1": "支付网关错误码定义",
"heading_h2": "超时类错误",
"heading_h3": "ERR_TIMEOUT"
}
```
**收益评估**:
| 场景 | 无面包屑的问题 | 有面包屑的改善 | 提升幅度 |
|------|---------------|---------------|:--------:|
| 单文档多分块 | LLM 知道标题但不知道层级关系 | 清楚"文档>章节>条目"归属 | 中等 |
| 跨文档混合结果 | 分块看不出源文档 | breadcrumb 第一段就是文档标题 | 大 |
| 深层嵌套文档(3+ 级) | 分块内容难以定位 | 完整路径一目了然 | 显著 |
| 向量检索相关性 | 只对 chunk content 做 embedding | breadcrumb 可拼入 content 做 embedding 或单独索引 | 中等 |
**MVP 阶段价值**:当前文档结构较浅(2-3级),breadcrumb 对 LLM 理解帮助中等。但如果后续文档层级加深(像你提到的"排障指南 > 支付网关 > 502错误处理"),价值会显著提升。
**实现成本**:低。修改 `DocumentChunkService` 的分块逻辑,积累当前标题栈,写入 `DocumentChunk` 和 Milvus metadata。
#### 方向 B:混合检索 + RRF 重排
**做法**:L0 关键词和 L1 向量检索并行执行 → 结果用 Reciprocal Rank Fusion 统一排序 → 取 top-K。
```
用户查询 → 并行的:
├── L0 关键词匹配 → 得分向量 S₀
└── L1 向量检索 → 得分向量 S₁
↓
RRF 融合重排
↓
top-K 统一结果
```
RRF 公式:对每个文档 d,`score(d) = Σ 1/(k + rank_r(d))`,其中 k=60(常数)。
**收益评估**:
| 场景 | 当前的问题 | 混合 + RRF | 提升幅度 |
|------|-----------|-----------|:--------:|
| 精确关键词("ERR_TIMEOUT") | L0 匹配但不排序,L1 可能不匹配 | L0 高排名 → RRF 拉到顶部 | 大 |
| 语义查询("支付超时如何处理") | L0 可能不匹配,全靠 L1 | L1 兜底不受影响 | 无变化 |
| 混合查询("ERR_TIMEOUT 支付网关超时") | L0 匹配一个、L1 匹配一个,无融合 | RRF 统一排序,更合理 | 中等 |
| 多文档匹配 | L0 返回无序列表 + L1 独立结果 | 统一排序、去重 | 大 |
**MVP 阶段价值**:RRF 的实现成本和维护成本较高,而当前 MVP 数据量小(6 个文档),人工检查即可确定哪些匹配是好的。**建议数据量 > 50 个文档时引入**。
#### 方向 C:Breadcrumb + Embedding 增强
**做法**:将 breadcrumb 拼入 chunk content 后再做 embedding,让向量包含层级语义。
```java
// 当前
embeddingService.generateEmbedding(chunk.getContent())
// 改进
String augmentedContent = chunk.getBreadcrumb() + "\n" + chunk.getContent();
embeddingService.generateEmbedding(augmentedContent);
```
这样搜索"ERR_TIMEOUT"时,"支付网关错误码定义 > 超时类错误 > ERR_TIMEOUT" 也会匹配到,而不只是 chunk 正文。
| 场景 | 当前 | Breadcrumb + Embedding | 提升 |
|------|------|------------------------|:----:|
| 搜索"支付网关超时" | 匹配到正文含"超时"和"支付网关"的 chunk | breadcrumb 直接含"支付网关",匹配更准 | 中等 |
| 搜索"错误码定义" | 可能匹配不到具体错误内容的 chunk | breadcrumb 含"错误码定义",相关性更高 | 大 |
---
### 7.4 实施优先级建议
| 优先级 | 改进项 | 复杂度 | 收益 | 状态 |
|:------:|--------|:------:|:----:|:----:|
| P0 | **Breadcrumb 上下文**(方向 A) | 低 | 中 | **✅ 已实现 (2026-06-26)** |
| P1 | 下个版本 | 低 | 中-大 | 待定 |
| P1 | L0 排序(匹配评分 + 排序) | 低 | 中 | 待定 |
| P2 | 混合检索 + RRF 重排 | 高 | 大 | 数据量 > 50 文档时引入 |
### 7.5 Breadcrumb 实现说明
已于 2026-06-26 实现。改动范围:
| 文件 | 改动 |
|------|------|
| `DocumentChunk.java` | 新增 `breadcrumb` 字段 |
| `DocumentChunkService.java` | `splitByHeadings()` 维护标题层级栈,`Section` 新增 `level`/`breadcrumb`,`chunkSection()` 和 `saveChunkAndGetNextStart()` 透传 Breadcrumb |
| `VectorIndexService.java` | `buildMetadata()` 和 `buildDocumentMetadata()` 将 breadcrumb 写入 Milvus metadata |
#### 层级栈算法
```java
// 在 splitByHeadings() 中,每次匹配到标题时:
while (!headingStack.isEmpty() && headingStack.size() >= level) {
headingStack.remove(headingStack.size() - 1); // 弹出同级或更高级
}
headingStack.add(title); // 追加当前标题
currentBreadcrumb = String.join(" > ", headingStack);
```
示例:处理 `fault-diagnosis-process.md` 的完整面包屑路径──
```json
// 分块 "应急响应流程 > 1. 初步评估"
{ "breadcrumb": "故障诊断流程规范 > 应急响应流程 > 1. 初步评估" }
// 分块 "根因分析方法 > 5-Why 分析法"
{ "breadcrumb": "故障诊断流程规范 > 根因分析方法 > 5-Why 分析法" }
```
#### 当前 metadata 结构(Milvus)
```json
{
"_source": "knowledge_base/api/payment-errors.md",
"_file_name": "payment-errors.md",
"category": "api",
"chunkIndex": 2,
"totalChunks": 5,
"title": "超时类错误",
"breadcrumb": "支付网关错误码定义 > 超时类错误 > ERR_TIMEOUT"
}
```
以 `fault-diagnosis-process.md` 为例:
```markdown
# 故障诊断流程规范 ← heading_h1
## 应急响应流程 ← heading_h2
### 1. 初步评估 ← heading_h3(分块1)
内容...
### 2. 快速止血 ← heading_h3(分块2)
内容...
## 根因分析方法 ← heading_h2
### 5-Why 分析法 ← heading_h3(分块3)
内容...
```
改造后每个分块的 metadata:
```
分块1: breadcrumb = "故障诊断流程规范 > 应急响应流程 > 1. 初步评估"
分块2: breadcrumb = "故障诊断流程规范 > 应急响应流程 > 2. 快速止血"
分块3: breadcrumb = "故障诊断流程规范 > 根因分析方法 > 5-Why 分析法"
```
LLM 视角受益:当检索到 "2. 快速止血" 时,LLM 立刻知道它属于"故障诊断流程规范 > 应急响应流程"体系,不需要额外读取其他分块来推断上下文。
| 文件 | 说明 |
|------|------|
| `LookupKnowledgeTool.java` | 检索工具入口 |
| `KnowledgeIndexService.java` | L0 内存索引管理 |
| `VectorSearchService.java` | L1 向量检索(Milvus) |
| `KnowledgeEntry.java` | 索引条目 DTO(含 title, summary, keywords) |
| `LookupResult.java` | 查询结果 DTO |
| `PrimaryResult.java` | L0 结果 DTO |
| `SupplementResult.java` | L1 结果 DTO |
@@ -0,0 +1,477 @@
# 知识库检索使用指南
## 快速开始
### 1. 文档格式要求
所有知识库文档必须包含 YAML frontmatter:
```markdown
---
title: 文档标题(必填)
keywords: [关键词1, 关键词2, 关键词3](必填)
summary: 文档摘要(必填)
category: api(可选)
version: 1.0(可选)
author: zhangsan(可选)
---
# 正文内容
这里是文档的正文...
```
### 2. 上传文档
**API 端点**:
```
POST /api/documents/upload
Content-Type: multipart/form-data
参数:
- file: Markdown 文件
- category: 分类(如 api, infrastructure, domain, troubleshooting)
```
**示例**:
```bash
curl -X POST http://localhost:9900/api/documents/upload \
-F "file=@payment-errors.md" \
-F "category=api"
```
**返回**:
```json
{
"docId": "abc123-def456-...",
"status": "success"
}
```
### 3. Agent 调用
在 Agent 对话中,工具会自动可用:
```
用户:ERR_TIMEOUT 是什么错误?
Agent 内部:
1. 调用 lookup_knowledge("ERR_TIMEOUT")
2. L0 精确匹配找到 payment-errors.md
3. 返回完整错误码定义(高置信度)
Agent 回复:
ERR_TIMEOUT 是支付网关超时错误。
原因:...
排查方向:...
```
---
## 编写知识库文档
### Frontmatter 字段说明
#### 必填字段
**title**(标题)
```yaml
title: 支付网关错误码定义
```
- 简洁明了,能准确描述文档内容
- 建议 10-30 字
**keywords**(关键词列表)
```yaml
keywords: [ERR_TIMEOUT, 超时, 支付网关, 错误码]
```
- 用于 L0 精确匹配
- 包含所有可能的查询词
- 建议 3-10 个关键词
- 既要精确(ERR_TIMEOUT),也要通用(超时)
**summary**(摘要)
```yaml
summary: 记录了支付网关所有核心错误码的含义、原因分析及排查方向
```
- 一句话描述文档用途
- 建议 30-100 字
#### 可选字段
**category**(分类)
```yaml
category: api
```
- 推荐值:api, infrastructure, domain, troubleshooting
- 用于目录组织
**version**(版本)
```yaml
version: 1.0
```
- 文档版本号
- 便于追踪更新
**author**(作者)
```yaml
author: zhangsan
```
- 文档维护者
### 关键词设计技巧
#### ✅ 好的关键词设计
```yaml
keywords: [ERR_TIMEOUT, 超时, 支付网关, timeout, 网关超时, 支付超时]
```
**特点**:
- 包含精确术语(ERR_TIMEOUT)
- 包含通用描述(超时)
- 包含组合词(网关超时、支付超时)
- 包含英文(timeout)
#### ❌ 不好的关键词设计
```yaml
keywords: [错误, 问题]
```
**问题**:
- 太宽泛,导致多个文档匹配
- Agent 获得低置信度结果
### 文档内容建议
#### 结构化内容
```markdown
# 支付网关错误码定义
## ERR_TIMEOUT
**错误说明**:支付网关调用超时
**可能原因**:
1. 网络延迟
2. 支付网关响应慢
3. 本地超时配置过短
**排查步骤**:
1. 检查网络连通性
2. 查看支付网关监控
3. 检查超时配置
**解决方案**:
- 增加超时时间
- 优化网络链路
- 联系支付网关排查
```
#### 包含实际示例
```markdown
## 配置示例
```yaml
payment:
gateway:
timeout: 5000ms # 推荐 5 秒
retry: 3
```
## 日志示例
```
2026-06-24 10:00:00 ERROR PaymentService - ERR_TIMEOUT: 支付请求超时
orderId: 12345, timeout: 3000ms
```
```
---
## 使用场景
### 场景 1: 错误码查询
**用户输入**:
```
ERR_TIMEOUT 是什么意思?
```
**Agent 流程**:
1. 调用 `lookup_knowledge("ERR_TIMEOUT")`
2. L0 精确匹配 → 唯一匹配 → 高置信度
3. 返回完整文档内容(前 2000 字符)
4. Agent 基于文档内容回答
**响应时间**:< 10ms
### 场景 2: 配置项查询
**用户输入**:
```
Redis 连接池怎么配置?
```
**Agent 流程**:
1. 调用 `lookup_knowledge("Redis")`
2. L0 精确匹配 → 可能多个匹配 → 低置信度
3. 同时调用 L1 语义检索补充
4. 返回 primary (L0) + supplement (L1)
5. Agent 综合两份结果回答
**响应时间**:< 500ms
### 场景 3: 流程查询
**用户输入**:
```
如何排查生产故障?
```
**Agent 流程**:
1. 调用 `lookup_knowledge("故障排查")`
2. L0 精确匹配 → 找到故障诊断文档
3. 返回标准诊断流程
4. Agent 按照流程指导用户
### 场景 4: 最佳实践查询
**用户输入**:
```
Spring AI 工具怎么写?
```
**Agent 流程**:
1. 调用 `lookup_knowledge("Spring AI")`
2. L0 + L1 混合检索
3. 返回最佳实践文档
4. Agent 提供具体建议和代码示例
---
## 维护知识库
### 文档更新流程
1. **修改本地文件**
```bash
vim knowledge_base/api/payment-errors.md
```
2. **重新上传**
```bash
curl -X POST http://localhost:9900/api/documents/upload \
-F "file=@payment-errors.md" \
-F "category=api"
```
3. **验证更新**
- 重启应用(L0 索引重建)
- 或等待下次部署
### 文档删除
```bash
DELETE /api/documents/{docId}
```
**注意**:
- 同时删除 MySQL 记录
- 删除 Milvus 向量索引
- 删除本地文件
- 从 L0 索引移除
### 查看已索引文档
启动日志中查看:
```
[INFO] 开始扫描知识库目录: knowledge_base/
[DEBUG] 文档已加入索引: title=支付网关错误码定义
[DEBUG] 文档已加入索引: title=Redis 缓存配置指南
[INFO] 知识库索引加载完成,共 6 个文档
```
---
## 故障排查
### 问题 1: 文档未被索引
**症状**:
- 上传成功,但 Agent 查询不到
**排查**:
1. 检查 frontmatter 格式是否正确
2. 查看启动日志是否有 WARN
3. 确认文件保存位置
**解决**:
```bash
# 检查文件是否存在
ls knowledge_base/api/payment-errors.md
# 检查 frontmatter 格式
head -20 knowledge_base/api/payment-errors.md
# 重启应用重建索引
```
### 问题 2: 总是调用 L1(低置信度)
**症状**:
- 查询耗时 > 200ms
- 日志显示调用 L1
**原因**:
- L0 未匹配(关键词不在 keywords 中)
- L0 多个匹配(关键词重复)
**解决**:
```yaml
# 检查关键词是否覆盖查询词
keywords: [ERR_TIMEOUT, 超时, timeout]
# 避免关键词过于宽泛
❌ keywords: [错误, 问题] # 太宽泛
✅ keywords: [ERR_TIMEOUT, 超时] # 精准
```
### 问题 3: 查询返回不完整
**症状**:
- 文档内容被截断
**原因**:
- 文档过长,L0 只返回前 2000 字符
**解决**:
1. 将长文档拆分成多个短文档
2. 每个文档聚焦一个主题
3. 或等待 Phase 2 章节锚点功能
### 问题 4: 启动扫描很慢
**症状**:
- 应用启动时间过长
**原因**:
- knowledge_base/ 文件过多
**解决**:
```bash
# 检查文档数量
find knowledge_base -name "*.md" | wc -l
# 清理无用文档
rm knowledge_base/.backup/*.md
```
**参考指标**:
- 500 个文档:< 1s
- 1000 个文档:可能需要优化
---
## 性能优化
### 优化关键词匹配率
**目标**:提高高置信度命中率(减少 L1 调用)
**方法**:
1. 分析查询日志,找到常见查询词
2. 将常见查询词加入 keywords
3. 定期审查和优化 keywords
**示例**:
```bash
# 查看低置信度查询
grep "confidence=low" logs/application.log | \
awk -F'query=' '{print $2}' | \
awk -F',' '{print $1}' | \
sort | uniq -c | sort -rn
```
### 减少文档数量
**策略**:
- 删除过时文档
- 合并相似文档
- 归档不常用文档
### 监控关键指标
**配置监控**:
- L0 查询耗时(目标 < 10ms)
- L1 调用频率(目标 < 30%)
- 高置信度命中率(目标 > 70%)
---
## 最佳实践总结
### ✅ 推荐做法
1. **关键词全面**
- 包含精确术语和通用描述
- 包含英文和中文
- 包含常见拼写变体
2. **文档聚焦**
- 一个文档一个主题
- 避免大而全的文档
3. **结构化内容**
- 使用清晰的标题层次
- 包含实际示例
- 提供具体步骤
4. **定期维护**
- 定期审查和更新
- 删除过时内容
- 优化关键词
### ❌ 避免做法
1. **关键词模糊**
```yaml
❌ keywords: [错误, 问题]
✅ keywords: [ERR_TIMEOUT, 超时]
```
2. **文档过长**
```markdown
❌ 一个文档包含 50 个错误码定义(会被截断)
✅ 每个错误码一个文档,或按类型分组
```
3. **缺少实际示例**
```markdown
❌ Redis 配置很重要,需要优化
✅
```yaml
spring:
redis:
lettuce:
pool:
max-active: 8
```
```
4. **长期不更新**
- 定期审查(建议每季度)
- 删除过时内容
- 添加新的常见问题
---
## 参考资料
- **架构文档**:`mvp/architecture/knowledge-retrieval-architecture.md`
- **可观测性**:`.docs/knowledge-observability.md`
- **Handoff 文档**:`handoff/2026-06-24-lookup-knowledge-integration.md`
- **OpenSpec**:`openspec/changes/lookup-knowledge-integration/`
@@ -0,0 +1,188 @@
# 会话级去重与知识域地图
文档级去重 + 知识域地图注入 Planner,解决 ISS-001 Executor 重复召回同一文档问题。
---
## 一、整体架构
本 change 包含两个独立但互补的部分:
```
Part A: 工具层去重
LookupKnowledgeTool
├── 维护 ConcurrentHashMap<sessionId, Set<filePath>>(JVM 内)
├── 每次检索前过滤已召回文档
└── SessionContextHolder.clear() 时同步清理
Part B: 知识域地图
┌─────────────────────────────────────────────┐
│ 文档上传 (DocumentManagementService) │
│ → LLM 生成 doc.covers + doc.when_to_retrieve │
│ → 存入 api_document.metadata │
│ → 触发域级重算 (KnowledgeDomainService) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 域级聚合 (KnowledgeDomainService) │
│ → 读取同域所有文档的 when_to_retrieve │
│ → LLM 生成 domain.when_to_retrieve │
│ → 存入 knowledge_domain 表 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 启动 (KnowledgeIndexService.loadIndex) │
│ → 加载 knowledge_domain 表 │
│ → 某域无记录则触发域级生成 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Planner prompt (ChatService) │
│ → 注入 knowledge map(域级) │
│ → Planner 做粗粒度检索决策 │
└─────────────────────────────────────────────┘
```
---
## 二、Part A:工具层去重
### RetrievedDocTracker
session 级已召回文档追踪组件,将去重责任从 LLM 移交到工具层。
```java
ConcurrentHashMap<String, Set<String>> retrieved
key: sessionId
value: Set<filePath>
```
| 方法 | 作用 |
|------|------|
| `isAlreadyRetrieved(sessionId, filePath)` | 检查文档是否已召回 |
| `markRetrieved(sessionId, filePath)` | 记录已召回文档 |
| `clearSession(sessionId)` | 清理会话记录(SessionContextHolder.clear 触发) |
### 去重流程
```
lookup_knowledge(query)
→ L0 检索 → 命中一批文档
→ 遍历结果,过滤 isAlreadyRetrieved=true 的文档
→ 剩余文档作为 primary/supplement 返回
→ 实际返回的文档调用 markRetrieved
```
---
## 三、Part B:知识域地图
### Frontmatter 新增字段
文档上传时 LLM 自动生成以下两个字段:
```yaml
covers: ["支付失败排查", "扣款无回调"] # 业务场景标签
when_to_retrieve: "用户描述支付失败、超时时" # 文档级检索时机
```
### knowledge_domain 表
```sql
CREATE TABLE knowledge_domain (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
domain_id VARCHAR(64) NOT NULL UNIQUE,
description VARCHAR(256),
when_to_retrieve TEXT,
document_count INT DEFAULT 0,
updated_at DATETIME,
created_at DATETIME
);
```
### Knowledge Map(注入 Planner 的 YAML)
```yaml
available_knowledge_domains:
- domain_id: "payment"
description: "支付链路问题排查"
when_to_retrieve: "用户问题涉及支付、退款、对账时检索;优先检索一次,勿重复"
documents:
- title: "支付失败排查手册"
covers: ["支付超时", "扣款无回调"]
- title: "退款处理指南"
covers: ["退款未到账", "退款状态异常"]
- domain_id: "infrastructure"
...
```
### 注入链路
```
文档上传/删除
→ KnowledgeDomainService.onDocumentChange(category)
→ 读取同域所有文档的 when_to_retrieve
→ LLM 聚合为 domain.when_to_retrieve
→ 写入 knowledge_domain 表
应用启动
→ KnowledgeIndexService.loadIndex()
→ 加载 knowledge_domain → 无记录则触发聚合
→ ChatService.buildChatPlannerAgent() 注入 prompt
Planner prompt 中包含知识域地图
→ Planner 做粗粒度检索决策("查 payment 域")
→ Executor 收到步骤后执行具体检索
```
---
## 四、关键设计决策
| 决策 | 方案 | 原因 |
|------|------|------|
| 域级 when_to_retrieve 存 DB | 持久化 | 避免每次重启调 LLM,文档变更时只重算受影响域 |
| 文档级 when_to_retrieve 存 metadata JSON | 沿用现有路径 | 无需新增数据库字段 |
| RetrievedDocTracker 独立于 SessionContextHolder | 职责分离 | SessionContextHolder 只持有 sessionId,Tracker 是业务状态 |
| Planner 只看域级 | 分层决策 | 文档级 when_to_retrieve 留 Executor 筛选(Phase 2) |
| LLM 调用同步执行 | 上传时即时生成 | 接受约 1-2s 延迟,保证数据库和 L0 索引立即一致 |
---
## 五、Agent 边界
```
Planner 角色:知道"有什么域"
└─ 知识域地图:选定要检索的域(一次规划)
Executor 角色:知道"做了什么"
└─ 行动记忆:域级 + 文档级去重(ISS-002 升级为双层记忆)
```
Part B(知识域地图)只注入 Planner prompt,**不注入 Executor prompt**。Executor 只通过 RetrievedDocTracker 知道自己已检索了哪些文档,不需要知道全局域有哪些。
---
## 六、数据库变更
### V009
```sql
CREATE TABLE knowledge_domain (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
domain_id VARCHAR(64) NOT NULL UNIQUE,
description VARCHAR(256),
when_to_retrieve TEXT,
document_count INT DEFAULT 0,
updated_at DATETIME,
created_at DATETIME
);
```
---
## 七、参考资料
- **架构文档**:`mvp/architecture/knowledge-retrieval-architecture.md`
- **使用指南**:`mvp/architecture/knowledge-retrieval-usage.md`
- **OpenSpec**:`openspec/changes/archive/2026-06-30-session-dedup-knowledge-map/`
@@ -0,0 +1,210 @@
# 会话管理设计
## 会话存储策略
### Redis(主)
**数据结构**:
```
key: session:{session_id}
value: {
"sessionId": "sess-abc",
"userId": "user-123",
"currentDiagnosisId": "diag-001",
"messages": [
{"role": "user", "content": "诊断订单 A"},
{"role": "assistant", "content": "完整报告..."}
],
"context": {
"province": "广东",
"apiName": "社保查询",
"errorCode": "40003"
},
"createdAt": "2024-06-15T14:30:00Z",
"lastActiveAt": "2024-06-15T14:35:00Z"
}
ttl: 1800秒(30分钟)
```
**优势**:
- ✅ 快速读写
- ✅ 自动过期
- ✅ 支持追问(保存上下文)
---
### MySQL(辅助,可选)
**同步策略**:
1. 重要会话同步
- 有用户反馈的会话
- 诊断失败的会话(BadCase)
- 多轮对话 > 3 轮的会话
2. 同步时机
- 会话结束时(30分钟过期)
- 用户反馈时(实时)
- 定时任务(每小时,可选)
3. 同步目标
- conversation_history 表
- 用于长期分析和审计
---
## 数据流设计
### 场景1:单次诊断(主流 80%)
```
1. 用户发起诊断
POST /api/diagnosis/start
{
"orderId": "202406150001"
}
2. 创建会话(Redis)
key: session:sess-abc
ttl: 1800秒
3. 创建诊断记录(MySQL)
INSERT INTO diagnosis_record
- diagnosis_id: diag-001
- session_id: sess-abc
- status: RUNNING
4. Agent 执行诊断
- 调用工具(queryOrder, queryLogs, searchDoc...)
- 生成报告
5. 更新诊断记录(MySQL)
UPDATE diagnosis_record
- status: SUCCESS
- root_cause: "idCard字段缺失"
- report_markdown: "完整报告..."
6. 返回报告
→ 大部分用户到此结束
```
---
### 场景2:追问(少数 20%)
```
1. 用户追问
POST /api/chat
{
"sessionId": "sess-abc",
"message": "为什么会缺失字段?"
}
2. 从 Redis 获取上下文
GET session:sess-abc
- 有之前的诊断结果
- 有对话历史
3. Agent 基于上下文回答
- 不创建新的 diagnosis_record
- 只是普通对话
4. 更新 Redis 会话
- 追加对话历史
- 刷新 TTL(重新计时30分钟)
5. 可选:保存到 conversation_history(MySQL)
- 如果需要长期分析
- 异步存储
```
---
### 场景3:同一会话多次诊断
```
1. 用户第一次诊断
"诊断订单 A"
→ diagnosis_record(diag-001, session_id=sess-abc)
2. 用户第二次诊断
"再诊断订单 B"
→ diagnosis_record(diag-002, session_id=sess-abc)
3. 会话关联
- 同一个 session_id
- 两条 diagnosis_record
- Redis 中保存完整对话历史
```
---
## 会话生命周期
```
创建
↓
活跃(每次交互刷新TTL)
↓
30分钟无活动
↓
自动过期
↓
可选:同步到 MySQL(重要会话)
```
---
## 实现示例
### Java 代码
```java
@Service
public class SessionService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String SESSION_PREFIX = "session:";
private static final Duration SESSION_TTL = Duration.ofMinutes(30);
// 创建会话
public String createSession(String userId) {
String sessionId = UUID.randomUUID().toString();
SessionData session = SessionData.builder()
.sessionId(sessionId)
.userId(userId)
.messages(new ArrayList<>())
.context(new HashMap<>())
.createdAt(LocalDateTime.now())
.lastActiveAt(LocalDateTime.now())
.build();
String key = SESSION_PREFIX + sessionId;
redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL);
return sessionId;
}
// 获取会话
public SessionData getSession(String sessionId) {
String key = SESSION_PREFIX + sessionId;
String json = redisTemplate.opsForValue().get(key);
return json != null ? fromJson(json) : null;
}
// 更新会话(刷新TTL)
public void updateSession(SessionData session) {
session.setLastActiveAt(LocalDateTime.now());
String key = SESSION_PREFIX + session.getSessionId();
redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL);
}
// 删除会话
public void deleteSession(String sessionId) {
String key = SESSION_PREFIX + sessionId;
redisTemplate.delete(key);
}
}
```