From ffbf67765d2ae447d62333eed85100a15685d781 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 12:27:59 +0800
Subject: [PATCH] #AI.内置工具治理

---
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx                                   |   29 ++++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/BuiltinToolGovernanceSupport.java       |   67 +++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java      |   19 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java                    |   39 ++--
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java                 |   10 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java |  122 ++++++++++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java                   |   34 ++--
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java          |    3 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java                    |   68 +++++----
 9 files changed, 315 insertions(+), 76 deletions(-)

diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
index 2a333e9..96edb0a 100644
--- a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
@@ -305,8 +305,18 @@
                                                     <Typography variant="body2" color="text.secondary">
                                                         {tool.description || "鏆傛棤鎻忚堪"}
                                                     </Typography>
+                                                    {!!tool.toolPurpose && (
+                                                        <Typography variant="caption" color="text.secondary" display="block" mt={0.5}>
+                                                            鐢ㄩ��: {tool.toolPurpose}
+                                                        </Typography>
+                                                    )}
                                                 </Box>
                                                 <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
+                                                    {!!tool.toolGroup && (
+                                                        <Typography variant="caption" color="text.secondary">
+                                                            {tool.toolGroup}
+                                                        </Typography>
+                                                    )}
                                                     <Typography variant="caption" color="text.secondary">
                                                         {schemaInfo.fields.length} 涓弬鏁�
                                                     </Typography>
@@ -317,8 +327,25 @@
                                             </Stack>
                                         </AccordionSummary>
                                         <AccordionDetails>
-                                            <Card variant="outlined" sx={{ borderRadius: 3 }}>
+                                                <Card variant="outlined" sx={{ borderRadius: 3 }}>
                                                 <CardContent>
+                                                    {!!tool.queryBoundary && (
+                                                        <Alert severity="info" sx={{ mb: 2 }}>
+                                                            鏌ヨ杈圭晫: {tool.queryBoundary}
+                                                        </Alert>
+                                                    )}
+                                                    {!!tool.exampleQuestions?.length && (
+                                                        <Alert severity="success" sx={{ mb: 2 }}>
+                                                            <Typography variant="body2" fontWeight={700} mb={0.5}>
+                                                                绀轰緥鎻愰棶
+                                                            </Typography>
+                                                            {tool.exampleQuestions.map((question) => (
+                                                                <Typography key={question} variant="body2">
+                                                                    {`- ${question}`}
+                                                                </Typography>
+                                                            ))}
+                                                        </Alert>
+                                                    )}
                                                     {!!schemaInfo.error && (
                                                         <Alert severity="warning" sx={{ mb: 2 }}>
                                                             {schemaInfo.error}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java
index 1d59c15..13fde18 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java
@@ -3,6 +3,8 @@
 import lombok.Builder;
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 @Builder
 public class AiMcpToolPreviewDto {
@@ -14,4 +16,12 @@
     private String inputSchema;
 
     private Boolean returnDirect;
+
+    private String toolGroup;
+
+    private String toolPurpose;
+
+    private String queryBoundary;
+
+    private List<String> exampleQuestions;
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java
index 8d26eeb..d785baf 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java
@@ -1,5 +1,6 @@
 package com.vincent.rsf.server.ai.service;
 
+import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto;
 import com.vincent.rsf.server.ai.entity.AiMcpMount;
 import org.springframework.ai.tool.ToolCallback;
 
@@ -10,4 +11,6 @@
     void validateBuiltinCode(String builtinCode);
 
     List<ToolCallback> createToolCallbacks(AiMcpMount mount, Long userId);
+
+    List<AiMcpToolPreviewDto> listBuiltinToolCatalog(String builtinCode);
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java
index 5bde192..87eb622 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java
@@ -72,7 +72,10 @@
         AiMcpMount mount = requireMount(mountId, tenantId);
         long startedAt = System.currentTimeMillis();
         try (McpMountRuntimeFactory.McpMountRuntime runtime = mcpMountRuntimeFactory.create(List.of(mount), userId)) {
-            List<AiMcpToolPreviewDto> tools = buildToolPreviewDtos(runtime.getToolCallbacks());
+            List<AiMcpToolPreviewDto> tools = buildToolPreviewDtos(runtime.getToolCallbacks(),
+                    AiDefaults.MCP_TRANSPORT_BUILTIN.equals(mount.getTransportType())
+                            ? builtinMcpToolRegistry.listBuiltinToolCatalog(mount.getBuiltinCode())
+                            : List.of());
             if (!runtime.getErrors().isEmpty()) {
                 String message = String.join("锛�", runtime.getErrors());
                 updateHealthStatus(mount.getId(), AiDefaults.MCP_HEALTH_UNHEALTHY, message, System.currentTimeMillis() - startedAt);
@@ -274,17 +277,29 @@
         }
     }
 
-    private List<AiMcpToolPreviewDto> buildToolPreviewDtos(ToolCallback[] callbacks) {
+    private List<AiMcpToolPreviewDto> buildToolPreviewDtos(ToolCallback[] callbacks, List<AiMcpToolPreviewDto> governedCatalog) {
         List<AiMcpToolPreviewDto> tools = new ArrayList<>();
+        Map<String, AiMcpToolPreviewDto> catalogMap = new java.util.LinkedHashMap<>();
+        for (AiMcpToolPreviewDto item : governedCatalog) {
+            if (item == null || !StringUtils.hasText(item.getName())) {
+                continue;
+            }
+            catalogMap.put(item.getName(), item);
+        }
         for (ToolCallback callback : callbacks) {
             if (callback == null || callback.getToolDefinition() == null) {
                 continue;
             }
+            AiMcpToolPreviewDto governedItem = catalogMap.get(callback.getToolDefinition().name());
             tools.add(AiMcpToolPreviewDto.builder()
                     .name(callback.getToolDefinition().name())
                     .description(callback.getToolDefinition().description())
                     .inputSchema(callback.getToolDefinition().inputSchema())
                     .returnDirect(callback.getToolMetadata() == null ? null : callback.getToolMetadata().returnDirect())
+                    .toolGroup(governedItem == null ? null : governedItem.getToolGroup())
+                    .toolPurpose(governedItem == null ? null : governedItem.getToolPurpose())
+                    .queryBoundary(governedItem == null ? null : governedItem.getQueryBoundary())
+                    .exampleQuestions(governedItem == null ? List.of() : governedItem.getExampleQuestions())
                     .build());
         }
         return tools;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
index 01e72e0..41f0d52 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
@@ -2,6 +2,7 @@
 
 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;
@@ -15,7 +16,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 @Service
 @RequiredArgsConstructor
@@ -41,21 +44,52 @@
         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() {
@@ -66,4 +100,80 @@
                 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();
+    }
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/BuiltinToolGovernanceSupport.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/BuiltinToolGovernanceSupport.java
new file mode 100644
index 0000000..06030dc
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/BuiltinToolGovernanceSupport.java
@@ -0,0 +1,67 @@
+package com.vincent.rsf.server.ai.tool;
+
+import com.vincent.rsf.framework.exception.CoolException;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class BuiltinToolGovernanceSupport {
+
+    private BuiltinToolGovernanceSupport() {
+    }
+
+    public static int normalizeLimit(Integer limit, int defaultValue, int maxValue) {
+        if (limit == null) {
+            return defaultValue;
+        }
+        if (limit < 1 || limit > maxValue) {
+            throw new CoolException("limit 蹇呴』鍦� 1 鍒� " + maxValue + " 涔嬮棿");
+        }
+        return limit;
+    }
+
+    public static void requireAnyFilter(String message, String... values) {
+        if (values == null || values.length == 0) {
+            throw new CoolException(message);
+        }
+        for (String value : values) {
+            if (StringUtils.hasText(value)) {
+                return;
+            }
+        }
+        throw new CoolException(message);
+    }
+
+    public static String sanitizeQueryText(String value, String fieldLabel, int maxLength) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        String normalized = value.trim();
+        if (normalized.length() > maxLength) {
+            throw new CoolException(fieldLabel + "闀垮害涓嶈兘瓒呰繃 " + maxLength);
+        }
+        return normalized;
+    }
+
+    public static List<String> sanitizeStringList(List<String> values, String fieldLabel, int maxSize, int maxItemLength) {
+        if (values == null || values.isEmpty()) {
+            throw new CoolException(fieldLabel + "涓嶈兘涓虹┖");
+        }
+        if (values.size() > maxSize) {
+            throw new CoolException(fieldLabel + "鏁伴噺涓嶈兘瓒呰繃 " + maxSize);
+        }
+        List<String> result = new ArrayList<>();
+        for (String value : values) {
+            String normalized = sanitizeQueryText(value, fieldLabel + "椤�", maxItemLength);
+            if (!StringUtils.hasText(normalized)) {
+                continue;
+            }
+            result.add(normalized);
+        }
+        if (result.isEmpty()) {
+            throw new CoolException(fieldLabel + "涓嶈兘涓虹┖");
+        }
+        return result;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java
index 0a8350d..b0d2e1c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java
@@ -27,18 +27,21 @@
     private final BasStationService basStationService;
     private final DictDataService dictDataService;
 
-    @Tool(name = "rsf_query_warehouses", description = "鎸変粨搴撶紪鐮佹垨鍚嶇О鏌ヨ浠撳簱鍩虹淇℃伅銆�")
+    @Tool(name = "rsf_query_warehouses", description = "鍙鏌ヨ宸ュ叿銆傛寜浠撳簱缂栫爜鎴栧悕绉版煡璇粨搴撳熀纭�淇℃伅銆�")
     public List<Map<String, Object>> queryWarehouses(
             @ToolParam(description = "浠撳簱缂栫爜锛屽彲閫�") String code,
             @ToolParam(description = "浠撳簱鍚嶇О锛屽彲閫�") String name,
             @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        String normalizedCode = BuiltinToolGovernanceSupport.sanitizeQueryText(code, "浠撳簱缂栫爜", 64);
+        String normalizedName = BuiltinToolGovernanceSupport.sanitizeQueryText(name, "浠撳簱鍚嶇О", 100);
+        BuiltinToolGovernanceSupport.requireAnyFilter("浠撳簱鏌ヨ鑷冲皯闇�瑕佹彁渚涗粨搴撶紪鐮佹垨鍚嶇О", normalizedCode, normalizedName);
         LambdaQueryWrapper<Warehouse> queryWrapper = new LambdaQueryWrapper<>();
-        int finalLimit = normalizeLimit(limit, 10, 50);
-        if (StringUtils.hasText(code)) {
-            queryWrapper.like(Warehouse::getCode, code);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(normalizedCode)) {
+            queryWrapper.like(Warehouse::getCode, normalizedCode);
         }
-        if (StringUtils.hasText(name)) {
-            queryWrapper.like(Warehouse::getName, name);
+        if (StringUtils.hasText(normalizedName)) {
+            queryWrapper.like(Warehouse::getName, normalizedName);
         }
         queryWrapper.orderByAsc(Warehouse::getCode).last("LIMIT " + finalLimit);
         List<Warehouse> warehouses = warehouseService.list(queryWrapper);
@@ -58,22 +61,27 @@
         return result;
     }
 
-    @Tool(name = "rsf_query_bas_stations", description = "鎸夌珯鐐圭紪鍙枫�佺珯鐐瑰悕绉版垨浣跨敤鐘舵�佹煡璇㈠熀纭�绔欑偣銆�")
+    @Tool(name = "rsf_query_bas_stations", description = "鍙鏌ヨ宸ュ叿銆傛寜绔欑偣缂栧彿銆佺珯鐐瑰悕绉版垨浣跨敤鐘舵�佹煡璇㈠熀纭�绔欑偣銆�")
     public List<Map<String, Object>> queryBasStations(
-            @ToolParam(description = "绔欑偣缂栧彿锛屽彲閫�") String stationName,
-            @ToolParam(description = "绔欑偣鍚嶇О锛屽彲閫�") String stationId,
+            @ToolParam(description = "绔欑偣鍚嶇О锛屽彲閫�") String stationName,
+            @ToolParam(description = "绔欑偣缂栧彿锛屽彲閫�") String stationId,
             @ToolParam(description = "浣跨敤鐘舵�侊紝鍙��") String useStatus,
             @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        String normalizedStationName = BuiltinToolGovernanceSupport.sanitizeQueryText(stationName, "绔欑偣鍚嶇О", 100);
+        String normalizedStationId = BuiltinToolGovernanceSupport.sanitizeQueryText(stationId, "绔欑偣缂栧彿", 64);
+        String normalizedUseStatus = BuiltinToolGovernanceSupport.sanitizeQueryText(useStatus, "浣跨敤鐘舵��", 32);
+        BuiltinToolGovernanceSupport.requireAnyFilter("鍩虹绔欑偣鏌ヨ鑷冲皯闇�瑕佹彁渚涚珯鐐瑰悕绉般�佺珯鐐圭紪鍙锋垨浣跨敤鐘舵��",
+                normalizedStationName, normalizedStationId, normalizedUseStatus);
         LambdaQueryWrapper<BasStation> queryWrapper = new LambdaQueryWrapper<>();
-        int finalLimit = normalizeLimit(limit, 10, 50);
-        if (StringUtils.hasText(stationName)) {
-            queryWrapper.like(BasStation::getStationName, stationName);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(normalizedStationName)) {
+            queryWrapper.like(BasStation::getStationName, normalizedStationName);
         }
-        if (StringUtils.hasText(stationId)) {
-            queryWrapper.like(BasStation::getStationId, stationId);
+        if (StringUtils.hasText(normalizedStationId)) {
+            queryWrapper.like(BasStation::getStationId, normalizedStationId);
         }
-        if (StringUtils.hasText(useStatus)) {
-            queryWrapper.eq(BasStation::getUseStatus, useStatus);
+        if (StringUtils.hasText(normalizedUseStatus)) {
+            queryWrapper.eq(BasStation::getUseStatus, normalizedUseStatus);
         }
         queryWrapper.orderByAsc(BasStation::getStationName).last("LIMIT " + finalLimit);
         List<BasStation> stations = basStationService.list(queryWrapper);
@@ -98,23 +106,26 @@
         return result;
     }
 
-    @Tool(name = "rsf_query_dict_data", description = "鏍规嵁瀛楀吀绫诲瀷缂栫爜鏌ヨ瀛楀吀鏁版嵁锛屽彲鎸夊�兼垨鏍囩杩涗竴姝ヨ繃婊ゃ��")
+    @Tool(name = "rsf_query_dict_data", description = "鍙鏌ヨ宸ュ叿銆傛牴鎹瓧鍏哥被鍨嬬紪鐮佹煡璇㈠瓧鍏告暟鎹紝鍙寜鍊兼垨鏍囩杩涗竴姝ヨ繃婊ゃ��")
     public List<Map<String, Object>> queryDictData(
             @ToolParam(required = true, description = "瀛楀吀绫诲瀷缂栫爜") String dictTypeCode,
             @ToolParam(description = "瀛楀吀鍊硷紝鍙��") String value,
             @ToolParam(description = "瀛楀吀鏍囩锛屽彲閫�") String label,
             @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 20锛屾渶澶� 100") Integer limit) {
-        if (!StringUtils.hasText(dictTypeCode)) {
+        String normalizedDictTypeCode = BuiltinToolGovernanceSupport.sanitizeQueryText(dictTypeCode, "瀛楀吀绫诲瀷缂栫爜", 64);
+        String normalizedValue = BuiltinToolGovernanceSupport.sanitizeQueryText(value, "瀛楀吀鍊�", 64);
+        String normalizedLabel = BuiltinToolGovernanceSupport.sanitizeQueryText(label, "瀛楀吀鏍囩", 100);
+        if (!StringUtils.hasText(normalizedDictTypeCode)) {
             throw new CoolException("瀛楀吀绫诲瀷缂栫爜涓嶈兘涓虹┖");
         }
-        int finalLimit = normalizeLimit(limit, 20, 100);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 20, 100);
         LambdaQueryWrapper<DictData> queryWrapper = new LambdaQueryWrapper<DictData>()
-                .eq(DictData::getDictTypeCode, dictTypeCode);
-        if (StringUtils.hasText(value)) {
-            queryWrapper.like(DictData::getValue, value);
+                .eq(DictData::getDictTypeCode, normalizedDictTypeCode);
+        if (StringUtils.hasText(normalizedValue)) {
+            queryWrapper.like(DictData::getValue, normalizedValue);
         }
-        if (StringUtils.hasText(label)) {
-            queryWrapper.like(DictData::getLabel, label);
+        if (StringUtils.hasText(normalizedLabel)) {
+            queryWrapper.like(DictData::getLabel, normalizedLabel);
         }
         queryWrapper.orderByAsc(DictData::getSort).last("LIMIT " + finalLimit);
         List<DictData> dictDataList = dictDataService.list(queryWrapper);
@@ -135,13 +146,4 @@
         return result;
     }
 
-    private int normalizeLimit(Integer limit, int defaultValue, int maxValue) {
-        if (limit == null) {
-            return defaultValue;
-        }
-        if (limit < 1 || limit > maxValue) {
-            throw new CoolException("limit 蹇呴』鍦� 1 鍒� " + maxValue + " 涔嬮棿");
-        }
-        return limit;
-    }
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java
index 78994a2..7fcce30 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java
@@ -27,23 +27,26 @@
     private final LocItemService locItemService;
     private final DeviceSiteService deviceSiteService;
 
-    @Tool(name = "rsf_query_available_inventory", description = "鏍规嵁鐗╂枡缂栫爜鎴栫墿鏂欏悕绉版煡璇㈠綋鍓嶅湪搴撲笖鍙敤浜庡嚭搴撶殑搴撳瓨鏄庣粏銆�")
+    @Tool(name = "rsf_query_available_inventory", description = "鍙鏌ヨ宸ュ叿銆傛牴鎹墿鏂欑紪鐮佹垨鐗╂枡鍚嶇О鏌ヨ褰撳墠鍦ㄥ簱涓斿彲鐢ㄤ簬鍑哄簱鐨勫簱瀛樻槑缁嗐��")
     public List<Map<String, Object>> queryAvailableInventory(
             @ToolParam(description = "鐗╂枡缂栫爜锛屼紭鍏堜娇鐢�") String matnr,
-            @ToolParam(description = "鐗╂枡鍚嶇О锛屽綋娌℃湁鐗╂枡缂栫爜鏃朵娇鐢�") String maktx) {
-        if (!StringUtils.hasText(matnr) && !StringUtils.hasText(maktx)) {
-            throw new CoolException("鐗╂枡缂栫爜鎴栫墿鏂欏悕绉拌嚦灏戦渶瑕佹彁渚涗竴涓�");
-        }
+            @ToolParam(description = "鐗╂枡鍚嶇О锛屽綋娌℃湁鐗╂枡缂栫爜鏃朵娇鐢�") String maktx,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        String normalizedMatnr = BuiltinToolGovernanceSupport.sanitizeQueryText(matnr, "鐗╂枡缂栫爜", 64);
+        String normalizedMaktx = BuiltinToolGovernanceSupport.sanitizeQueryText(maktx, "鐗╂枡鍚嶇О", 100);
+        BuiltinToolGovernanceSupport.requireAnyFilter("鐗╂枡缂栫爜鎴栫墿鏂欏悕绉拌嚦灏戦渶瑕佹彁渚涗竴涓�", normalizedMatnr, normalizedMaktx);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50);
         LambdaQueryWrapper<LocItem> queryWrapper = new LambdaQueryWrapper<>();
-        if (StringUtils.hasText(matnr)) {
-            queryWrapper.eq(LocItem::getMatnrCode, matnr);
+        if (StringUtils.hasText(normalizedMatnr)) {
+            queryWrapper.eq(LocItem::getMatnrCode, normalizedMatnr);
         } else {
-            queryWrapper.eq(LocItem::getMaktx, maktx);
+            queryWrapper.like(LocItem::getMaktx, normalizedMaktx);
         }
         queryWrapper.apply(
                 "EXISTS (SELECT 1 FROM man_loc ml WHERE ml.use_status = {0} AND ml.id = man_loc_item.loc_id)",
                 LocStsType.LOC_STS_TYPE_F.type
         );
+        queryWrapper.orderByDesc(LocItem::getId).last("LIMIT " + finalLimit);
         List<LocItem> locItems = locItemService.list(queryWrapper);
         List<Map<String, Object>> result = new ArrayList<>();
         for (LocItem locItem : locItems) {
@@ -69,14 +72,17 @@
         return result;
     }
 
-    @Tool(name = "rsf_query_station_list", description = "鏍规嵁浣滀笟绫诲瀷鍒楄〃鏌ヨ鍙敤绔欑偣锛岃繑鍥炵珯鐐圭紪鍙枫�佸悕绉般�佺洰鏍囦綅缃拰鐘舵�佺瓑淇℃伅銆�")
+    @Tool(name = "rsf_query_station_list", description = "鍙鏌ヨ宸ュ叿銆傛牴鎹綔涓氱被鍨嬪垪琛ㄦ煡璇㈠彲鐢ㄧ珯鐐癸紝杩斿洖绔欑偣缂栧彿銆佸悕绉般�佺洰鏍囦綅缃拰鐘舵�佺瓑淇℃伅銆�")
     public List<Map<String, Object>> queryStationList(
-            @ToolParam(required = true, description = "浣滀笟绫诲瀷鍒楄〃") List<String> types) {
-        if (types == null || types.isEmpty()) {
-            throw new CoolException("绔欑偣绫诲瀷鍒楄〃涓嶈兘涓虹┖");
-        }
+            @ToolParam(required = true, description = "浣滀笟绫诲瀷鍒楄〃") List<String> types,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 20锛屾渶澶� 50") Integer limit) {
+        List<String> normalizedTypes = BuiltinToolGovernanceSupport.sanitizeStringList(types, "绔欑偣绫诲瀷鍒楄〃", 10, 32);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 20, 50);
         List<DeviceSite> sites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>()
-                .in(DeviceSite::getType, types));
+                .in(DeviceSite::getType, normalizedTypes)
+                .orderByAsc(DeviceSite::getType)
+                .orderByAsc(DeviceSite::getSite)
+                .last("LIMIT " + finalLimit));
         List<Map<String, Object>> result = new ArrayList<>();
         for (DeviceSite site : sites) {
             Map<String, Object> item = new LinkedHashMap<>();
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
index ba6d622..0aecbf7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
@@ -21,7 +21,7 @@
 
     private final TaskService taskService;
 
-    @Tool(name = "rsf_query_task_list", description = "鎸変换鍔″彿銆佺姸鎬併�佷换鍔$被鍨嬨�佹簮绔欑偣銆佺洰鏍囩珯鐐圭瓑鏉′欢鏌ヨ浠诲姟鍒楄〃銆�")
+    @Tool(name = "rsf_query_task_list", description = "鍙鏌ヨ宸ュ叿銆傛寜浠诲姟鍙枫�佺姸鎬併�佷换鍔$被鍨嬨�佹簮绔欑偣銆佺洰鏍囩珯鐐圭瓑鏉′欢鏌ヨ浠诲姟鍒楄〃銆�")
     public List<Map<String, Object>> queryTaskList(
             @ToolParam(description = "浠诲姟鍙凤紝鍙ā绯婃煡璇�") String taskCode,
             @ToolParam(description = "浠诲姟鐘舵�侊紝鍙��") Integer taskStatus,
@@ -29,10 +29,17 @@
             @ToolParam(description = "婧愮珯鐐癸紝鍙��") String orgSite,
             @ToolParam(description = "鐩爣绔欑偣锛屽彲閫�") String targSite,
             @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        String normalizedTaskCode = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "浠诲姟鍙�", 64);
+        String normalizedOrgSite = BuiltinToolGovernanceSupport.sanitizeQueryText(orgSite, "婧愮珯鐐�", 64);
+        String normalizedTargSite = BuiltinToolGovernanceSupport.sanitizeQueryText(targSite, "鐩爣绔欑偣", 64);
+        BuiltinToolGovernanceSupport.requireAnyFilter("浠诲姟鍒楄〃鏌ヨ鑷冲皯闇�瑕佹彁渚涗竴涓繃婊ゆ潯浠�",
+                normalizedTaskCode, normalizedOrgSite, normalizedTargSite,
+                taskStatus == null ? null : String.valueOf(taskStatus),
+                taskType == null ? null : String.valueOf(taskType));
         LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
-        int finalLimit = normalizeLimit(limit, 10, 50);
-        if (StringUtils.hasText(taskCode)) {
-            queryWrapper.like(Task::getTaskCode, taskCode);
+        int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(normalizedTaskCode)) {
+            queryWrapper.like(Task::getTaskCode, normalizedTaskCode);
         }
         if (taskStatus != null) {
             queryWrapper.eq(Task::getTaskStatus, taskStatus);
@@ -40,11 +47,11 @@
         if (taskType != null) {
             queryWrapper.eq(Task::getTaskType, taskType);
         }
-        if (StringUtils.hasText(orgSite)) {
-            queryWrapper.eq(Task::getOrgSite, orgSite);
+        if (StringUtils.hasText(normalizedOrgSite)) {
+            queryWrapper.eq(Task::getOrgSite, normalizedOrgSite);
         }
-        if (StringUtils.hasText(targSite)) {
-            queryWrapper.eq(Task::getTargSite, targSite);
+        if (StringUtils.hasText(normalizedTargSite)) {
+            queryWrapper.eq(Task::getTargSite, normalizedTargSite);
         }
         queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit);
         List<Task> tasks = taskService.list(queryWrapper);
@@ -55,18 +62,19 @@
         return result;
     }
 
-    @Tool(name = "rsf_query_task_detail", description = "鏍规嵁浠诲姟 ID 鎴栦换鍔″彿鏌ヨ浠诲姟璇︽儏銆�")
+    @Tool(name = "rsf_query_task_detail", description = "鍙鏌ヨ宸ュ叿銆傛牴鎹换鍔� ID 鎴栦换鍔″彿鏌ヨ浠诲姟璇︽儏銆�")
     public Map<String, Object> queryTaskDetail(
             @ToolParam(description = "浠诲姟 ID") Long taskId,
             @ToolParam(description = "浠诲姟鍙�") String taskCode) {
-        if (taskId == null && !StringUtils.hasText(taskCode)) {
+        String normalizedTaskCode = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "浠诲姟鍙�", 64);
+        if (taskId == null && !StringUtils.hasText(normalizedTaskCode)) {
             throw new CoolException("浠诲姟 ID 鍜屼换鍔″彿鑷冲皯闇�瑕佹彁渚涗竴涓�");
         }
         Task task;
         if (taskId != null) {
             task = taskService.getById(taskId);
         } else {
-            task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, taskCode));
+            task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, normalizedTaskCode));
         }
         if (task == null) {
             throw new CoolException("鏈煡璇㈠埌浠诲姟");
@@ -111,13 +119,4 @@
         return item;
     }
 
-    private int normalizeLimit(Integer limit, int defaultValue, int maxValue) {
-        if (limit == null) {
-            return defaultValue;
-        }
-        if (limit < 1 || limit > maxValue) {
-            throw new CoolException("limit 蹇呴』鍦� 1 鍒� " + maxValue + " 涔嬮棿");
-        }
-        return limit;
-    }
 }

--
Gitblit v1.9.1