#AI
zhou zhou
6 小时以前 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package com.vincent.rsf.server.ai.service.mcp;
 
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vincent.rsf.server.ai.constant.AiMcpConstants;
import com.vincent.rsf.server.ai.constant.AiSceneCode;
import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
import com.vincent.rsf.server.system.entity.AiMcpMount;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
@Component
public class AiMcpPayloadMapper {
 
    @Resource
    private ObjectMapper objectMapper;
 
    /**
     * 将远程 MCP tools/list 返回的单个工具定义转换成本系统统一的工具描述符。
     */
    public AiMcpToolDescriptor toExternalToolDescriptor(AiMcpMount mount, JsonNode item) {
        String remoteName = item.path("name").asText("");
        if (remoteName.trim().isEmpty()) {
            return null;
        }
        return new AiMcpToolDescriptor()
                .setMountCode(mount.getMountCode())
                .setMountName(mount.getName())
                .setToolCode(remoteName)
                .setMcpToolName(buildMcpToolName(mount.getMountCode(), remoteName))
                .setToolName(item.path("title").asText(remoteName))
                .setSceneCode(resolveSceneCode(mount.getUsageScope()))
                .setDescription(item.path("description").asText(""))
                .setEnabledFlag(mount.getEnabledFlag())
                .setPriority(999)
                .setToolPrompt(item.path("description").asText(""))
                .setUsageScope(normalizeUsageScope(mount.getUsageScope()))
                .setTransportType(mount.getTransportType())
                .setInputSchema(readInputSchema(item.path("inputSchema"), true));
    }
 
    /**
     * 将远程 MCP tools/call 返回的结果转换为系统内部统一的工具结果模型。
     */
    public AiDiagnosticToolResult toExternalToolResult(AiMcpMount mount, String toolName, JsonNode result) {
        boolean isError = result.path("isError").asBoolean(false);
        Map<String, Object> rawMeta = new LinkedHashMap<>();
        if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
            rawMeta.put("structuredContent", objectMapper.convertValue(result.get("structuredContent"), Map.class));
        }
        if (result.has("content") && !result.get("content").isNull()) {
            rawMeta.put("content", objectMapper.convertValue(result.get("content"), Object.class));
        }
        return new AiDiagnosticToolResult()
                .setToolCode(toolName)
                .setMountCode(mount.getMountCode())
                .setMcpToolName(buildMcpToolName(mount.getMountCode(), toolName))
                .setToolName(toolName)
                .setSeverity(isError ? "WARN" : "INFO")
                .setSummaryText(extractContentText(result))
                .setRawMeta(rawMeta);
    }
 
    /**
     * 将本地工具描述符转换为 MCP 协议 tools/list 所需的数据结构。
     */
    public Map<String, Object> toProtocolTool(AiMcpToolDescriptor descriptor) {
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("name", descriptor.getMcpToolName());
        item.put("title", descriptor.getToolName());
        item.put("description", descriptor.getDescription() == null || descriptor.getDescription().trim().isEmpty()
                ? descriptor.getToolPrompt()
                : descriptor.getDescription());
        item.put("inputSchema", descriptor.getInputSchema() == null || descriptor.getInputSchema().isEmpty()
                ? defaultInputSchema(false)
                : descriptor.getInputSchema());
        return item;
    }
 
    /**
     * 读取远程工具声明中的 inputSchema;缺省时回退到系统默认输入结构。
     */
    public Map<String, Object> readInputSchema(JsonNode schemaNode, boolean includeTenantId) {
        if (schemaNode == null || schemaNode.isMissingNode() || schemaNode.isNull()) {
            return defaultInputSchema(includeTenantId);
        }
        return objectMapper.convertValue(schemaNode, Map.class);
    }
 
    /**
     * 生成系统默认的工具输入 schema。
     */
    public Map<String, Object> defaultInputSchema(boolean includeTenantId) {
        Map<String, Object> schema = new LinkedHashMap<>();
        schema.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put("question", fieldSchema("string", "当前诊断问题或调用目的"));
        properties.put("sceneCode", fieldSchema("string", "诊断场景编码"));
        if (includeTenantId) {
            properties.put("tenantId", fieldSchema("integer", "租户ID"));
        }
        schema.put("properties", properties);
        return schema;
    }
 
    /**
     * 从 MCP tools/call 结果中提取可直接给模型看的文本摘要。
     */
    public String extractContentText(JsonNode result) {
        List<String> parts = new ArrayList<>();
        JsonNode contentNode = result.path("content");
        if (contentNode.isArray()) {
            for (Iterator<JsonNode> it = contentNode.elements(); it.hasNext(); ) {
                JsonNode item = it.next();
                if ("text".equals(item.path("type").asText(""))) {
                    parts.add(item.path("text").asText(""));
                } else if (item.isObject() || item.isArray()) {
                    parts.add(item.toString());
                } else {
                    parts.add(item.asText(""));
                }
            }
        }
        if (!parts.isEmpty()) {
            return String.join("\n", parts).trim();
        }
        if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
            return result.get("structuredContent").toString();
        }
        return result.toString();
    }
 
    /**
     * 构造系统统一的 MCP 工具全名。
     */
    public String buildMcpToolName(String mountCode, String toolCode) {
        return (mountCode == null ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : mountCode) + "_" + toolCode;
    }
 
    /**
     * 将用途预设转换成运行时 sceneCode。
     */
    public String resolveSceneCode(String usageScope) {
        if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equalsIgnoreCase(usageScope)) {
            return AiSceneCode.SYSTEM_DIAGNOSE;
        }
        return "";
    }
 
    /**
     * 标准化用途范围字段,统一成系统支持的三个枚举值。
     */
    public String normalizeUsageScope(String usageScope) {
        if (usageScope == null || usageScope.trim().isEmpty()) {
            return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
        }
        String normalized = usageScope.trim().toUpperCase();
        if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)
                || AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
            return normalized;
        }
        return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
    }
 
    /**
     * 根据 sceneCode、enabledFlag 和 usageScope 推断最终用途范围。
     */
    public String resolveUsageScope(String sceneCode, Integer enabledFlag, String usageScope) {
        String normalized = normalizeUsageScope(usageScope);
        if (AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
            return normalized;
        }
        if (enabledFlag != null && !Integer.valueOf(1).equals(enabledFlag)) {
            return AiMcpConstants.USAGE_SCOPE_DISABLED;
        }
        if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)) {
            return normalized;
        }
        if (sceneCode == null || sceneCode.trim().isEmpty()) {
            return AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE;
        }
        return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
    }
 
    /**
     * 构造单个字段的 schema 定义。
     */
    private Map<String, Object> fieldSchema(String type, String description) {
        Map<String, Object> field = new LinkedHashMap<>();
        field.put("type", type);
        field.put("description", description);
        return field;
    }
}