Merge branch 'emdash/afraid-geese-carry-h5718' into refactor/mvp1.0

# Conflicts:
#	devflow/index.md
This commit is contained in:
zhuyongxin
2026-06-26 17:36:59 +08:00
65 changed files with 4901 additions and 1034 deletions
@@ -0,0 +1,88 @@
# 会话存储 — 决策记录
## Question Pool
### 术语维度
| # | 问题 | 类型 | 状态 |
|---|------|------|:----:|
| Q1 | AgentLoggingHook 如何获得 Repository 访问能力? | evidence-driven | ✅ 已查证 |
| Q2 | AiOpsService 当前是否使用了 AgentLoggingHook? | evidence-driven | ✅ 已查证 |
### 边界维度
| # | 问题 | 类型 | 状态 |
|---|------|------|:----:|
| Q3 | Hook 中写 DB 是否同步?要不要一步到位做异步? | user-interview | ✅ 已确认 |
| Q4 | AiOpsService 的 Supervisor 步骤是否单独记录? | user-interview | ✅ 已确认 |
### 验收维度
| # | 问题 | 类型 | 状态 |
|---|------|------|:----:|
| Q5 | tool_invocation 的 output_preview 截断多长合适? | 默认 | 500 字符 |
### 技术实现维度
| # | 问题 | 类型 | 状态 |
|---|------|------|:----:|
| Q6 | LookupKnowledgeTool 如何获取当前 sessionId 和 stepId? | **待解决** | ⚠️ 未确认 |
## Evidence-Driven 查证
### E1: AgentLoggingHook 创建方式
**证据**:ChatService 第 180 行 `.hooks(new AgentLoggingHook())` — 直接 new 创建,非 Spring 管理。
**结论**:Hook 不是 Spring Bean,无法注入 Repository。AiOpsService 的 Planner/Executor 也没有加 Hook。
**影响**:需要改造为 @Component + 构造注入,并在 AiOpsService 中补齐。
### E2: 项目异步基础设施
**证据**:全局搜索 `@Async`、`@EnableAsync`、`CompletableFuture`、`TaskExecutor` — 均无匹配。
**结论**:项目没有异步执行基础设施。
**影响**:MVP 阶段 Hook 内同步写 DB,后续再优化。
## User-Interview 确认
### U1: Hook 改造方式
**问题**:AgentLoggingHook 怎样获得 Repository 访问能力?
**选项**:
1. 改造为 Spring Bean(@Component + 构造注入)
2. 保持 POJO,从外部传 Repository
**用户答复**:改为 Hook(Spring Bean)
**确认状态**:✅ 已确认
### U2: AiOpsService 记录粒度
**问题**:Supervisor 内部的步骤记录范围?
**选项**:
1. 只记子 Agent(Planner/Executor)步骤
2. 全量记录(含 Supervisor)
**用户答复**:接受建议,只记子 Agent
**确认状态**:✅ 已确认
## 开放问题
### O1: LookupKnowledgeTool 获取 sessionId
LookupKnowledgeTool 是 `@Component`,通过 Spring AI 的 `@Tool` 注解暴露给 Agent。它不直接参与 Agent Hook 调用链,**无法直接从 RunnableConfig 读取 sessionId**。
可能的方案:
1. **ThreadLocal** — ChatService/AiOpsService 在执行前设置当前 sessionId 到 ThreadLocal,工具中读取。简单,但需注意清理。
2. **从 agent_step 反查** — 工具调用后根据时间戳和 session 关联查找最近的 step。不准确。
3. **RequestContextHolder** — 利用 Spring 的请求上下文。仅限 Web 请求上下文有效。
**建议方案**:ThreadLocal。在 ChatService/AiOpsService 执行入口设置,AgentLoggingHook 和 LookupKnowledgeTool 都从 ThreadLocal 读取。
**用户确认**:✅ 同意 ThreadLocal 方案
@@ -0,0 +1,93 @@
# 会话存储体系 — 设计文档
## 架构概览
```
用户请求
│
▼
ChatService.executeChat() / AiOpsService.executeAiOpsAnalysis()
│ ┌── 创建 diagnosis_session (status=RUNNING)
│
▼
Agent Loop(带 AgentLoggingHook)
│
├── beforeModel() → 创建 agent_step(记录 model_input 摘要)
├── afterModel() → 更新 agent_step(记录 model_output、token_count、工具调用决策)
│
├── 工具执行(如 lookup_knowledge)
│ └── 写入 tool_invocation(L0/L1 明细、耗时、是否截断)
│
└── 循环直到模型不再调用工具
│
▼
更新 diagnosis_session (status=SUCCESS/FAILED,汇总指标)
```
## 表结构
### diagnosis_session
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT PK AUTO_INC | 自增主键 |
| session_id | VARCHAR(64) UNIQUE | 会话唯一 ID |
| query | TEXT | 用户原始问题 |
| status | VARCHAR(16) DEFAULT 'PENDING' | PENDING/RUNNING/SUCCESS/FAILED |
| agent_flow | VARCHAR(32) | CHAT / AI_OPS |
| total_duration_ms | INT | 总耗时 |
| total_token_count | INT | 总 Token 消耗 |
| step_count | INT | Agent 步数 |
| tool_call_count | INT | 工具调用次数 |
| self_evaluation | JSON | 自评估信号 |
| feedback | VARCHAR(16) | 用户反馈 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
### agent_step
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT PK AUTO_INC | 自增主键 |
| session_id | VARCHAR(64) | 关联 diagnosis_session |
| step_index | INT | 当前 Agent 的第几步 |
| agent_name | VARCHAR(32) | intelligent_assistant / planner / executor |
| model_input | JSON | 模型输入摘要 [{role, content_truncated}] |
| model_output | JSON | 模型输出摘要 {text, tool_calls} |
| thought | TEXT | Agent 思考过程文本 |
| has_tool_call | BOOLEAN | 本轮是否调用了工具 |
| duration_ms | INT | 本轮耗时 |
| token_count | INT | 本轮 Token 消耗 |
| created_at | DATETIME | 创建时间 |
### tool_invocation
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT PK AUTO_INC | 自增主键 |
| session_id | VARCHAR(64) | 关联 diagnosis_session |
| step_id | BIGINT | 关联 agent_step.id(可为空) |
| tool_name | VARCHAR(64) | lookup_knowledge / 等 |
| input_params | JSON | 工具入参 |
| output_preview | TEXT | 输出前 500 字符 |
| output_length | INT | 输出总字符数 |
| retrieval_layer | VARCHAR(8) | L0 / L1 / L0+L1 |
| l0_match_count | INT | L0 匹配数 |
| l1_match_count | INT | L1 匹配数 |
| is_truncated | BOOLEAN | 内容是否被截断 |
| retrieval_details | JSON | L0 标题列表、L1 分数等 |
| duration_ms | INT | 工具执行耗时 |
| success | BOOLEAN | 是否成功 |
| error_message | TEXT | 失败原因 |
| created_at | DATETIME | 创建时间 |
## 关键设计决策
| 决策 | 选择 | 理由 |
|------|------|------|
| Hook 创建方式 | Spring Bean (@Component) | 需要注入 Repository |
| DB 写入时机 | 同步(Hook 内部直接写入) | MVP 阶段简化,后续可异步化 |
| session_id 向 Hook 传递 | 通过 RunnableConfig 的 metadata 携带 | Spring AI Alibaba Agent Framework 原生支持 |
| session_id 向 Tool 传递 | ThreadLocal(SessionContextHolder 工具类) | Tool 不在 Hook 调用链中,无法获取 RunnableConfig |
| tool_invocation 关联 agent_step | 通过 step_id 外键(不加约束) | 允许 tool_invocation 独立于 agent_step 写入 |
| AiOps 多 Agent 记录 | 每个子 Agent 独立 Hook 实例 | 各自维护 step_index 计数器 |
@@ -0,0 +1,70 @@
# 会话存储体系
## 问题
当前 `diagnosis_record` 单表无法支撑通用会话存储需求:
1. 字段语义耦合在"告警分析"领域(fault_category、error_code 等),ChatService 通用问答场景无法使用
2. 缺少 Agent 决策链维度(两个 Agent 的多轮思考过程无法区分和追溯)
3. 检索质量不可评估(L0/L1 命中层、截断信息、召回内容长度无记录)
4. 指标不完整(缺 token 用量、自评信号、采纳率)
## 建议方案
将单表拆分为三表体系,用 `session_id` 关联:
```
diagnosis_session (1)
└── agent_step (0:N) —— 每次 Agent 决策
└── tool_invocation (0:N) —— 每步中的工具调用
```
### 三表职责
| 表 | 职责 | 示例查询 |
|---|---|---|
| diagnosis_session | 诊断级元数据 + 汇总指标 | "某次诊断的总耗时和 Token 消耗" |
| agent_step | 决策链:每步 Agent 的输入输出摘要 | "Planner 的思考过程和工具调用决策" |
| tool_invocation | 工具调用明细 + 检索质量 | "lookup_knowledge 的 L0/L1 命中分布" |
### 集成点
1. `AgentLoggingHook` → 写入 `agent_step`
2. `LookupKnowledgeTool` → 写入 `tool_invocation`
3. `ChatService` / `AiOpsService` → 创建/更新 `diagnosis_session`
## 范围
- 新建 3 张表(Flyway 迁移)
- 新建 3 个 JPA Entity + 3 个 Repository
- 改造 AgentLoggingHook、LookupKnowledgeTool、ChatService、AiOpsService
- 现有 `diagnosis_record` 表保持不动
## 非目标
- 不涉及 UI 层面的会话展示
- 不涉及历史数据迁移
- 不涉及 diagnosis_record 的合并或废弃
## 上下文约束
- Flyway 迁移脚本命名:V005__create_diagnosis_session.sql 起
- JPA ddl-auto 使用 validate 模式
- JSON 列使用 `@JdbcTypeCode(SqlTypes.JSON)`(同现有 diagnosis_record 的 tool_calls 字段)
- 已有 SessionManager/Redis 会话机制不变,新表作为持久化补充
## 已确认的设计决策
| 决策 | 结论 | 来源 |
|------|------|------|
| AgentLoggingHook 创建方式 | 改造为 Spring Bean(@Component + 构造注入) | grill user-interview |
| AiOpsService 钩子范围 | Planner 和 Executor 各加 AgentLoggingHook | grill user-interview |
| Supervisor 步骤记录 | 不单独记录,由子 Agent 步骤覆盖 | grill user-interview |
| tool_invocation 截断长度 | 500 字符 | proposal 默认 |
| sessionId 传递机制 | ThreadLocal(SessionContextHolder) | grill user-interview |
| AiOps 步骤记录 | 只记 Planner/Executor,不记 Supervisor | grill user-interview |
## 风险
- AgentLoggingHook 目前是同步写日志,新增 DB 写可能影响 Agent 响应时间 → 考虑异步写入或先同步后优化
- tool_invocation 的 output_preview 截断长度需合理(建议 500 字符)
@@ -0,0 +1,153 @@
# 会话存储 — 功能规格
## Requirement 1:三张新表的 DDL
**路径**:`src/main/resources/db/migration/V005__create_session_storage.sql`
**内容**:
- 创建 `diagnosis_session` 表(DDL 见 design.md)
- 创建 `agent_step` 表(DDL 见 design.md)
- 创建 `tool_invocation` 表(DDL 见 design.md)
- 三条 DDL 写在同一个迁移文件中
**验收标准**:
- [ ] Flyway migrate 后三张表均存在
- [ ] 表结构字段类型、索引与设计一致
- [ ] JSON 列使用 `JSON` 类型(MySQL 8+)
---
## Requirement 2:JPA Entity + Repository
### 2.1 实体类
**路径**:
- `src/main/java/com/superbiz/agent/domain/entity/DiagnosisSession.java`
- `src/main/java/com/superbiz/agent/domain/entity/AgentStep.java`
- `src/main/java/com/superbiz/agent/domain/entity/ToolInvocation.java`
**要求**:
- 使用 `@Entity` + `@Table(name = "...")` 映射
- JSON 字段使用 `@JdbcTypeCode(SqlTypes.JSON)`(同现有 `DiagnosisRecord.toolCalls`)
- `@PrePersist` 自动填充 `createdAt`
- 使用 Lombok `@Data @Builder @NoArgsConstructor @AllArgsConstructor`
### 2.2 Repository 接口
**路径**:
- `src/main/java/com/superbiz/agent/repository/DiagnosisSessionRepository.java`
- `src/main/java/com/superbiz/agent/repository/AgentStepRepository.java`
- `src/main/java/com/superbiz/agent/repository/ToolInvocationRepository.java`
**要求**:
- 继承 `JpaRepository`
- `DiagnosisSessionRepository`:`findBySessionId(String sessionId)`
- `AgentStepRepository`:`findBySessionIdOrderByStepIndex(String sessionId)`、`countBySessionId(String sessionId)`
- `ToolInvocationRepository`:`findBySessionId(String sessionId)`、`findByToolName(String toolName)`
**验收标准**:
- [ ] 3 个 Entity 编译通过
- [ ] 3 个 Repository 编译通过
- [ ] 自定义查询方法命名符合 Spring Data JPA 规范
---
## Requirement 3:AgentLoggingHook 改造为 Spring Bean
**路径**:`src/main/java/com/superbiz/agent/hook/AgentLoggingHook.java`
**变更**:
- 类上加 `@Component` 注解
- 不再通过 new 创建实例
- 构造注入 `AgentStepRepository`
- beforeModel:创建 `AgentStep` 记录,设置 `modelInput`,记录开始时间到 `RunnableConfig`
- afterModel:更新对应 `AgentStep`,设置 `modelOutput`、`thought`、`hasToolCall`、`durationMs`、`tokenCount`
- `modelInput` 和 `modelOutput` 只存摘要(前 500 字符),不存完整消息体
**session_id 传递机制**:
- 调用方(ChatService/AiOpsService)通过 `RunnableConfig.metadata()` 传入 `sessionId`
- Hook 从 `config.getMetadata("sessionId")` 读取
**验收标准**:
- [ ] Hook 可注入 AgentStepRepository
- [ ] beforeModel 创建 agent_step 记录并写入 DB
- [ ] afterModel 更新对应 agent_step 记录
- [ ] model_input/output 摘要不超过 500 字符
- [ ] 从 RunnableConfig 正确读取 sessionId
- [ ] 原日志输出行为保持不变
---
## Requirement 4:ChatService 集成
**路径**:`src/main/java/com/superbiz/agent/service/ChatService.java`
**变更**:
- 注入 `DiagnosisSessionRepository`
- `executeChat()` 中:
- 执行前:创建 `DiagnosisSession`(status=RUNNING),生成 `sessionId`,生成 `agent_flow=CHAT`
- 通过 `RunnableConfig` 将 sessionId 传给 Hook
- 执行后:更新 `DiagnosisSession`(status=SUCCESS/FAILED,汇总 step_count、tool_call_count、total_duration_ms)
- 不再通过 `new AgentLoggingHook()` 创建 Hook,改为注入 Bean 的 Hook
**验收标准**:
- [ ] executeChat 执行前后分别创建和更新 diagnosis_session
- [ ] sessionId 通过 RunnableConfig 正确传递给 Hook
- [ ] 汇总指标(duration、step_count)正确写入
- [ ] 异常路径正确设置 status=FAILED
---
## Requirement 5:AiOpsService 集成
**路径**:`src/main/java/com/superbiz/agent/service/AiOpsService.java`
**变更**:
- 注入 `DiagnosisSessionRepository` 和 `AgentLoggingHook`
- `executeAiOpsAnalysis()` 中:
- 执行前:创建 `DiagnosisSession`(status=RUNNING, agent_flow=AI_OPS)
- 构建 Planner 和 Executor 时传入 `AgentLoggingHook` 实例(使用注入的 Bean)
- 通过 `RunnableConfig` 将 sessionId 传给 Hook
- 执行后:更新 `DiagnosisSession`(汇总指标)
- Supervisor 不加 Hook
**验收标准**:
- [ ] AiOpsService 执行前后分别创建和更新 diagnosis_session
- [ ] Planner 和 Executor 各带 AgentLoggingHook
- [ ] 两个 Hook 使用相同的 sessionId
- [ ] Supervisor 不产生 agent_step 记录
---
## Requirement 6:LookupKnowledgeTool 写入 tool_invocation
**路径**:`src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`
**变更**:
- 注入 `ToolInvocationRepository`
- `lookupKnowledge()` 执行后:
- 创建 `ToolInvocation` 记录
- 写入 `toolName=lookup_knowledge`、`inputParams`(query)、`outputPreview`(前 500 字符)
- 写入检索质量:`retrievalLayer`、`l0MatchCount`、`l1MatchCount`、`isTruncated`、`retrievalDetails`
- 写入 `durationMs`、`success`
- `sessionId` 和 `stepId` 如何获取需要方案设计(见开放问题)
**验收标准**:
- [ ] lookup_knowledge 每次调用后创建 tool_invocation 记录
- [ ] 检索质量字段(L0/L1 明细)正确写入
- [ ] 工具执行失败的场景正确记录
---
## Requirement 7:构造注入适配(无 @Async)
**路径**:所有涉及新增 Repository 注入的类
**要求**:
- 所有新注入使用构造注入(`@RequiredArgsConstructor` 或显式构造器)
- 不在 MV 阶段引入 @Async 异步基础设施
- Hook 中的 DB 写入是同步的,作为已知的技术债记录
**验收标准**:
- [ ] 没有使用 @Autowired 字段注入新 Repository(保持项目已有风格)
- [ ] 没有引入 @Async / @EnableAsync
@@ -0,0 +1,151 @@
# 会话存储 — 任务拆解
## 切片 1:Flyway 迁移脚本
**文件**: `src/main/resources/db/migration/V005__create_session_storage.sql`
**内容**:创建 diagnosis_session、agent_step、tool_invocation 三张表
**验收标准**:
- [x] 三张表均通过 Flyway 创建成功
- [x] 字段类型、索引、JSON 列定义正确
- [x] 回滚脚本可选(不做强制要求)
---
## 切片 2:JPA 实体类
**文件**:
- `src/main/java/com/superbiz/agent/domain/entity/DiagnosisSession.java`
- `src/main/java/com/superbiz/agent/domain/entity/AgentStep.java`
- `src/main/java/com/superbiz/agent/domain/entity/ToolInvocation.java`
**内容**:三个 Entity,使用 @JdbcTypeCode(SqlTypes.JSON) 映射 JSON 列
**验收标准**:
- [x] 编译通过,无 JPA 映射错误
- [x] Entity 字段与 DDL 对齐
- [x] Lombok 注解完整
---
## 切片 3:JPA Repository
**文件**:
- `src/main/java/com/superbiz/agent/repository/DiagnosisSessionRepository.java`
- `src/main/java/com/superbiz/agent/repository/AgentStepRepository.java`
- `src/main/java/com/superbiz/agent/repository/ToolInvocationRepository.java`
**内容**:三个 Repository,含自定义查询方法
**验收标准**:
- [x] 编译通过
- [x] 自定义方法命名正确
- [x] 可在 Spring 中自动注入
---
## 切片 4:SessionContextHolder 工具类
**文件**: `src/main/java/com/superbiz/agent/util/SessionContextHolder.java`
**内容**:基于 ThreadLocal 的 sessionId 传递工具
```java
public class SessionContextHolder {
private static final ThreadLocal<String> SESSION_ID = new ThreadLocal<>();
public static void setSessionId(String sessionId) { SESSION_ID.set(sessionId); }
public static String getSessionId() { return SESSION_ID.get(); }
public static void clear() { SESSION_ID.remove(); }
}
```
**验收标准**:
- [x] 编译通过
- [x] set/get/clear 在同一线程内正常工作
---
## 切片 5:AgentLoggingHook 改造为 Spring Bean
**文件**: `src/main/java/com/superbiz/agent/hook/AgentLoggingHook.java`
**内容**:
- 加 @Component 注解
- 构造注入 AgentStepRepository
- beforeModel 创建 agent_step
- afterModel 更新 agent_step
- 从 RunnableConfig 读取 sessionId
**验收标准**:
- [x] 编译通过
- [x] beforeModel 写入 agent_step 到 DB
- [x] afterModel 更新正确行
- [x] 原日志行为不变
---
## 切片 6:ChatService 集成
**文件**: `src/main/java/com/superbiz/agent/service/ChatService.java`
**内容**:
- 注入 DiagnosisSessionRepository
- executeChat 前后创建/更新 diagnosis_session
- 通过 RunnableConfig 传递 sessionId
**验收标准**:
- [x] 每次 executeChat 产生一条 diagnosis_session 记录
- [x] sessionId 可被 Hook 读取
- [x] status、duration 等汇总指标正确
---
## 切片 7:AiOpsService 集成
**文件**: `src/main/java/com/superbiz/agent/service/AiOpsService.java`
**内容**:
- 注入 DiagnosisSessionRepository 和 AgentLoggingHook
- executeAiOpsAnalysis 前后创建/更新 diagnosis_session
- Planner 和 Executor 各加 AgentLoggingHook
- Supervisor 不加 Hook
**验收标准**:
- [x] 每次 executeAiOpsAnalysis 产生一条 diagnosis_session 记录
- [x] Planner 执行产生 agent_step 记录
- [x] Executor 执行产生 agent_step 记录
- [x] Supervisor 不产生 agent_step 记录
---
## 切片 8:LookupKnowledgeTool 集成
**文件**: `src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`
**内容**:
- 注入 ToolInvocationRepository
- 执行后写入 tool_invocation 记录
- 记录 L0/L1 检索质量
**验收标准**:
- [x] 每次 lookup_knowledge 调用写入一条 tool_invocation
- [x] retrieval_layer / l0_match_count 等字段正确
- [x] 异常场景 success=false
---
## 切片 9:测试
**文件**:
- `src/test/java/com/superbiz/agent/repository/DiagnosisSessionRepositoryTest.java`
- `src/test/java/com/superbiz/agent/repository/AgentStepRepositoryTest.java`
- `src/test/java/com/superbiz/agent/repository/ToolInvocationRepositoryTest.java`
**内容**:
- Repository 单元测试(CRUD + 自定义查询)
- 集成测试需要运行环境(后续补充)
**验收标准**:
- [x] Repository 测试通过