#AI
zhou zhou
4 小时以前 51877df13075ad10ef51107f15bcd21f1661febe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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;
    }
 
}