package com.vincent.rsf.server.ai.service.diagnosis; import com.fasterxml.jackson.databind.ObjectMapper; import com.vincent.rsf.server.ai.constant.AiMcpConstants; import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult; import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor; import com.vincent.rsf.server.ai.model.AiPromptContext; import com.vincent.rsf.server.ai.service.mcp.AiMcpPayloadMapper; import com.vincent.rsf.server.ai.service.mcp.AiMcpRegistryService; import com.vincent.rsf.server.system.entity.AiDiagnosticToolConfig; import com.vincent.rsf.server.system.service.AiDiagnosticToolConfigService; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @Service public class AiDiagnosticToolService { @Resource private ObjectMapper objectMapper; @Resource private AiDiagnosticToolConfigService aiDiagnosticToolConfigService; @Resource private AiMcpRegistryService aiMcpRegistryService; @Resource private AiMcpPayloadMapper aiMcpPayloadMapper; /** * 收集当前租户的所有内置诊断工具结果。 * 这是诊断场景的兜底路径,也用于在模型规划失败时强制聚合一轮内部数据。 */ public List collect(AiPromptContext context) { List output = new ArrayList<>(); for (AiMcpToolDescriptor descriptor : resolveInternalTools(context)) { try { AiDiagnosticToolResult result = aiMcpRegistryService.executeTool(context.getTenantId(), descriptor, context); if (result != null && result.getSummaryText() != null && !result.getSummaryText().trim().isEmpty()) { output.add(result); } } catch (Exception e) { output.add(new AiDiagnosticToolResult() .setToolCode(descriptor.getToolCode()) .setMountCode(descriptor.getMountCode() == null ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : descriptor.getMountCode()) .setMcpToolName(descriptor.getMcpToolName()) .setToolName(descriptor.getToolName()) .setSeverity("WARN") .setSummaryText("工具执行失败:" + e.getMessage())); } } return output; } /** * 将工具结果列表转换为可直接拼进系统 Prompt 的摘要文本。 */ public String buildPrompt(List results) { return buildPrompt(null, null, results); } /** * 根据当前场景补充工具级附加规则,并生成最终工具摘要 Prompt。 */ public String buildPrompt(Long tenantId, String sceneCode, List results) { if (results == null || results.isEmpty()) { return ""; } Map configMap = buildConfigMap(tenantId, sceneCode); List parts = new ArrayList<>(); parts.add("以下是诊断工具返回的实时摘要,请优先依据这些结果判断:"); for (AiDiagnosticToolResult item : results) { AiDiagnosticToolConfig config = configMap.get(item.getToolCode()); if (config != null && config.getToolPrompt() != null && !config.getToolPrompt().trim().isEmpty()) { parts.add("[" + safeValue(item.getToolName()) + "][指令] " + safeValue(config.getToolPrompt())); } parts.add("[" + safeValue(item.getToolName()) + "][" + safeValue(item.getSeverity()) + "] " + safeValue(item.getSummaryText())); } return String.join("\n", parts); } /** * 将工具结果序列化成 JSON,便于诊断记录与工具轨迹落库。 */ public String serializeResults(List results) { if (results == null || results.isEmpty()) { return "[]"; } try { return objectMapper.writeValueAsString(results); } catch (Exception e) { return "[]"; } } private String safeValue(String value) { return value == null ? "" : value; } /** * 按当前场景解析可以参与执行的内部工具列表,并按优先级排序。 */ private List resolveInternalTools(AiPromptContext context) { List output = new ArrayList<>(); if (context == null || context.getTenantId() == null) { return output; } for (AiMcpToolDescriptor descriptor : aiMcpRegistryService.listInternalTools(context.getTenantId())) { if (descriptor == null || !Integer.valueOf(1).equals(descriptor.getEnabledFlag())) { continue; } if (context.getSceneCode() != null && descriptor.getSceneCode() != null && !descriptor.getSceneCode().trim().isEmpty() && !context.getSceneCode().equals(descriptor.getSceneCode())) { continue; } output.add(descriptor); } output.sort(Comparator.comparing( item -> item.getPriority() == null ? Integer.MAX_VALUE : item.getPriority() )); return output; } /** * 为当前场景构建工具配置索引。 * 当工具用途为“聊天与诊断都可用”时,会允许在多个场景下复用同一条配置。 */ private Map buildConfigMap(Long tenantId, String sceneCode) { Map map = new LinkedHashMap<>(); if (tenantId == null || sceneCode == null || sceneCode.trim().isEmpty()) { return map; } List configs = aiDiagnosticToolConfigService.listTenantConfigs(tenantId); for (AiDiagnosticToolConfig config : emptyList(configs)) { if (!Integer.valueOf(1).equals(config.getStatus())) { continue; } String usageScope = aiMcpPayloadMapper.resolveUsageScope(config.getSceneCode(), config.getEnabledFlag(), config.getUsageScope()); if (AiMcpConstants.USAGE_SCOPE_DISABLED.equals(usageScope)) { continue; } if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equals(usageScope) && !sceneCode.equals(config.getSceneCode())) { continue; } AiDiagnosticToolConfig existed = map.get(config.getToolCode()); if (existed == null || preferConfig(config, existed, sceneCode)) { map.put(config.getToolCode(), config); } } return map; } /** * 冲突时优先选择与当前场景精确匹配的配置,其次比较优先级。 */ private boolean preferConfig(AiDiagnosticToolConfig candidate, AiDiagnosticToolConfig current, String sceneCode) { boolean candidateExact = sceneCode.equals(candidate.getSceneCode()); boolean currentExact = sceneCode.equals(current.getSceneCode()); if (candidateExact != currentExact) { return candidateExact; } Integer candidatePriority = candidate.getPriority() == null ? Integer.MAX_VALUE : candidate.getPriority(); Integer currentPriority = current.getPriority() == null ? Integer.MAX_VALUE : current.getPriority(); return candidatePriority < currentPriority; } private Collection emptyList(List list) { return list == null ? new ArrayList<>() : list; } }