Files
SuperBizAgent-java/openspec/changes/archive/2026-06-30-session-dedup-knowledge-map/specs/functional-spec.md
T
zhuyongxin a1c896ebda chore(docs): 归档 ISS-001 session-dedup-knowledge-map + ISS-002 mvp 文档
- 移动 session-dedup-knowledge-map OpenSpec 到 archive 目录
- 提交 ISS-001 遗留的 devflow 档案文件
- 更新 ISS-002 状态为已修复
- 新增 mvp/architecture/action-memory-relevance.md 设计文档
- 更新 mvp/README.md 文档导航
- 更新 devflow/index.md OpenSpec 链接指向 archive
2026-07-01 18:27:04 +08:00

88 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Functional Spec: session-dedup-knowledge-map
## REQ-01:工具层去重(Part A)
**触发**:`LookupKnowledgeTool.lookupKnowledge(query)` 被调用
**行为**:
1. 从 `SessionContextHolder.getSessionId()` 获取当前 sessionId;若为 null(非会话上下文)跳过去重逻辑,正常检索
2. L0+L1 检索完成后,将结果中已在 `RetrievedDocTracker` 中标记的 filePath 过滤掉
3. 若过滤后 L0 结果为空、L1 结果也为空(全部已召回),返回 `LookupResult.found=false`,并在结果中附带提示文本:"以下文档已在本会话中检索过:[列表],无需重复召回"
4. 未被过滤的文档正常返回后,将其 filePath 写入 `RetrievedDocTracker`
5. `SessionContextHolder.clear()` 调用时,`RetrievedDocTracker.clearSession(sessionId)` 同步清理
**验收**:
- 同一 session 内同一文档第二次命中时,返回去重提示而非完整文档内容
- 不同 session 之间互不影响
- sessionId 为 null 时不影响正常检索流程
---
## REQ-02:文档级 LLM 字段生成(Part B - 文档级)
**触发**:`DocumentManagementService.uploadDocument()` 完成 frontmatter 解析后
**行为**:
1. 若文档 frontmatter 中已包含 `covers` 和 `whenToRetrieve`,跳过 LLM 生成(作者手动填写优先)
2. 否则,调用 LLM,输入为文档 title + summary + 正文前 1000 字符
3. Prompt 要求 LLM 返回 JSON:`{"covers": [...], "whenToRetrieve": "..."}`
4. 解析结果,回填到 `Frontmatter` 对象
5. 序列化存入 `api_document.metadata`;同步更新 `KnowledgeEntry` 写入 L0 索引
6. LLM 调用失败时,`covers` 置为空列表,`whenToRetrieve` 置为 summary(降级),不阻断上传流程
**验收**:
- 上传后 `api_document.metadata` 中包含 `covers` 和 `whenToRetrieve` 字段
- frontmatter 已有这两个字段时不覆盖
- LLM 调用异常时文档仍上传成功,字段降级填充
---
## REQ-03:域级聚合与存储(Part B - 域级)
**触发**:文档上传成功后;文档删除后;`loadIndex()` 时发现某域在 `knowledge_domain` 表无记录
**行为**:
1. `KnowledgeDomainService.onDocumentChange(category)` 读取该 category 下所有 `KnowledgeEntry` 的 title + covers + whenToRetrieve
2. 调用 LLM,生成域级 `when_to_retrieve`(要求 LLM 识别域内文档边界,输出含区分语义的路由描述)
3. 写入 `knowledge_domain` 表(upsert by domain_id),同时更新 `document_count`
4. LLM 调用失败时,`domain.when_to_retrieve` 保留上次 DB 记录;若无历史记录则置为空字符串
**验收**:
- 上传文档后,对应 category 的 `knowledge_domain` 记录被更新
- 删除文档后,对应 category 的 `document_count` 减少,`when_to_retrieve` 重新生成
- `loadIndex()` 时无 DB 记录的域自动触发生成
---
## REQ-04:knowledge map 注入 Planner(Part B - 注入)
**触发**:`ChatService.buildChatPlannerAgent()` 调用时
**行为**:
1. 调用 `KnowledgeDomainService.buildKnowledgeMap()` 生成 YAML 文本
2. YAML 结构:域列表,每个域包含 domain_id、description、when_to_retrieve、documents(title + covers)
3. 若 knowledge_domain 表为空(无任何域记录),跳过注入,不修改 prompt
4. 注入位置:Planner system prompt 末尾,独立区块
**Planner prompt 附加规则**:
- 制定步骤时,先查看 `available_knowledge_domains`,按 `when_to_retrieve` 判断是否需要检索该域
- 每个域最多指示 Executor 检索一次;已检索过的域不再安排检索步骤
**验收**:
- Planner prompt 包含 `available_knowledge_domains` 区块
- 无域记录时 prompt 不包含该区块(不注入空结构)
- knowledge map 文本长度 < 1000 字符(6 个文档场景下)
---
## REQ-05:Frontmatter 字段扩展
**行为**:
- `Frontmatter.java` 新增 `List<String> covers` 和 `String whenToRetrieve`
- `KnowledgeEntry.java` 新增同名字段
- `FrontmatterParser.java` 解析 `covers`(YAML 数组)和 `when_to_retrieve`(YAML 字符串)
**验收**:
- 现有文档(无新字段)上传/解析不报错,字段为 null 或空列表
- 含新字段的文档正确解析