feat(knowledge): Executor 行动记忆 + 归一化质量等级解决 ISS-002 重复检索
- RetrievedDocTracker 升级为域级+文档级双层记录(Map<sessionId, Map<domain, Set<filePath>>>) - LookupKnowledgeTool 新增 Min-Max 归一化层(BGE-M3 L2 距离→[0,1] similarity) - 三等级 relevanceLevel:PRECISE / HIGHLY_RELEVANT / REFERENCE + completenessHint 兜底信号 - LookupResult 新增 relevanceLevel、completenessHint、retrievedDomainsThisSession - Executor prompt 重写:4 条检索约束 + 合法出口不查全不追责,重复检索才惩罚 - 入库可观测性:V010 迁移 + retrieval_details JSON 扩展 - 归档 executor-action-memory-relevance change
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
Archive-ready for executor-action-memory-relevance
|
||||
|
||||
Created: 2026-07-01
|
||||
Tasks complete: 7/7
|
||||
Verification: static + script + manual passed
|
||||
Unverified: PRECISE scenario, domain_retrieved scenario (low risk)
|
||||
@@ -0,0 +1,189 @@
|
||||
# Design: executor-action-memory-relevance
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 整体数据流
|
||||
|
||||
```
|
||||
用户问题
|
||||
→ Supervisor → Planner(规划查哪些域)
|
||||
→ Supervisor → Executor(自主调用 lookup_knowledge)
|
||||
↓
|
||||
LookupKnowledgeTool
|
||||
├─ L0 精确匹配 → l0Matches (含 category)
|
||||
├─ L1 语义检索 → l1Results (含 L2 score)
|
||||
├─ 归一化层 → computeRelevanceLevel(l0Count, l1TopScore)
|
||||
│ L2 距离 → similarity = 1 - min(score, 2.0) / 2.0
|
||||
│ L0 唯一匹配 → PRECISE
|
||||
│ L0 命中 + L1 similarity ≥ 0.75 → HIGHLY_RELEVANT
|
||||
│ 仅 L1 similarity ≥ 0.75 → HIGHLY_RELEVANT
|
||||
│ L0 多匹配 + L1 similarity [0.5, 0.75) → REFERENCE
|
||||
│ 仅 L1 similarity [0.5, 0.75) → REFERENCE
|
||||
├─ 域级行动记忆 → RetrievedDocTracker.markRetrieved(sessionId, domain, filePath)
|
||||
│ getRetrievedDomains(sessionId) → retrievedDomainsThisSession
|
||||
├─ 文档级去重 → 保留现有逻辑
|
||||
└─ 组装 LookupResult(含 relevanceLevel, completenessHint, retrievedDomainsThisSession)
|
||||
↓
|
||||
LLM 看到:
|
||||
relevanceLevel: PRECISE
|
||||
completenessHint: "知识库中不存在比上述结果更精准的文档"
|
||||
retrievedDomainsThisSession: ["infrastructure", "api"]
|
||||
```
|
||||
|
||||
### Agent 边界(保持清晰)
|
||||
|
||||
| Agent | 知道什么 | 不知道什么 |
|
||||
|-------|---------|-----------|
|
||||
| Planner | 全域知识边界(knowledge map) | 执行细节、检索结果 |
|
||||
| Executor | 自己的行动记忆(已检索域列表) | 全域知识边界(不注入 knowledge map) |
|
||||
|
||||
行动记忆通过**工具返回值**传递,不通过 prompt 注入。
|
||||
|
||||
### 数据结构设计
|
||||
|
||||
#### 1. RetrievedDocTracker 升级
|
||||
|
||||
```java
|
||||
// 现有:sessionId → Set<filePath>(文档级)
|
||||
ConcurrentHashMap<String, Set<String>> retrieved
|
||||
|
||||
// 新增:sessionId → { domain → Set<filePath> }(域级 + 文档级)
|
||||
ConcurrentHashMap<String, Map<String, Set<String>>> sessionRetrievals
|
||||
```
|
||||
|
||||
方法列表:
|
||||
- `markRetrieved(sessionId, domain, filePath)` — 一次记录两层
|
||||
- `isDocRetrieved(sessionId, filePath)` → boolean — 文档级去重(替代现有 isAlreadyRetrieved)
|
||||
- `isDomainRetrieved(sessionId, domain)` → boolean — 域级检查(Phase 2 硬限制用)
|
||||
- `getRetrievedDomains(sessionId)` → List<String> — 行动记忆(返回给 LLM)
|
||||
- `clearSession(sessionId)` — 清理(不变)
|
||||
|
||||
#### 2. LookupResult 扩展
|
||||
|
||||
```java
|
||||
@Data @Builder
|
||||
public class LookupResult {
|
||||
boolean found;
|
||||
PrimaryResult primary; // 不变,不暴露原始分数
|
||||
SupplementResult supplement; // 不变,不暴露原始分数
|
||||
// ---- 新增 ----
|
||||
String relevanceLevel; // PRECISE / HIGHLY_RELEVANT / REFERENCE
|
||||
String completenessHint; // 兜底信号
|
||||
List<String> retrievedDomainsThisSession; // 行动记忆
|
||||
String message; // 不变
|
||||
}
|
||||
```
|
||||
|
||||
**PrimaryResult 和 SupplementResult 不加任何分数字段**。原始分数在归一化层内部消化。
|
||||
|
||||
#### 3. 归一化计算
|
||||
|
||||
`RelevanceNormalizer`(LookupKnowledgeTool 内部静态方法):
|
||||
|
||||
```
|
||||
输入:l0MatchCount, l1TopScore (L2 距离)
|
||||
输出:RelevanceAssessment { relevanceLevel, completenessHint }
|
||||
|
||||
归一化公式(BGE-M3 输出 L2 归一化单位向量,已实测验证):
|
||||
similarity = 1 - min(l2Score, maxL2Distance) / maxL2Distance
|
||||
maxL2Distance 默认 2.0,yml 可覆盖
|
||||
|
||||
判定逻辑:
|
||||
if l0MatchCount == 1 → PRECISE
|
||||
if l0MatchCount > 1 && l1Similarity >= highlyRelevantThreshold → HIGHLY_RELEVANT
|
||||
if l0MatchCount == 0 && l1Similarity >= highlyRelevantThreshold → HIGHLY_RELEVANT
|
||||
if l0MatchCount > 1 && l1Similarity >= referenceThreshold → REFERENCE
|
||||
if l0MatchCount == 0 && l1Similarity >= referenceThreshold → REFERENCE
|
||||
else → 无结果
|
||||
|
||||
completenessHint 映射:
|
||||
PRECISE → "知识库中不存在比上述结果更精准的文档"
|
||||
HIGHLY_RELEVANT → "当前结果已高度相关,继续检索不太可能找到更精准的文档"
|
||||
REFERENCE → "当前结果为相关参考,如需更精准信息请明确缺少的具体维度"
|
||||
```
|
||||
|
||||
配置项(application.yml):
|
||||
```yaml
|
||||
retrieval:
|
||||
normalization:
|
||||
max-l2-distance: 2.0 # L2 距离上界(单位向量 = 2.0)
|
||||
highly-relevant-threshold: 0.75 # similarity ≥ 0.75 → HIGHLY_RELEVANT
|
||||
reference-threshold: 0.5 # similarity ≥ 0.5 → REFERENCE
|
||||
```
|
||||
|
||||
#### 4. 入库记录扩展
|
||||
|
||||
`tool_invocation` 表新增列:
|
||||
|
||||
| 列名 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `relevance_level` | VARCHAR(20) | PRECISE / HIGHLY_RELEVANT / REFERENCE / DEDUPED |
|
||||
| `dedup_reason` | VARCHAR(32) | doc_retrieved / domain_retrieved / null |
|
||||
|
||||
`retrieval_details` JSON 扩展:
|
||||
```json
|
||||
{
|
||||
"l0_match_count": 2,
|
||||
"l0_titles": ["MySQL连接池配置", "HikariCP参数调优"],
|
||||
"l1_top_score": 0.52,
|
||||
"l1_top_similarity": 0.74,
|
||||
"l1_match_count": 3,
|
||||
"l1_scores": [0.52, 0.68, 0.91],
|
||||
"relevance_level": "HIGHLY_RELEVANT",
|
||||
"completeness_hint": "当前结果已高度相关...",
|
||||
"retrieved_domains": ["infrastructure"],
|
||||
"dedup_reason": null
|
||||
}
|
||||
```
|
||||
|
||||
原始 L2 score 和归一化后的 similarity 都入库,保留可观测性。
|
||||
|
||||
### Executor Prompt 设计
|
||||
|
||||
不加 knowledge map,只加基于行动记忆的行为规则:
|
||||
|
||||
```markdown
|
||||
## 检索约束
|
||||
|
||||
### 1. 判断重复:基于已检索上下文
|
||||
每次 lookup_knowledge 返回值中包含 retrievedDomainsThisSession,
|
||||
表示本次会话已检索过的知识域。如果当前问题与已检索域语义重叠,
|
||||
**禁止再次调用 lookup_knowledge**。
|
||||
|
||||
### 2. 重复了该怎么办
|
||||
如果当前想检索的内容与【已检索上下文】语义相似:
|
||||
- 禁止换关键词重新检索
|
||||
- 直接基于已有事实回答
|
||||
- 如果信息不足,先明确指出缺少什么具体维度
|
||||
(如:"缺少 HikariCP 具体配置参数"、"缺少连接池耗尽的日志样例"),
|
||||
再针对该维度进行一次定向补充检索——而非盲目换词重查
|
||||
|
||||
### 3. 合法出口:允许信息不全时给出结论
|
||||
如果你认为已有信息足以回答核心问题,即使细节不全,
|
||||
也请直接给出结论并说明局限性(如:"基于已有信息,连接池配置建议如下,
|
||||
但具体参数值需结合实际负载调整")。
|
||||
**不查全不会被追责,重复检索才会被惩罚。**
|
||||
|
||||
### 4. 利用质量信号判断
|
||||
- relevanceLevel=PRECISE → 信息精准,直接使用,不再检索
|
||||
- relevanceLevel=HIGHLY_RELEVANT + 域已在 retrievedDomainsThisSession → 禁止再次调用
|
||||
- relevanceLevel=REFERENCE → 先指出缺什么维度,再定向补充一次
|
||||
- completenessHint 是知识库给你的天花板信号,信任它
|
||||
```
|
||||
|
||||
### 关键决策
|
||||
|
||||
1. **L0/L1 原始分数不暴露给 LLM** — 在归一化层内部消化,避免 LLM 混淆尺度
|
||||
2. **BGE-M3 L2 归一化已实测验证** — 范数 1.00000002,maxL2Distance=2.0 是数学硬上界
|
||||
3. **行动记忆通过工具返回值传递** — 不通过 prompt 注入,不修改 ReactAgent prompt 构建方式
|
||||
4. **不给 Executor knowledge map** — 保持 Agent 边界:Planner 知道全域,Executor 只知道自己做了什么
|
||||
5. **Phase 2 域级硬限制暂不实施** — 先观察 prompt 约束 + 归一化信号的效果
|
||||
|
||||
### 接口影响分级
|
||||
|
||||
| 变更 | 级别 | 说明 |
|
||||
|------|------|------|
|
||||
| RetrievedDocTracker 数据结构升级 | L2 内部接口 | 消费者只有 LookupKnowledgeTool,在同一实现范围内 |
|
||||
| LookupResult 新增 3 个字段 | L2 内部接口 | 消费者是 LLM(工具返回值),无跨模块调用方 |
|
||||
| tool_invocation 表新增 2 列 | L2 内部接口 | Flyway 迁移,nullable,不影响现有查询 |
|
||||
| chat-executor-prompt.md 更新 | L1 内部实现 | Prompt 文本变更,不改变接口 |
|
||||
@@ -0,0 +1,89 @@
|
||||
# Proposal: executor-action-memory-relevance
|
||||
|
||||
## 问题
|
||||
|
||||
ISS-002:Executor 在单次会话中调用 `lookup_knowledge` 20+ 次,大部分是同域换变体的冗余调用。
|
||||
|
||||
根因:
|
||||
1. **行动记忆缺失**:Executor 不知道自己已经检索过哪些域,反复用不同关键词查同一个域
|
||||
2. **质量信号缺失**:检索结果没有归一化质量等级,LLM 无法判断"结果够不够"
|
||||
3. **Prompt 约束缺失**:现有 executor prompt 要求"所有需要外部信息的地方都必须调用工具",没有"放弃检索"的合法出口
|
||||
|
||||
## 建议方案
|
||||
|
||||
### 1. 行动记忆(通过工具返回值传递)
|
||||
|
||||
`RetrievedDocTracker` 数据结构升级:`Map<sessionId, Map<domain, Set<filePath>>>`。
|
||||
|
||||
每次 `lookup_knowledge` 返回值附带 `retrievedDomainsThisSession`,让 Executor 知道自己本次会话已检索过哪些域。
|
||||
|
||||
**不给 Executor knowledge map**——保持 Agent 边界清晰:Planner 知道全域(规划查哪个域),Executor 只知道自己做了什么(执行检索 + 基于结果推理)。
|
||||
|
||||
### 2. 归一化质量等级(封装 L0/L1 分数差异)
|
||||
|
||||
在 `LookupKnowledgeTool` 内部新增归一化层,将 L0 匹配数和 L1 score 统一为三个等级:
|
||||
|
||||
| 等级 | 含义 | LLM 应做什么 |
|
||||
|------|------|-------------|
|
||||
| `PRECISE` | 精准命中 | 直接使用,不再检索 |
|
||||
| `HIGHLY_RELEVANT` | 高度相关 | 综合推理,大概率不需要继续查 |
|
||||
| `REFERENCE` | 相关参考 | 可参考,如需更精准请明确缺什么维度 |
|
||||
|
||||
归一化逻辑:
|
||||
- L0 唯一匹配 → PRECISE
|
||||
- L0 命中 + L1 高分 → HIGHLY_RELEVANT
|
||||
- L0 多匹配 + L1 中分 → HIGHLY_RELEVANT
|
||||
- L0 多匹配 + 无 L1 → REFERENCE
|
||||
- 仅 L1 命中 → 按 score 分 HIGHLY_RELEVANT / REFERENCE
|
||||
|
||||
**L0/L1 原始分数不返回给 LLM**,只在归一化层内部使用。原始分数入库(`tool_invocation.retrieval_details`)保留可观测性。
|
||||
|
||||
### 3. 兜底信号(completenessHint)
|
||||
|
||||
每次返回附带 `completenessHint`,给 LLM "天花板"信号:
|
||||
|
||||
| relevanceLevel | completenessHint |
|
||||
|----------------|-----------------|
|
||||
| PRECISE | "知识库中不存在比上述结果更精准的文档" |
|
||||
| HIGHLY_RELEVANT | "当前结果已高度相关,继续检索不太可能找到更精准的文档" |
|
||||
| REFERENCE | "当前结果为相关参考,如需更精准信息请明确缺少的具体维度" |
|
||||
|
||||
### 4. Executor prompt 重写检索约束
|
||||
|
||||
- 基于 `retrievedDomainsThisSession` 判断重复(不是"不要重复",而是"重复了该怎么办")
|
||||
- 给 LLM 合法出口:"不查全不会被追责,重复检索才会被惩罚"
|
||||
- 利用 `relevanceLevel` + `completenessHint` 判断质量
|
||||
|
||||
### 5. 入库可观测性
|
||||
|
||||
`tool_invocation` 表新增 `relevance_level` 和 `dedup_reason` 列。
|
||||
`retrieval_details` JSON 扩展:加入归一化等级、兜底信号、已检索域、去重原因、L1 top score。
|
||||
|
||||
## 范围
|
||||
|
||||
- `LookupKnowledgeTool`:归一化层 + 行动记忆注入 + 域级拦截
|
||||
- `RetrievedDocTracker`:数据结构升级(域级记录)
|
||||
- `LookupResult`:新增 `relevanceLevel`、`completenessHint`、`retrievedDomainsThisSession`
|
||||
- `chat-executor-prompt.md`:检索约束重写
|
||||
- `ToolInvocation` 实体 + V010 迁移:新增列
|
||||
- `LookupKnowledgeTool.saveToolInvocation()`:扩展入库字段
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不给 Executor 注入 knowledge map(保持 Agent 边界)
|
||||
- 不修改 Planner prompt 或 Planner 逻辑
|
||||
- 不修改 `PrimaryResult`/`SupplementResult` 的字段(不暴露原始分数给 LLM)
|
||||
- Phase 2 域级硬限制暂不实施,先观察 prompt 约束效果
|
||||
|
||||
## 风险
|
||||
|
||||
1. L1 score 阈值(0.3/0.7)需要根据实际 embedding 分布调优,当前为初始值
|
||||
2. 归一化等级可能让 LLM 过早停止检索——需实测观察 REFERENCE 场景下的行为
|
||||
3. Prompt 约束仍依赖 LLM 遵守——如果效果不足,需启用 Phase 2 域级硬限制
|
||||
|
||||
## 来自 devflow 的上下文约束
|
||||
|
||||
- 前序 change `session-dedup-knowledge-map`:已实现文档级去重(RetrievedDocTracker + filePath)和 Planner knowledge map 注入
|
||||
- ISS-001:文档级重复召回已修复
|
||||
- glossary:ReactAgent 是自主决策工具调用的 Agent,不受外部流程控制
|
||||
- JPA ddl-auto 使用 validate 模式,表结构修改必须通过 Flyway 迁移
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Functional Spec: executor-action-memory-relevance
|
||||
|
||||
## FS-1: L2 距离归一化
|
||||
|
||||
### 需求
|
||||
LookupKnowledgeTool 内部将 L1 的 L2 距离归一化为 [0,1] 区间的 similarity 值,基于 BGE-M3 输出为 L2 归一化单位向量(已实测验证,范数=1.00000002)。
|
||||
|
||||
### 可观察行为
|
||||
- 归一化公式:`similarity = 1 - min(l2Score, maxL2Distance) / maxL2Distance`
|
||||
- `maxL2Distance` 默认 2.0,可通过 `retrieval.normalization.max-l2-distance` 覆盖
|
||||
- 归一化阈值可通过 `retrieval.normalization.highly-relevant-threshold` 和 `retrieval.normalization.reference-threshold` 配置
|
||||
- 归一化计算在 LookupKnowledgeTool 内部完成,不暴露原始分数给 LLM
|
||||
|
||||
### 验收标准
|
||||
- [ ] L2 score=0 → similarity=1.0
|
||||
- [ ] L2 score=1.0 → similarity=0.5
|
||||
- [ ] L2 score=2.0 → similarity=0.0
|
||||
- [ ] L2 score=3.0(超出上界)→ similarity=0.0(min 函数截断)
|
||||
- [ ] 配置项可通过 yml 覆盖默认值
|
||||
|
||||
## FS-2: 归一化质量等级判定
|
||||
|
||||
### 需求
|
||||
基于 L0 匹配数和归一化后的 L1 similarity,输出三等级 relevanceLevel + completenessHint。
|
||||
|
||||
### 可观察行为
|
||||
- L0 唯一匹配 → PRECISE + "知识库中不存在比上述结果更精准的文档"
|
||||
- L0 命中 + L1 similarity ≥ 0.75 → HIGHLY_RELEVANT + "当前结果已高度相关,继续检索不太可能找到更精准的文档"
|
||||
- 仅 L1 similarity ≥ 0.75 → HIGHLY_RELEVANT + 对应 hint
|
||||
- L0 多匹配 + L1 similarity [0.5, 0.75) → REFERENCE + "当前结果为相关参考,如需更精准信息请明确缺少的具体维度"
|
||||
- 仅 L1 similarity [0.5, 0.75) → REFERENCE + 对应 hint
|
||||
- L1 similarity < 0.5 → 不视为有效结果
|
||||
- 无 L0 且无 L1 → found=false
|
||||
|
||||
### 验收标准
|
||||
- [ ] L0 matchCount=1 → relevanceLevel=PRECISE
|
||||
- [ ] L0 matchCount=2, L1 similarity=0.8 → relevanceLevel=HIGHLY_RELEVANT
|
||||
- [ ] L0 matchCount=0, L1 similarity=0.8 → relevanceLevel=HIGHLY_RELEVANT
|
||||
- [ ] L0 matchCount=3, L1 similarity=0.6 → relevanceLevel=REFERENCE
|
||||
- [ ] L0 matchCount=0, L1 similarity=0.4 → found=false 或 supplement 被过滤
|
||||
- [ ] 每个 relevanceLevel 对应正确的 completenessHint
|
||||
|
||||
## FS-3: 域级行动记忆
|
||||
|
||||
### 需求
|
||||
RetrievedDocTracker 升级为域级 + 文档级双层记录,支持查询当前会话已检索的域列表。
|
||||
|
||||
### 可观察行为
|
||||
- `markRetrieved(sessionId, domain, filePath)` 一次记录两层
|
||||
- `isDocRetrieved(sessionId, filePath)` 返回文档级去重结果
|
||||
- `isDomainRetrieved(sessionId, domain)` 返回域级检查结果
|
||||
- `getRetrievedDomains(sessionId)` 返回已检索域列表
|
||||
- `clearSession(sessionId)` 清理所有记录
|
||||
- 现有 `isAlreadyRetrieved(sessionId, filePath)` 语义不变(内部委托给 isDocRetrieved)
|
||||
|
||||
### 验收标准
|
||||
- [ ] markRetrieved("s1", "infrastructure", "a.md") 后,isDocRetrieved("s1", "a.md")=true
|
||||
- [ ] markRetrieved("s1", "infrastructure", "a.md") 后,isDomainRetrieved("s1", "infrastructure")=true
|
||||
- [ ] markRetrieved("s1", "infrastructure", "a.md") 后,getRetrievedDomains("s1")=["infrastructure"]
|
||||
- [ ] markRetrieved("s1", "api", "b.md") 后,getRetrievedDomains("s1")=["infrastructure","api"]
|
||||
- [ ] clearSession("s1") 后,所有方法返回空/false
|
||||
- [ ] 线程安全:ConcurrentHashMap + ConcurrentHashMap 内层
|
||||
|
||||
## FS-4: LookupResult 返回值扩展
|
||||
|
||||
### 需求
|
||||
LookupResult 新增 relevanceLevel、completenessHint、retrievedDomainsThisSession 三个字段,让 LLM 获得行动记忆和质量信号。
|
||||
|
||||
### 可观察行为
|
||||
- 每次 lookup_knowledge 返回值包含这三个新字段
|
||||
- PrimaryResult 和 SupplementResult 不变,不暴露原始分数
|
||||
- 去重拦截时,返回值仍包含 retrievedDomainsThisSession(让 LLM 知道已检索了哪些域)
|
||||
|
||||
### 验收标准
|
||||
- [ ] 正常检索返回时,LookupResult 包含 relevanceLevel + completenessHint + retrievedDomainsThisSession
|
||||
- [ ] 文档级去重拦截时,LookupResult.message 包含去重提示,retrievedDomainsThisSession 不为 null
|
||||
- [ ] PrimaryResult 和 SupplementResult 无新增分数字段
|
||||
|
||||
## FS-5: Executor Prompt 检索约束
|
||||
|
||||
### 需求
|
||||
重写 chat-executor-prompt.md 的检索规则,从"必须调用工具"改为"基于行动记忆和质量信号判断是否需要检索"。
|
||||
|
||||
### 可观察行为
|
||||
- Prompt 不包含 knowledge map
|
||||
- Prompt 包含 4 条检索约束(判断重复、重复了该怎么办、合法出口、利用质量信号)
|
||||
- 原有规则"所有需要外部信息的地方,都必须调用对应的工具"被替换
|
||||
|
||||
### 验收标准
|
||||
- [ ] Executor prompt 不包含 knowledge map 内容
|
||||
- [ ] Executor prompt 包含"禁止换关键词重新检索"约束
|
||||
- [ ] Executor prompt 包含"不查全不会被追责"合法出口
|
||||
- [ ] Executor prompt 包含 relevanceLevel 行为指导
|
||||
|
||||
## FS-6: 入库可观测性
|
||||
|
||||
### 需求
|
||||
tool_invocation 表新增 relevance_level 和 dedup_reason 列,retrieval_details JSON 扩展。
|
||||
|
||||
### 可观察行为
|
||||
- 每次 lookup_knowledge 调用后,tool_invocation 记录包含 relevance_level 和 dedup_reason
|
||||
- retrieval_details JSON 包含 l1_top_similarity(归一化后值)、relevance_level、completeness_hint、retrieved_domains、dedup_reason
|
||||
- 历史数据新列为 null,不影响现有查询
|
||||
|
||||
### 验收标准
|
||||
- [ ] V010 迁移脚本成功执行
|
||||
- [ ] 新增 relevance_level 列 VARCHAR(20) nullable
|
||||
- [ ] 新增 dedup_reason 列 VARCHAR(32) nullable
|
||||
- [ ] saveToolInvocation() 写入新字段
|
||||
- [ ] SQL 可查询归一化等级分布:`SELECT relevance_level, COUNT(*) FROM tool_invocation WHERE tool_name='lookup_knowledge' GROUP BY relevance_level`
|
||||
@@ -0,0 +1,89 @@
|
||||
# Tasks: executor-action-memory-relevance
|
||||
|
||||
## T1: RetrievedDocTracker 域级升级
|
||||
|
||||
**文件**: `src/main/java/com/superbiz/agent/tool/RetrievedDocTracker.java`
|
||||
|
||||
**改动**:
|
||||
- 数据结构从 `ConcurrentHashMap<sessionId, Set<filePath>>` 升级为 `ConcurrentHashMap<sessionId, Map<domain, Set<filePath>>>`
|
||||
- 新增 `markRetrieved(sessionId, domain, filePath)`
|
||||
- 新增 `isDocRetrieved(sessionId, filePath)` — 从内层 Map 的 values 中查找 filePath
|
||||
- 新增 `isDomainRetrieved(sessionId, domain)` — 检查 domain key 存在
|
||||
- 新增 `getRetrievedDomains(sessionId)` → `List<String>`
|
||||
- `isAlreadyRetrieved(sessionId, filePath)` 保留(委托给 isDocRetrieved,向后兼容)
|
||||
- `clearSession(sessionId)` 清理外层 key
|
||||
|
||||
**验收**: FS-3 所有验收标准通过
|
||||
|
||||
## T2: LookupResult 新增字段
|
||||
|
||||
**文件**: `src/main/java/com/superbiz/agent/dto/LookupResult.java`
|
||||
|
||||
**改动**:
|
||||
- 新增 `String relevanceLevel`
|
||||
- 新增 `String completenessHint`
|
||||
- 新增 `List<String> retrievedDomainsThisSession`
|
||||
|
||||
**验收**: 编译通过,字段存在且类型正确
|
||||
|
||||
## T3: 归一化计算逻辑
|
||||
|
||||
**文件**: `src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`
|
||||
|
||||
**改动**:
|
||||
- 新增配置类或字段读取 `retrieval.normalization.max-l2-distance`(默认 2.0)、`highly-relevant-threshold`(默认 0.75)、`reference-threshold`(默认 0.5)
|
||||
- 新增私有方法 `computeRelevance(int l0MatchCount, float l1TopScore)` → 返回包含 `relevanceLevel` + `completenessHint` 的 record/内部类
|
||||
- L2 距离归一化:`similarity = 1 - min(l1TopScore, maxL2Distance) / maxL2Distance`
|
||||
- 判定逻辑按 design.md 中的优先级实现
|
||||
|
||||
**验收**: FS-1 + FS-2 所有验收标准通过
|
||||
|
||||
## T4: LookupKnowledgeTool 集成归一化 + 行动记忆
|
||||
|
||||
**文件**: `src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`
|
||||
|
||||
**改动**:
|
||||
- `lookupKnowledge()` 方法中,在 Step 4(组装结果)后、Step 5(去重过滤)前,调用 `computeRelevance()` 计算 relevanceLevel 和 completenessHint
|
||||
- 从 l0Matches 提取 domain(`l0Matches.get(0).getCategory()`),L1 结果尝试从 metadata JSON 解析 category(兜底)
|
||||
- markRetrieved 调用从 `markRetrieved(sessionId, docKey)` 改为 `markRetrieved(sessionId, domain, docKey)`
|
||||
- 去重拦截时(文档级),LookupResult 也附带 retrievedDomainsThisSession
|
||||
- LookupResult.builder() 中设置三个新字段
|
||||
|
||||
**验收**: FS-4 所有验收标准通过;日志中可看到 relevanceLevel 和 completenessHint 输出
|
||||
|
||||
## T5: Executor Prompt 重写
|
||||
|
||||
**文件**: `src/main/resources/prompts/chat-executor-prompt.md`
|
||||
|
||||
**改动**:
|
||||
- 将"所有需要外部信息的地方,都必须调用对应的工具"替换为"需要外部信息时调用工具,但须遵守下方的检索约束"
|
||||
- 新增"## 检索约束"区块,包含 4 条规则(判断重复、重复了该怎么办、合法出口、利用质量信号)
|
||||
- 不注入 knowledge map
|
||||
|
||||
**验收**: FS-5 所有验收标准通过
|
||||
|
||||
## T6: 入库可观测性
|
||||
|
||||
**文件**:
|
||||
- `src/main/resources/db/migration/V010__add_relevance_level_to_tool_invocation.sql`
|
||||
- `src/main/java/com/superbiz/agent/domain/entity/ToolInvocation.java`
|
||||
- `src/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.java`(saveToolInvocation 方法)
|
||||
|
||||
**改动**:
|
||||
- V010: ALTER TABLE tool_invocation ADD relevance_level VARCHAR(20), ADD dedup_reason VARCHAR(32)
|
||||
- ToolInvocation 实体新增 `relevanceLevel` 和 `dedupReason` 字段
|
||||
- saveToolInvocation() 中:
|
||||
- 设置 `inv.setRelevanceLevel(...)` 和 `inv.setDedupReason(...)`
|
||||
- retrieval_details JSON 扩展:新增 l1_top_similarity、relevance_level、completeness_hint、retrieved_domains、dedup_reason 字段
|
||||
- 去重拦截时,dedupReason 设为 "doc_retrieved";域级拦截时设为 "domain_retrieved"
|
||||
|
||||
**验收**: FS-6 所有验收标准通过
|
||||
|
||||
## T7: BGE-M3 归一化验证测试
|
||||
|
||||
**文件**: `src/test/java/com/superbiz/agent/service/FullPipelineSmokeTest.java`
|
||||
|
||||
**改动**:
|
||||
- 已完成:embeddingBgeM3Works() 中新增 L2 范数断言(范数=1.00000002,测试已通过)
|
||||
|
||||
**验收**: 测试通过,范数断言 |norm - 1.0| < 0.01
|
||||
Reference in New Issue
Block a user