Files
zhuyongxin e438df4355 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
2026-07-01 18:24:41 +08:00

5.2 KiB
Raw Permalink Blame History

Decisions: executor-action-memory-relevance

过程日志

Clarify 阶段

入口摘要:ISS-002 Executor 无约束重复调用 lookup_knowledge(单会话 20+ 次),需要行动记忆 + 归一化质量等级 + prompt 约束来解决。

slug: executor-action-memory-relevance

规模分档: standard(涉及 7 个文件,跨 DTO/工具层/持久化/Prompt,有设计决策需澄清)

Context 阶段

devflow/index.md 使用状态: 已命中。前序 change session-dedup-knowledge-map(archived)提供了 RetrievedDocTracker、KnowledgeDomainService、ISS-002 文档。

相关 ADR: 无直接 ADR,但 session-dedup-knowledge-map 的 decisions.md 和 evidence.md 记录了文档级去重和 knowledge map 注入的决策。

不能违反的历史决策:

  1. RetrievedDocTracker 的文档级去重必须保留
  2. knowledge map 只注入 Planner,不注入 Executor(本次讨论确认)
  3. L0/L1 原始分数不暴露给 LLM,只在归一化层内部使用(本次讨论确认)

需进入 OpenSpec 的上下文点:

  1. L1 score 是 L2 距离(值域 [0,+∞)),不是归一化分数——阈值设计需基于实际分布
  2. L0 的 category 可从 KnowledgeEntry.getCategory() 直接获取;L1 需解析 metadata JSON
  3. ReactAgent 是自主决策工具调用的 Agent,Prompt 约束是软约束

Grill 阶段 — Question Pool

维度:术语

  1. [evidence-driven] relevanceLevel 三个等级(PRECISE/HIGHLY_RELEVANT/REFERENCE)的边界是否清晰,是否存在 LLM 误解的可能? → 已查证:三个等级语义明确,PRECISE=唯一匹配、HIGHLY_RELEVANT=高分命中、REFERENCE=低置信度参考。LLM 理解风险低。

维度:边界 2. [evidence-driven] L1 score 是 L2 距离(值域 [0,+∞)),当前代码无阈值判断。归一化阈值如何设计? → 已查证:L2 距离典型范围取决于 BGE-M3 1024 维 embedding 的尺度,需从 tool_invocation.retrieval_details 中查询实际 l1_scores 分布才能定阈值。当前先以常量定义,标记为"需实测校准"。 3. [evidence-driven] L1 结果的 category 提取需要解析 metadata JSON 字符串,当前 SearchResult.metadata 是 toString() 的结果。归一化层是否需要 L1 的 domain? → 已查证:L1 的 domain 主要用于 RetrievedDocTracker 的域级记录。如果 L0 已命中且包含 category,可直接用 L0 的 category;如果仅 L1 命中,需解析 metadata 提取 category。当前知识库中 L0 大概率先命中,L1 domain 提取作为兜底路径。 4. [user-interview] 归一化阈值(L1 score 分界线)在实测数据不足时,是否接受先用保守初始值 + 后续调优的策略? → 用户待确认

维度:验收 5. [evidence-driven] 现有 tool_invocation 表 retrieval_details JSON 中 l1_scores 存的是 L2 距离原始值,新增的 relevance_level 和 completeness_hint 入库后是否需要回填历史数据? → 已查证:不需要回填历史数据,新列 nullable 即可,历史记录 relevance_level=null。

Grill 结论

evidence-driven 汇报:

  • E1: relevanceLevel 三等级语义清晰,LLM 误解风险低
  • E2: L1 score 是 L2 距离,值域不固定,阈值需实测校准
  • E3: L0 category 直接可用,L1 category 需解析 metadata(兜底路径)
  • E4: 历史数据不回填,新列 nullable

user-interview 已确认:

  • Q4: 归一化阈值先用保守初始值 + 后续调优 → 用户已确认,并建议用 Min-Max 归一化到 [0,1]

Specify 阶段补充

BGE-M3 L2 归一化实测验证:

  • FullPipelineSmokeTest.embeddingBgeM3Works() 新增 L2 范数断言
  • 结果:范数=1.00000002,误差 < 0.01,测试通过
  • 结论:BGE-M3 输出为 L2 归一化单位向量,L2 距离数学硬上界 = 2.0
  • Min-Max 归一化公式:similarity = 1 - min(l2Score, 2.0) / 2.0

Cross-artifact 对齐检查:

对齐项 状态
brief 目标/范围/非目标 → proposal 覆盖 已对齐
proposal 范围/约束 → design 覆盖 已对齐
design 归一化/行动记忆/接口影响 → specs 覆盖 已对齐
specs 可观察行为 → tasks 覆盖 已对齐

接口影响分级:

  • RetrievedDocTracker 数据结构升级 → L2(内部接口,消费者只有 LookupKnowledgeTool)
  • LookupResult 新增 3 字段 → L2(工具返回值,无跨模块调用方)
  • tool_invocation 新增 2 列 → L2(Flyway nullable,不影响现有查询)
  • chat-executor-prompt.md 更新 → L1(Prompt 文本变更)

Audit 阶段

架构风险评估(5 句以内):

  1. 归一化层嵌入 LookupKnowledgeTool 内部(静态方法),无跨模块耦合风险。
  2. RetrievedDocTracker 升级为双层结构,数据量级不变(文档数 × session 数),内存无风险。
  3. L1 metadata 解析 category 是兜底路径,如果 JSON 格式不一致可能解析失败——已有 try-catch 兜底。
  4. 归一化阈值 yml 配置化,运行时调优不需要改代码和重启——运维友好。
  5. Prompt 约束仍依赖 LLM 遵守——如果 Phase 1 效果不足,Phase 2 域级硬限制的 isDomainRetrieved 已就绪,无需额外改造。