| | |
| | | |
| | | 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.impl.mcp.BuiltinMcpToolCatalogProvider; |
| | | 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 java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | |
| | | private final RsfWmsStockTools rsfWmsStockTools; |
| | | private final RsfWmsTaskTools rsfWmsTaskTools; |
| | | private final RsfWmsBaseTools rsfWmsBaseTools; |
| | | private final BuiltinMcpToolCatalogProvider builtinMcpToolCatalogProvider; |
| | | |
| | | /** |
| | | * 校验内置 MCP 编码是否合法。 |
| | | * 当前版本只允许使用显式登记在注册表中的编码,未知编码直接拒绝, |
| | | * 这样可以确保“页面可选项”和“运行时可挂载项”始终一致。 |
| | | */ |
| | | @Override |
| | | public void validateBuiltinCode(String builtinCode) { |
| | | if (!StringUtils.hasText(builtinCode)) { |
| | | throw new CoolException("内置 MCP 编码不能为空"); |
| | | } |
| | | if (!supportedBuiltinCodes().contains(builtinCode)) { |
| | | if (!builtinMcpToolCatalogProvider.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(Arrays.asList(ToolCallbacks.from(rsfWmsStockTools))); |
| | | callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools))); |
| | | callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools))); |
| | | callbacks.addAll(createValidatedCallbacks(rsfWmsStockTools, builtinCode)); |
| | | callbacks.addAll(createValidatedCallbacks(rsfWmsTaskTools, builtinCode)); |
| | | callbacks.addAll(createValidatedCallbacks(rsfWmsBaseTools, builtinCode)); |
| | | return callbacks; |
| | | } |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)) { |
| | | return Arrays.asList(ToolCallbacks.from(rsfWmsStockTools)); |
| | | } |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) { |
| | | return Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools)); |
| | | } |
| | | if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) { |
| | | return Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools)); |
| | | } |
| | | throw new CoolException("不支持的内置 MCP 编码: " + builtinCode); |
| | | } |
| | | |
| | | 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 |
| | | ); |
| | | /** |
| | | * 返回某个内置编码下可预览的工具目录信息。 |
| | | * 该目录比运行时回调多了工具用途、查询边界和示例提问,供管理页展示。 |
| | | */ |
| | | @Override |
| | | public List<AiMcpToolPreviewDto> listBuiltinToolCatalog(String builtinCode) { |
| | | validateBuiltinCode(builtinCode); |
| | | return new ArrayList<>(builtinMcpToolCatalogProvider.getCatalog(builtinCode).values()); |
| | | } |
| | | |
| | | private List<ToolCallback> createValidatedCallbacks(Object toolBean, String builtinCode) { |
| | | /** |
| | | * 把 `@Tool` Bean 转成 Spring AI ToolCallback,并强制校验: |
| | | * 1. 工具名必须符合命名规范 |
| | | * 2. 每个工具都必须出现在治理目录里 |
| | | */ |
| | | List<ToolCallback> callbacks = Arrays.asList(ToolCallbacks.from(toolBean)); |
| | | Map<String, AiMcpToolPreviewDto> catalog = builtinMcpToolCatalogProvider.getCatalog(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; |
| | | } |
| | | |
| | | } |