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 javax.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<AiDiagnosticToolResult> collect(AiPromptContext context) {
|
List<AiDiagnosticToolResult> 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<AiDiagnosticToolResult> results) {
|
return buildPrompt(null, null, results);
|
}
|
|
/**
|
* 根据当前场景补充工具级附加规则,并生成最终工具摘要 Prompt。
|
*/
|
public String buildPrompt(Long tenantId, String sceneCode, List<AiDiagnosticToolResult> results) {
|
if (results == null || results.isEmpty()) {
|
return "";
|
}
|
Map<String, AiDiagnosticToolConfig> configMap = buildConfigMap(tenantId, sceneCode);
|
List<String> 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<AiDiagnosticToolResult> 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<AiMcpToolDescriptor> resolveInternalTools(AiPromptContext context) {
|
List<AiMcpToolDescriptor> 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<String, AiDiagnosticToolConfig> buildConfigMap(Long tenantId, String sceneCode) {
|
Map<String, AiDiagnosticToolConfig> map = new LinkedHashMap<>();
|
if (tenantId == null || sceneCode == null || sceneCode.trim().isEmpty()) {
|
return map;
|
}
|
List<AiDiagnosticToolConfig> 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 <T> Collection<T> emptyList(List<T> list) {
|
return list == null ? new ArrayList<>() : list;
|
}
|
|
}
|