- 移动 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
88 lines
4.1 KiB
Markdown
88 lines
4.1 KiB
Markdown
# 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 或空列表
|
||
- 含新字段的文档正确解析
|