Files
SuperBizAgent-java/src/main/java/com/superbiz/agent/tool/RetrievedDocTracker.java
T
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

90 lines
3.0 KiB
Java
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.
package com.superbiz.agent.tool;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* session 级已召回文档追踪器
* 支持文档级去重 + 域级行动记忆
*
* 数据结构:sessionId → { domain → Set<filePath> }
* - 域级:控制"不要重复查同域",提供行动记忆给 LLM
* - 文档级:控制"不要重复召回同文档"(替代原有单层结构)
*/
@Component
public class RetrievedDocTracker {
// key: sessionId, value: { domain → Set<filePath> }
private final ConcurrentHashMap<String, Map<String, Set<String>>> sessionRetrievals = new ConcurrentHashMap<>();
/**
* 记录一次检索(域级 + 文档级)
*/
public void markRetrieved(String sessionId, String domain, String filePath) {
if (sessionId == null || filePath == null) return;
sessionRetrievals.computeIfAbsent(sessionId,
k -> new ConcurrentHashMap<>())
.computeIfAbsent(domain != null ? domain : "_unknown",
d -> Collections.newSetFromMap(new ConcurrentHashMap<>()))
.add(filePath);
}
/**
* 文档级去重:检查 filePath 是否已在本会话中检索过
*/
public boolean isDocRetrieved(String sessionId, String filePath) {
if (sessionId == null || filePath == null) return false;
Map<String, Set<String>> domains = sessionRetrievals.get(sessionId);
if (domains == null) return false;
return domains.values().stream().anyMatch(docs -> docs.contains(filePath));
}
/**
* 域级检查:检查 domain 是否已在本会话中检索过
*/
public boolean isDomainRetrieved(String sessionId, String domain) {
if (sessionId == null || domain == null) return false;
Map<String, Set<String>> domains = sessionRetrievals.get(sessionId);
return domains != null && domains.containsKey(domain);
}
/**
* 获取本次会话已检索的域列表(行动记忆,返回给 LLM)
*/
public List<String> getRetrievedDomains(String sessionId) {
if (sessionId == null) return List.of();
Map<String, Set<String>> domains = sessionRetrievals.get(sessionId);
if (domains == null) return List.of();
return List.copyOf(domains.keySet());
}
/**
* 向后兼容:文档级去重(委托给 isDocRetrieved)
*/
public boolean isAlreadyRetrieved(String sessionId, String filePath) {
return isDocRetrieved(sessionId, filePath);
}
/**
* 向后兼容:旧版 markRetrieved(domain 设为 null,归入 _unknown)
*/
public void markRetrieved(String sessionId, String filePath) {
markRetrieved(sessionId, null, filePath);
}
/**
* 清理会话
*/
public void clearSession(String sessionId) {
if (sessionId == null) return;
sessionRetrievals.remove(sessionId);
}
}