feat(feedback): 置信度评分与用户反馈机制 & 归档 confidence-feedback change
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Design: 置信度评分与用户反馈机制
|
||||
|
||||
## 架构约束(来自 devflow)
|
||||
|
||||
- Spring Boot 3.2 + Spring AI Alibaba
|
||||
- JPA ddl-auto=validate,变更走 Flyway
|
||||
- 已有实体:`DiagnosisSession`(含 selfEvaluation JSON、feedback VARCHAR)、`CaseLibrary`
|
||||
- **新增字段**:`DiagnosisSession.answer TEXT`,存储返回给用户的完整答案,Flyway V008 迁移
|
||||
- 已有 Repository:`DiagnosisSessionRepository`、`CaseLibraryRepository`
|
||||
- 当前主流程入口:`ChatService.executeChat`(非流式)、`executeChatComplex`(多 Agent)
|
||||
|
||||
## 模块链路
|
||||
|
||||
```
|
||||
用户对话
|
||||
↓
|
||||
ChatService.executeChat / executeChatComplex
|
||||
↓ SUCCESS 后异步
|
||||
EvaluationService.evaluate(sessionId, answer, steps)
|
||||
├─ LLM 自评 → 写 selfEvaluation(含 confidence + reasoning)
|
||||
└─ 规则兜底(LLM 失败时)→ 写 selfEvaluation(含 source: "rule")
|
||||
|
||||
用户提交反馈
|
||||
↓
|
||||
POST /api/feedback { sessionId, feedback }
|
||||
↓
|
||||
FeedbackService.submitFeedback(sessionId, feedback)
|
||||
├─ 写 DiagnosisSession.feedback
|
||||
├─ feedback=useful → 写 CaseLibrary
|
||||
└─ feedback=not_useful → 更新 status=BAD_CASE
|
||||
```
|
||||
|
||||
## 数据结构定义
|
||||
|
||||
### DiagnosisSession.selfEvaluation(JSON 字符串)
|
||||
|
||||
```json
|
||||
{
|
||||
"evidence_score": 65,
|
||||
"source": "rule",
|
||||
"factors": [
|
||||
{"name": "has_successful_tool_call", "delta": 30, "description": "有成功的工具调用(2次)"},
|
||||
{"name": "l1_semantic_match", "delta": 20, "description": "L1 语义匹配命中"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
- `evidence_score`:0-100,衡量证据收集充分度(非答案准确性)
|
||||
- `source`:评分来源,当前固定为 `"rule"`;预留 `"llm"` 供后续 LLM 观点叠加
|
||||
- `factors`:命中的规则因子列表,每项含 name / delta / description,可直接用于分析
|
||||
- `llm_opinion`:预留字段,LLM 观点叠加时扩展此处,不改变现有规则逻辑
|
||||
|
||||
### FeedbackRequest(新 DTO)
|
||||
|
||||
```java
|
||||
public class FeedbackRequest {
|
||||
String sessionId; // 必填
|
||||
String feedback; // "useful" | "not_useful"
|
||||
}
|
||||
```
|
||||
|
||||
### FeedbackResponse(新 DTO)
|
||||
|
||||
```java
|
||||
public class FeedbackResponse {
|
||||
boolean success;
|
||||
String message;
|
||||
String caseId; // useful 时返回生成的 case_id,否则 null
|
||||
}
|
||||
```
|
||||
|
||||
### CaseLibrary 生成规则(useful 时)
|
||||
|
||||
| CaseLibrary 字段 | 来源 |
|
||||
|---|---|
|
||||
| caseId | UUID |
|
||||
| diagnosisId | DiagnosisSession.sessionId |
|
||||
| sourceType | SourceType.AUTO |
|
||||
| faultCategory | FaultCategory.GENERAL(暂时) |
|
||||
| title | DiagnosisSession.query 前 100 字符 |
|
||||
| rootCause | DiagnosisSession.answer(完整答案,不截断) |
|
||||
| solution | DiagnosisSession.answer(同上) |
|
||||
| createdBy | "system" |
|
||||
|
||||
## 关键技术决策
|
||||
|
||||
### 决策 1:LLM 自评异步执行
|
||||
|
||||
置信度计算在 Agent 主流程结束后异步进行(`@Async` + Spring 线程池),不阻塞用户响应。
|
||||
原因:LLM 自评耗时 1-3 秒,主流程不应等待。
|
||||
|
||||
### 决策 2:置信度规则兜底参数
|
||||
|
||||
```
|
||||
基础分:60
|
||||
工具调用加分:toolCallCount × 5,上限 +20
|
||||
步数少加分:stepCount <= 3 → +10
|
||||
status=FAILED → 直接 0
|
||||
```
|
||||
|
||||
### 决策 3:BAD_CASE 用 status 字段而非新字段
|
||||
|
||||
`DiagnosisSession.status` 已有 PENDING/RUNNING/SUCCESS/FAILED,扩展为允许包含 BAD_CASE。
|
||||
该字段是 VARCHAR 16,直接存字符串,无需枚举类(Java 端用常量控制)。
|
||||
|
||||
### 决策 4:案例内容提取策略
|
||||
|
||||
useful 时,`rootCause` 和 `solution` 从 `AgentStepRepository.findBySessionIdOrderByStepIndex` 的最后一步 `thought` 字段提取。
|
||||
如果 thought 为空,则用 DiagnosisSession.query + "(自动提取失败,请人工补充)" 占位。
|
||||
|
||||
## 接口影响等级
|
||||
|
||||
- `POST /api/feedback`:新增接口,L2(内部,前端新消费)
|
||||
- `ChatService.executeChat`:新增异步后置调用,不改返回值,L1
|
||||
- `DiagnosisSession.status` 增加 BAD_CASE 值:原调用方只读不写此字段,L2
|
||||
@@ -0,0 +1,61 @@
|
||||
# Proposal: 置信度评分与用户反馈机制
|
||||
|
||||
## 问题
|
||||
|
||||
DiagnosisSession 已预留 `selfEvaluation`(JSON)和 `feedback`(VARCHAR 16)两个字段,但目前完全为空——Agent 完成对话后不计算置信度,也没有接收用户反馈的 API,无法支撑报告质量评估和 BadCase 追踪。
|
||||
|
||||
## 建议方案
|
||||
|
||||
### 置信度评分(双轨)
|
||||
|
||||
**主轨:LLM 自评**
|
||||
- 在 ChatService 的 `executeChat` 流程结束后,追加一次轻量 LLM 调用(EvaluationService),
|
||||
将 Agent 的最终答案 + 步骤摘要传给模型,要求输出 `{"confidence": 0-100, "reasoning": "..."}` JSON。
|
||||
- 结果写入 `DiagnosisSession.selfEvaluation`。
|
||||
|
||||
**兜底轨:规则计算**
|
||||
- 若 LLM 自评失败(超时/解析失败),用规则计算:
|
||||
- 基础分 60
|
||||
- 工具调用数 > 0 每次 +5(上限 +20)
|
||||
- 步数 <= 3 额外 +10
|
||||
- 状态为 FAILED 直接 0
|
||||
- 兜底结果同样写入 `selfEvaluation`,并附 `"source": "rule"` 标记。
|
||||
|
||||
### 用户反馈 API
|
||||
|
||||
新增 `POST /api/feedback`,接收:
|
||||
```json
|
||||
{ "sessionId": "xxx", "feedback": "useful" | "not_useful" }
|
||||
```
|
||||
后端操作:
|
||||
1. 写入 `DiagnosisSession.feedback`。
|
||||
2. 若 `feedback = "not_useful"`,将 `status` 更新为 `BAD_CASE`(需要在 status 枚举扩展此值)。
|
||||
3. 若 `feedback = "useful"`,写入一条 `CaseLibrary` 记录(从 session 提取 query/answer)。
|
||||
|
||||
## 范围
|
||||
|
||||
- 新建 `EvaluationService`(置信度计算)
|
||||
- 新建 `FeedbackService`(反馈处理)
|
||||
- 新增 `POST /api/feedback` 接口(在 ChatController 或新 FeedbackController)
|
||||
- 改造 `ChatService.executeChat` 在 SUCCESS 后调用 EvaluationService
|
||||
- `DiagnosisSession.status` 枚举扩展 `BAD_CASE` 值
|
||||
- Flyway 迁移:`diagnosis_session.status` 列注释更新(不改类型,字段已存在)
|
||||
- 无需新建数据库表
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不实现 Verifier Agent 完整链路(只做轻量自评,不是多 Agent 编排)
|
||||
- 不实现案例沉淀的复杂结构化字段(CaseLibrary 的 faultCategory/errorCode 等填 GENERAL/null)
|
||||
- 不实现 BadCase 的自动分析或 Prompt 优化流程
|
||||
|
||||
## 关键约束(来自 devflow)
|
||||
|
||||
- JPA ddl-auto = validate,表结构变更必须走 Flyway 迁移,但本次无需加新列
|
||||
- `DiagnosisSession.selfEvaluation` 已声明为 JSON 类型,直接用 String 写入
|
||||
- `CaseLibrary.faultCategory` 是枚举,默认填 GENERAL
|
||||
- `SourceType.AUTO` 表示系统自动生成
|
||||
|
||||
## 风险
|
||||
|
||||
- LLM 自评 prompt 质量影响分数可信度,需要在 reasoning 字段记录依据
|
||||
- BAD_CASE status 与现有 PENDING/RUNNING/SUCCESS/FAILED 并存,需确认 UI 是否受影响
|
||||
@@ -0,0 +1,44 @@
|
||||
# Functional Spec: 置信度评分与用户反馈机制
|
||||
|
||||
## REQ-1:置信度 LLM 自评
|
||||
|
||||
- Agent 对话(executeChat / executeChatComplex)成功后,异步调用 EvaluationService。
|
||||
- EvaluationService 构造 Prompt,调用 ChatModel,要求输出纯 JSON:`{"confidence": 0-100, "reasoning": "...", "source": "llm"}`。
|
||||
- 若 JSON 解析成功,写入 `DiagnosisSession.selfEvaluation`。
|
||||
- 若调用失败或解析失败,转入规则兜底(REQ-2)。
|
||||
- 验收:对话结束后数秒内,DB `diagnosis_session.self_evaluation` 非 null,且 `source` 字段存在。
|
||||
|
||||
## REQ-2:置信度规则兜底
|
||||
|
||||
- 触发条件:LLM 自评失败(任何异常)。
|
||||
- 规则:基础分 60 + toolCallCount×5(上限+20)+ (stepCount<=3 ? +10 : 0),status=FAILED 则直接 0。
|
||||
- 结果写入 `selfEvaluation`,含 `"source": "rule"`。
|
||||
- 验收:LLM 自评失败时,self_evaluation 仍有值(非 null),且 source=rule。
|
||||
|
||||
## REQ-3:反馈接收 API
|
||||
|
||||
- 接口:`POST /api/feedback`
|
||||
- 入参:`{ "sessionId": "xxx", "feedback": "useful" | "not_useful" }`
|
||||
- 出参:`{ "success": true/false, "message": "...", "caseId": "uuid 或 null" }`
|
||||
- 校验:sessionId 不能为空;feedback 只能是 useful 或 not_useful,否则返回 400。
|
||||
- 验收:接口返回 200,DB 对应行 feedback 字段有值。
|
||||
|
||||
## REQ-4:useful → 案例沉淀
|
||||
|
||||
- 触发条件:feedback = "useful"。
|
||||
- 操作:在 case_library 插入一条记录,caseId=UUID,sourceType=AUTO,faultCategory=GENERAL,
|
||||
title=query 前 100 字,rootCause/solution 来自最后一步 agent_step.thought。
|
||||
- 响应中返回 caseId。
|
||||
- 验收:提交 useful 后,case_library 表新增一行,diagnosis_id = sessionId。
|
||||
|
||||
## REQ-5:not_useful → BAD_CASE 标记
|
||||
|
||||
- 触发条件:feedback = "not_useful"。
|
||||
- 操作:feedback 字段本身即为标记,不修改 status 字段(status 保持执行状态语义)。
|
||||
- 查询 BadCase 使用:`WHERE feedback = 'not_useful'`。
|
||||
- 验收:提交 not_useful 后,DB diagnosis_session.feedback = "not_useful",status 不变。
|
||||
|
||||
## REQ-6:幂等性
|
||||
|
||||
- 同一 sessionId 重复提交 feedback,覆盖写入(不报错,不重复创建 CaseLibrary)。
|
||||
- 已有 case_library 记录时(diagnosisId 已存在),跳过插入并返回已有 caseId。
|
||||
@@ -0,0 +1,133 @@
|
||||
# Tasks: 置信度评分与用户反馈机制
|
||||
|
||||
## T0:Flyway 迁移 + DiagnosisSession 实体加字段
|
||||
|
||||
**文件**:
|
||||
- `src/main/resources/db/migration/V008__add_answer_to_diagnosis_session.sql`(新建)
|
||||
- `src/main/java/com/superbiz/agent/domain/entity/DiagnosisSession.java`(加字段)
|
||||
|
||||
**迁移脚本**:
|
||||
```sql
|
||||
ALTER TABLE diagnosis_session ADD COLUMN answer LONGTEXT COMMENT 'Agent 返回给用户的完整答案';
|
||||
```
|
||||
|
||||
**实体**:在 `DiagnosisSession` 加:
|
||||
```java
|
||||
@Column(name = "answer", columnDefinition = "LONGTEXT")
|
||||
private String answer;
|
||||
```
|
||||
|
||||
**验收标准**:应用启动不报 schema validation 错误;`diagnosis_session` 表有 answer 列
|
||||
|
||||
---
|
||||
|
||||
— LLM 自评 + 规则兜底
|
||||
|
||||
**文件**:`src/main/java/com/superbiz/agent/service/EvaluationService.java`
|
||||
|
||||
**实现**:
|
||||
- `@Service @Async` 标注
|
||||
- `evaluate(String sessionId, String answer)` 方法:
|
||||
1. 从 `DiagnosisSessionRepository` 加载 session(含 stepCount、toolCallCount、status)
|
||||
2. 调用 ChatModel 做 LLM 自评,Prompt 见下
|
||||
3. 解析 JSON → 写入 `selfEvaluation`
|
||||
4. 失败时走规则兜底
|
||||
- 规则兜底逻辑:`computeRuleScore(session)` → 返回 JSON 字符串
|
||||
|
||||
**LLM 自评 Prompt(系统提示)**:
|
||||
```
|
||||
你是一个 AI 回答质量评估器。
|
||||
请根据以下信息,评估这次 AI 回答的置信度(0-100分):
|
||||
- 用户原始问题:{query}
|
||||
- AI 的回答:{answer}
|
||||
- 工具调用次数:{toolCallCount}
|
||||
- 推理步数:{stepCount}
|
||||
|
||||
只返回一个 JSON,格式如下,不要输出任何其他内容:
|
||||
{"confidence": <0-100的整数>, "reasoning": "<评估依据,50字以内>"}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
- LLM 正常时:DB selfEvaluation 包含 confidence 和 reasoning,source = "llm"
|
||||
- LLM 失败时:DB selfEvaluation 包含 confidence 和 source = "rule"
|
||||
|
||||
---
|
||||
|
||||
## T2:ChatService 后置调用 EvaluationService
|
||||
|
||||
**文件**:`src/main/java/com/superbiz/agent/service/ChatService.java`
|
||||
|
||||
**实现**:
|
||||
- 在 `executeChat` 的 `session.setStatus("SUCCESS")` 之后,追加 `session.setAnswer(answer)` 写入完整答案,再注入 EvaluationService 调用 `evaluate(sessionId, answer)`
|
||||
- 在 `executeChatComplex` 的 SUCCESS 分支同样补充 `session.setAnswer(answer)`
|
||||
- 注意:EvaluationService 是 @Async,调用方不等待返回值
|
||||
|
||||
**验收标准**:发送一次 chat 请求后,数秒内 DB self_evaluation 非 null
|
||||
|
||||
---
|
||||
|
||||
## T3:FeedbackController + FeedbackService
|
||||
|
||||
**文件**:
|
||||
- `src/main/java/com/superbiz/agent/controller/FeedbackController.java`(新建)
|
||||
- `src/main/java/com/superbiz/agent/service/FeedbackService.java`(新建)
|
||||
- `src/main/java/com/superbiz/agent/dto/FeedbackRequest.java`(新建)
|
||||
- `src/main/java/com/superbiz/agent/dto/FeedbackResponse.java`(新建)
|
||||
|
||||
**FeedbackService.submitFeedback(sessionId, feedback)**:
|
||||
1. 加载 session,sessionId 不存在抛异常
|
||||
2. 校验 feedback 值(useful/not_useful)
|
||||
3. 更新 `DiagnosisSession.feedback`
|
||||
4. if useful:调用 `CaseLibraryService.createFromSession(session)`
|
||||
5. if not_useful:更新 `DiagnosisSession.status = "BAD_CASE"`
|
||||
6. 保存 session
|
||||
7. 返回 FeedbackResponse
|
||||
|
||||
**幂等逻辑(useful 重复提交)**:
|
||||
- 调用 `CaseLibraryRepository.findByDiagnosisId(sessionId)` 检查
|
||||
- 已存在则返回已有 caseId,不重复插入
|
||||
|
||||
**FeedbackController**:
|
||||
```
|
||||
POST /api/feedback
|
||||
@RequestBody FeedbackRequest
|
||||
@ResponseBody FeedbackResponse
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
- useful:返回 200,feedback 字段有值,case_library 新增一行
|
||||
- not_useful:返回 200,status = BAD_CASE
|
||||
- 非法 feedback 值:返回 400
|
||||
|
||||
---
|
||||
|
||||
## T4:CaseLibraryService — createFromSession
|
||||
|
||||
**文件**:`src/main/java/com/superbiz/agent/service/CaseLibraryService.java`(新建)
|
||||
|
||||
**实现**:
|
||||
- `createFromSession(DiagnosisSession session)` → `CaseLibrary`
|
||||
- 直接从 `session.getAnswer()` 取完整答案
|
||||
- answer 为空时用占位文本 `query + "\n(自动提取失败,请人工补充)"`
|
||||
- 填写 CaseLibrary 各字段,save 后返回 caseId
|
||||
|
||||
**验收标准**:case_library 行的 diagnosis_id = sessionId,root_cause 非空
|
||||
|
||||
---
|
||||
|
||||
## T5:Spring @Async 配置
|
||||
|
||||
**文件**:检查项目是否已有 `@EnableAsync`,若无则在 `SessionConfiguration` 或新建 `AsyncConfig` 中添加
|
||||
|
||||
**验收标准**:EvaluationService 中 @Async 方法可被正确调度(不抛 bean 配置错误)
|
||||
|
||||
---
|
||||
|
||||
## T6:集成验证
|
||||
|
||||
验证步骤:
|
||||
1. 启动服务,POST /api/chat,发送一条问题
|
||||
2. 查 `diagnosis_session` 表,确认 self_evaluation 有值
|
||||
3. POST /api/feedback `{"sessionId": "xxx", "feedback": "useful"}`,确认 case_library 新增
|
||||
4. POST /api/feedback `{"sessionId": "yyy", "feedback": "not_useful"}`,确认 status = BAD_CASE
|
||||
5. 重复步骤 3,确认不重复创建 case_library
|
||||
Reference in New Issue
Block a user