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.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 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;
|
private final BuiltinMcpToolCatalogProvider builtinMcpToolCatalogProvider;
|
|
/**
|
* 校验内置 MCP 编码是否合法。
|
* 当前版本只允许使用显式登记在注册表中的编码,未知编码直接拒绝,
|
* 这样可以确保“页面可选项”和“运行时可挂载项”始终一致。
|
*/
|
@Override
|
public void validateBuiltinCode(String builtinCode) {
|
if (!StringUtils.hasText(builtinCode)) {
|
throw new CoolException("内置 MCP 编码不能为空");
|
}
|
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(createValidatedCallbacks(rsfWmsStockTools, builtinCode));
|
callbacks.addAll(createValidatedCallbacks(rsfWmsTaskTools, builtinCode));
|
callbacks.addAll(createValidatedCallbacks(rsfWmsBaseTools, builtinCode));
|
return callbacks;
|
}
|
throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
|
}
|
|
/**
|
* 返回某个内置编码下可预览的工具目录信息。
|
* 该目录比运行时回调多了工具用途、查询边界和示例提问,供管理页展示。
|
*/
|
@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;
|
}
|
|
}
|