package com.superbiz.agent.tool; import com.superbiz.agent.dto.*; import com.superbiz.agent.service.KnowledgeIndexService; import com.superbiz.agent.service.VectorSearchService; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.tool.annotation.Tool; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * 知识库查询工具 * 提供给 Agent 的混合检索工具(L0 + L1) */ @Slf4j @Component public class LookupKnowledgeTool { @Autowired private KnowledgeIndexService knowledgeIndexService; @Autowired private VectorSearchService vectorSearchService; /** * 查询知识库文档 * * @param query 查询关键词 * @return 查询结果 */ @Tool(description = "查询内部知识库文档,获取错误码定义、接口文档、排障步骤、配置说明等背景信息。" + "采用两阶段检索:L0 精确匹配关键词(< 10ms),L1 语义检索补充(200-500ms)。" + "IMPORTANT: 遇到错误码、接口名、配置项、排障问题时,优先使用此工具。" + "支持的查询场景:" + "1) 错误码定义 - 查询错误码的含义和处理方法,例如 'ERR_TIMEOUT'、'ERR_CONNECTION_REFUSED';" + "2) 接口文档 - 查询 API 接口定义、参数说明、返回格式,例如 'payment-gateway'、'/api/v1/orders';" + "3) 排障步骤 - 查询故障诊断流程、最佳实践,例如 '支付超时排查'、'数据库连接池配置';" + "4) 配置说明 - 查询系统配置、中间件参数,例如 'HikariCP'、'Redis 集群配置'。" + "参数 query: 查询关键词或描述") public LookupResult lookupKnowledge(String query) { // 生成请求ID用于追踪 String requestId = java.util.UUID.randomUUID().toString().substring(0, 8); long startTime = System.currentTimeMillis(); log.info("========================================"); log.info(">>> [工具调用] lookup_knowledge"); log.info(">>> 参数: query = \"{}\"", query); log.info(">>> RequestId: {}", requestId); log.info("----------------------------------------"); // Step 1: L0 精确匹配 long l0Start = System.currentTimeMillis(); List l0Matches = knowledgeIndexService.exactMatch(query); long l0Time = System.currentTimeMillis() - l0Start; log.info("[L0 精确匹配] 完成: matches={}, time={}ms", l0Matches.size(), l0Time); if (!l0Matches.isEmpty()) { log.info("[L0 精确匹配] 找到文档:"); for (int i = 0; i < Math.min(3, l0Matches.size()); i++) { KnowledgeEntry entry = l0Matches.get(i); log.info(" - [{}] 标题: {}, 路径: {}", i+1, entry.getTitle(), entry.getFilePath()); } } // Step 2: 判断是否高置信度(唯一匹配) boolean highConfidence = (l0Matches.size() == 1); log.info("[置信度判断] highConfidence={}, reason={}", highConfidence, highConfidence ? "唯一匹配" : "多个或零个匹配"); // Step 3: L1 条件调用 List l1Results = null; if (!highConfidence) { log.info("[L1 语义检索] L0非唯一匹配,触发L1语义检索..."); long l1Start = System.currentTimeMillis(); l1Results = vectorSearchService.searchSimilarDocuments(query, 3, null); long l1Time = System.currentTimeMillis() - l1Start; log.info("[L1 语义检索] 完成: matches={}, time={}ms", l1Results != null ? l1Results.size() : 0, l1Time); if (l1Results != null && !l1Results.isEmpty()) { log.info("[L1 语义检索] 找到文档:"); for (int i = 0; i < Math.min(3, l1Results.size()); i++) { VectorSearchService.SearchResult result = l1Results.get(i); log.info(" - [{}] 文档ID: {}, 相似度得分: {}", i+1, result.getId(), result.getScore()); } } } else { log.info("[L1 语义检索] L0唯一匹配,跳过L1检索"); } // Step 4: 组装结果 LookupResult result = buildResult(l0Matches, l1Results, highConfidence); // 记录完整结果 long totalTime = System.currentTimeMillis() - startTime; log.info("----------------------------------------"); log.info("<<< [工具返回] lookup_knowledge"); log.info("<<< 结果: found={}, matchType={}, confidence={}", result.isFound(), result.getPrimary() != null ? result.getPrimary().getMatchType() : "N/A", result.getPrimary() != null ? result.getPrimary().getConfidence() : "N/A"); log.info("<<< 总耗时: {}ms (L0={}ms, L1={}ms)", totalTime, l0Time, l1Results != null ? (totalTime - l0Time) : 0); if (result.isFound() && result.getPrimary() != null) { String content = result.getPrimary().getContent(); log.info("<<< 返回内容长度: {} 字符", content != null ? content.length() : 0); if (content != null && content.length() > 200) { log.info("<<< 内容预览: {}", content.substring(0, 200) + "..."); } } log.info("========================================"); return result; } /** * 组装查询结果 * * @param l0Matches L0 匹配结果 * @param l1Results L1 检索结果 * @param highConfidence 是否高置信度 * @return 组装后的结果 */ private LookupResult buildResult( List l0Matches, List l1Results, boolean highConfidence ) { LookupResult.LookupResultBuilder builder = LookupResult.builder(); // 构建 primary(L0 结果) PrimaryResult primary = null; if (l0Matches != null && !l0Matches.isEmpty()) { KnowledgeEntry first = l0Matches.get(0); String content = knowledgeIndexService.readDocument(first.getFilePath(), 2000); if (content != null) { primary = PrimaryResult.builder() .content(content) .source(first.getFilePath()) .matchType("exact_L0") .confidence(highConfidence ? "high" : "low") .availableSections(null) // MVP 返回 null .build(); log.debug("L0结果已构建: source={}, contentLength={}", first.getFilePath(), content.length()); } else { log.warn("L0匹配但文件读取失败: {}", first.getFilePath()); } } builder.primary(primary); // 构建 supplement(L1 结果) SupplementResult supplement = null; boolean hasL1 = l1Results != null && !l1Results.isEmpty(); if (hasL1) { VectorSearchService.SearchResult firstL1 = l1Results.get(0); supplement = SupplementResult.builder() .content(firstL1.getContent()) .source(firstL1.getMetadata()) .matchType("semantic_L1") .build(); log.debug("L1结果已构建: source={}, score={}", firstL1.getMetadata(), firstL1.getScore()); } builder.supplement(supplement); // 判断是否找到结果(primary 或 supplement 至少有一个) boolean found = (primary != null) || (supplement != null); builder.found(found); return builder.build(); } }