| | |
| | | |
| | | 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 java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | |
| | | validateBuiltinCode(builtinCode); |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) { |
| | | List<ToolCallback> callbacks = new ArrayList<>(); |
| | | callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsStockTools))); |
| | | callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools))); |
| | | callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools))); |
| | | 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 Arrays.asList(ToolCallbacks.from(rsfWmsStockTools)); |
| | | return createValidatedCallbacks(rsfWmsStockTools, builtinCode); |
| | | } |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) { |
| | | return Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools)); |
| | | return createValidatedCallbacks(rsfWmsTaskTools, builtinCode); |
| | | } |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) { |
| | | return Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools)); |
| | | 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() { |
| | |
| | | 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(); |
| | | } |
| | | } |