- 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
7.7 KiB
7.7 KiB
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 升级
// 现有: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 — 行动记忆(返回给 LLM)clearSession(sessionId)— 清理(不变)
2. LookupResult 扩展
@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):
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 扩展:
{
"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,只加基于行动记忆的行为规则:
## 检索约束
### 1. 判断重复:基于已检索上下文
每次 lookup_knowledge 返回值中包含 retrievedDomainsThisSession,
表示本次会话已检索过的知识域。如果当前问题与已检索域语义重叠,
**禁止再次调用 lookup_knowledge**。
### 2. 重复了该怎么办
如果当前想检索的内容与【已检索上下文】语义相似:
- 禁止换关键词重新检索
- 直接基于已有事实回答
- 如果信息不足,先明确指出缺少什么具体维度
(如:"缺少 HikariCP 具体配置参数"、"缺少连接池耗尽的日志样例"),
再针对该维度进行一次定向补充检索——而非盲目换词重查
### 3. 合法出口:允许信息不全时给出结论
如果你认为已有信息足以回答核心问题,即使细节不全,
也请直接给出结论并说明局限性(如:"基于已有信息,连接池配置建议如下,
但具体参数值需结合实际负载调整")。
**不查全不会被追责,重复检索才会被惩罚。**
### 4. 利用质量信号判断
- relevanceLevel=PRECISE → 信息精准,直接使用,不再检索
- relevanceLevel=HIGHLY_RELEVANT + 域已在 retrievedDomainsThisSession → 禁止再次调用
- relevanceLevel=REFERENCE → 先指出缺什么维度,再定向补充一次
- completenessHint 是知识库给你的天花板信号,信任它
关键决策
- L0/L1 原始分数不暴露给 LLM — 在归一化层内部消化,避免 LLM 混淆尺度
- BGE-M3 L2 归一化已实测验证 — 范数 1.00000002,maxL2Distance=2.0 是数学硬上界
- 行动记忆通过工具返回值传递 — 不通过 prompt 注入,不修改 ReactAgent prompt 构建方式
- 不给 Executor knowledge map — 保持 Agent 边界:Planner 知道全域,Executor 只知道自己做了什么
- Phase 2 域级硬限制暂不实施 — 先观察 prompt 约束 + 归一化信号的效果
接口影响分级
| 变更 | 级别 | 说明 |
|---|---|---|
| RetrievedDocTracker 数据结构升级 | L2 内部接口 | 消费者只有 LookupKnowledgeTool,在同一实现范围内 |
| LookupResult 新增 3 个字段 | L2 内部接口 | 消费者是 LLM(工具返回值),无跨模块调用方 |
| tool_invocation 表新增 2 列 | L2 内部接口 | Flyway 迁移,nullable,不影响现有查询 |
| chat-executor-prompt.md 更新 | L1 内部实现 | Prompt 文本变更,不改变接口 |