zhou zhou
13 小时以前 d5884d0974d17d96225a5d80e432de33a5ee6552
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
package com.vincent.rsf.server.ai.service.impl;
 
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto;
import com.vincent.rsf.server.ai.entity.AiMcpMount;
import com.vincent.rsf.server.ai.service.BuiltinMcpToolRegistry;
import com.vincent.rsf.server.ai.tool.RsfWmsBaseTools;
import com.vincent.rsf.server.ai.tool.RsfWmsStockTools;
import com.vincent.rsf.server.ai.tool.RsfWmsTaskTools;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
@Service
@RequiredArgsConstructor
public class BuiltinMcpToolRegistryImpl implements BuiltinMcpToolRegistry {
 
    private final RsfWmsStockTools rsfWmsStockTools;
    private final RsfWmsTaskTools rsfWmsTaskTools;
    private final RsfWmsBaseTools rsfWmsBaseTools;
 
    @Override
    public void validateBuiltinCode(String builtinCode) {
        if (!StringUtils.hasText(builtinCode)) {
            throw new CoolException("内置 MCP 编码不能为空");
        }
        if (!supportedBuiltinCodes().contains(builtinCode)) {
            throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
        }
    }
 
    @Override
    public List<ToolCallback> createToolCallbacks(AiMcpMount mount, Long userId) {
        String builtinCode = mount.getBuiltinCode();
        validateBuiltinCode(builtinCode);
        if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) {
            List<ToolCallback> callbacks = new ArrayList<>();
            callbacks.addAll(createValidatedCallbacks(rsfWmsStockTools, AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK));
            callbacks.addAll(createValidatedCallbacks(rsfWmsTaskTools, AiDefaults.MCP_BUILTIN_RSF_WMS_TASK));
            callbacks.addAll(createValidatedCallbacks(rsfWmsBaseTools, AiDefaults.MCP_BUILTIN_RSF_WMS_BASE));
            return callbacks;
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)) {
            return createValidatedCallbacks(rsfWmsStockTools, builtinCode);
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) {
            return createValidatedCallbacks(rsfWmsTaskTools, builtinCode);
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) {
            return createValidatedCallbacks(rsfWmsBaseTools, builtinCode);
        }
        throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
    }
 
    @Override
    public List<AiMcpToolPreviewDto> listBuiltinToolCatalog(String builtinCode) {
        validateBuiltinCode(builtinCode);
        if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) {
            List<AiMcpToolPreviewDto> catalog = new ArrayList<>();
            catalog.addAll(catalogByBuiltinCode(AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK).values());
            catalog.addAll(catalogByBuiltinCode(AiDefaults.MCP_BUILTIN_RSF_WMS_TASK).values());
            catalog.addAll(catalogByBuiltinCode(AiDefaults.MCP_BUILTIN_RSF_WMS_BASE).values());
            return catalog;
        }
        return new ArrayList<>(catalogByBuiltinCode(builtinCode).values());
    }
 
    private List<ToolCallback> createValidatedCallbacks(Object toolBean, String builtinCode) {
        List<ToolCallback> callbacks = Arrays.asList(ToolCallbacks.from(toolBean));
        Map<String, AiMcpToolPreviewDto> catalog = catalogByBuiltinCode(builtinCode);
        for (ToolCallback callback : callbacks) {
            if (callback == null || callback.getToolDefinition() == null) {
                continue;
            }
            String toolName = callback.getToolDefinition().name();
            if (!StringUtils.hasText(toolName) || !toolName.startsWith("rsf_query_")) {
                throw new CoolException("内置工具命名不符合规范,必须以 rsf_query_ 开头: " + toolName);
            }
            if (!catalog.containsKey(toolName)) {
                throw new CoolException("内置工具缺少治理目录配置: " + toolName);
            }
        }
        return callbacks;
    }
 
    private List<String> supportedBuiltinCodes() {
        return List.of(
                AiDefaults.MCP_BUILTIN_RSF_WMS,
                AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK,
                AiDefaults.MCP_BUILTIN_RSF_WMS_TASK,
                AiDefaults.MCP_BUILTIN_RSF_WMS_BASE
        );
    }
 
    private Map<String, AiMcpToolPreviewDto> catalogByBuiltinCode(String builtinCode) {
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)) {
            Map<String, AiMcpToolPreviewDto> catalog = new LinkedHashMap<>();
            catalog.put("rsf_query_available_inventory", buildCatalogItem(
                    "rsf_query_available_inventory",
                    "库存查询",
                    "查询指定物料当前可用于出库的库存明细。",
                    "必须提供物料编码或物料名称,并且最多返回 50 条库存记录。",
                    List.of("查询物料 MAT001 当前可出库库存", "按物料名称查询托盘库存明细")
            ));
            catalog.put("rsf_query_station_list", buildCatalogItem(
                    "rsf_query_station_list",
                    "库存查询",
                    "查询指定作业类型可用的设备站点。",
                    "必须提供站点类型列表,类型数量最多 10 个,最多返回 50 个站点。",
                    List.of("查询入库和出库作业可用站点", "列出 AGV_PICK 类型的作业站点")
            ));
            return catalog;
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) {
            Map<String, AiMcpToolPreviewDto> catalog = new LinkedHashMap<>();
            catalog.put("rsf_query_task_list", buildCatalogItem(
                    "rsf_query_task_list",
                    "任务查询",
                    "按任务号、状态、类型或站点条件查询任务列表。",
                    "至少提供一个过滤条件,最多返回 50 条任务记录,不支持全表扫描。",
                    List.of("查询最近 10 条状态为执行中的任务", "按任务号关键字查询任务列表")
            ));
            catalog.put("rsf_query_task_detail", buildCatalogItem(
                    "rsf_query_task_detail",
                    "任务查询",
                    "按任务 ID 或任务号查询单个任务详情。",
                    "必须提供任务 ID 或任务号之一,只返回单个任务。",
                    List.of("查询任务 12345 的详情", "根据任务号 TASK24001 查看执行明细")
            ));
            return catalog;
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) {
            Map<String, AiMcpToolPreviewDto> catalog = new LinkedHashMap<>();
            catalog.put("rsf_query_warehouses", buildCatalogItem(
                    "rsf_query_warehouses",
                    "基础资料",
                    "查询仓库基础信息。",
                    "至少提供仓库编码或名称,最多返回 50 条仓库记录。",
                    List.of("查询编码包含 WH 的仓库", "按仓库名称查询仓库地址")
            ));
            catalog.put("rsf_query_bas_stations", buildCatalogItem(
                    "rsf_query_bas_stations",
                    "基础资料",
                    "查询基础站点信息。",
                    "至少提供站点编号、站点名称或使用状态之一,最多返回 50 条站点记录。",
                    List.of("查询使用中的基础站点", "按站点编号查询基础站点")
            ));
            catalog.put("rsf_query_dict_data", buildCatalogItem(
                    "rsf_query_dict_data",
                    "基础资料",
                    "查询指定字典类型下的字典数据。",
                    "必须提供字典类型编码,最多返回 100 条字典记录。",
                    List.of("查询 task_status 字典", "按字典标签过滤 task_type 字典数据")
            ));
            return catalog;
        }
        throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
    }
 
    private AiMcpToolPreviewDto buildCatalogItem(String name, String toolGroup, String toolPurpose,
                                                 String queryBoundary, List<String> exampleQuestions) {
        return AiMcpToolPreviewDto.builder()
                .name(name)
                .toolGroup(toolGroup)
                .toolPurpose(toolPurpose)
                .queryBoundary(queryBoundary)
                .exampleQuestions(exampleQuestions)
                .build();
    }
}