# 会话级去重与知识域地图 文档级去重 + 知识域地图注入 Planner,解决 ISS-001 Executor 重复召回同一文档问题。 --- ## 一、整体架构 本 change 包含两个独立但互补的部分: ``` Part A: 工具层去重 LookupKnowledgeTool ├── 维护 ConcurrentHashMap>(JVM 内) ├── 每次检索前过滤已召回文档 └── SessionContextHolder.clear() 时同步清理 Part B: 知识域地图 ┌─────────────────────────────────────────────┐ │ 文档上传 (DocumentManagementService) │ │ → LLM 生成 doc.covers + doc.when_to_retrieve │ │ → 存入 api_document.metadata │ │ → 触发域级重算 (KnowledgeDomainService) │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 域级聚合 (KnowledgeDomainService) │ │ → 读取同域所有文档的 when_to_retrieve │ │ → LLM 生成 domain.when_to_retrieve │ │ → 存入 knowledge_domain 表 │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 启动 (KnowledgeIndexService.loadIndex) │ │ → 加载 knowledge_domain 表 │ │ → 某域无记录则触发域级生成 │ └─────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ Planner prompt (ChatService) │ │ → 注入 knowledge map(域级) │ │ → Planner 做粗粒度检索决策 │ └─────────────────────────────────────────────┘ ``` --- ## 二、Part A:工具层去重 ### RetrievedDocTracker session 级已召回文档追踪组件,将去重责任从 LLM 移交到工具层。 ```java ConcurrentHashMap> retrieved key: sessionId value: Set ``` | 方法 | 作用 | |------|------| | `isAlreadyRetrieved(sessionId, filePath)` | 检查文档是否已召回 | | `markRetrieved(sessionId, filePath)` | 记录已召回文档 | | `clearSession(sessionId)` | 清理会话记录(SessionContextHolder.clear 触发) | ### 去重流程 ``` lookup_knowledge(query) → L0 检索 → 命中一批文档 → 遍历结果,过滤 isAlreadyRetrieved=true 的文档 → 剩余文档作为 primary/supplement 返回 → 实际返回的文档调用 markRetrieved ``` --- ## 三、Part B:知识域地图 ### Frontmatter 新增字段 文档上传时 LLM 自动生成以下两个字段: ```yaml covers: ["支付失败排查", "扣款无回调"] # 业务场景标签 when_to_retrieve: "用户描述支付失败、超时时" # 文档级检索时机 ``` ### knowledge_domain 表 ```sql CREATE TABLE knowledge_domain ( id BIGINT AUTO_INCREMENT PRIMARY KEY, domain_id VARCHAR(64) NOT NULL UNIQUE, description VARCHAR(256), when_to_retrieve TEXT, document_count INT DEFAULT 0, updated_at DATETIME, created_at DATETIME ); ``` ### Knowledge Map(注入 Planner 的 YAML) ```yaml available_knowledge_domains: - domain_id: "payment" description: "支付链路问题排查" when_to_retrieve: "用户问题涉及支付、退款、对账时检索;优先检索一次,勿重复" documents: - title: "支付失败排查手册" covers: ["支付超时", "扣款无回调"] - title: "退款处理指南" covers: ["退款未到账", "退款状态异常"] - domain_id: "infrastructure" ... ``` ### 注入链路 ``` 文档上传/删除 → KnowledgeDomainService.onDocumentChange(category) → 读取同域所有文档的 when_to_retrieve → LLM 聚合为 domain.when_to_retrieve → 写入 knowledge_domain 表 应用启动 → KnowledgeIndexService.loadIndex() → 加载 knowledge_domain → 无记录则触发聚合 → ChatService.buildChatPlannerAgent() 注入 prompt Planner prompt 中包含知识域地图 → Planner 做粗粒度检索决策("查 payment 域") → Executor 收到步骤后执行具体检索 ``` --- ## 四、关键设计决策 | 决策 | 方案 | 原因 | |------|------|------| | 域级 when_to_retrieve 存 DB | 持久化 | 避免每次重启调 LLM,文档变更时只重算受影响域 | | 文档级 when_to_retrieve 存 metadata JSON | 沿用现有路径 | 无需新增数据库字段 | | RetrievedDocTracker 独立于 SessionContextHolder | 职责分离 | SessionContextHolder 只持有 sessionId,Tracker 是业务状态 | | Planner 只看域级 | 分层决策 | 文档级 when_to_retrieve 留 Executor 筛选(Phase 2) | | LLM 调用同步执行 | 上传时即时生成 | 接受约 1-2s 延迟,保证数据库和 L0 索引立即一致 | --- ## 五、Agent 边界 ``` Planner 角色:知道"有什么域" └─ 知识域地图:选定要检索的域(一次规划) Executor 角色:知道"做了什么" └─ 行动记忆:域级 + 文档级去重(ISS-002 升级为双层记忆) ``` Part B(知识域地图)只注入 Planner prompt,**不注入 Executor prompt**。Executor 只通过 RetrievedDocTracker 知道自己已检索了哪些文档,不需要知道全局域有哪些。 --- ## 六、数据库变更 ### V009 ```sql CREATE TABLE knowledge_domain ( id BIGINT AUTO_INCREMENT PRIMARY KEY, domain_id VARCHAR(64) NOT NULL UNIQUE, description VARCHAR(256), when_to_retrieve TEXT, document_count INT DEFAULT 0, updated_at DATETIME, created_at DATETIME ); ``` --- ## 七、参考资料 - **架构文档**:`mvp/architecture/knowledge-retrieval-architecture.md` - **使用指南**:`mvp/architecture/knowledge-retrieval-usage.md` - **OpenSpec**:`openspec/changes/archive/2026-06-30-session-dedup-knowledge-map/`