From 9f02a0a221b261e84c642d500042b503bb2e0f31 Mon Sep 17 00:00:00 2001 From: matevip Date: Wed, 3 Jun 2026 08:38:48 +0800 Subject: [PATCH] fix(agent): unbreak DashScope tool calls, sharpen error class, rebalance plan triage (refs #246) --- .../binding/service/AgentBindingService.java | 2 +- .../agent/graph/NodeStreamingChatHelper.java | 31 +++++++++++++------ .../graph/plan/node/PlanGenerationNode.java | 18 ++++++----- .../vip/mate/tool/builtin/WebSearchTool.java | 7 ++++- ...V138__rename_search_tool_to_web_search.sql | 7 +++++ ...V138__rename_search_tool_to_web_search.sql | 7 +++++ .../src/main/resources/messages.properties | 10 +++--- .../src/main/resources/messages_en.properties | 10 +++--- 8 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 mateclaw-server/src/main/resources/db/migration/h2/V138__rename_search_tool_to_web_search.sql create mode 100644 mateclaw-server/src/main/resources/db/migration/mysql/V138__rename_search_tool_to_web_search.sql diff --git a/mateclaw-server/src/main/java/vip/mate/agent/binding/service/AgentBindingService.java b/mateclaw-server/src/main/java/vip/mate/agent/binding/service/AgentBindingService.java index d002e80b..834d9a62 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/binding/service/AgentBindingService.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/binding/service/AgentBindingService.java @@ -718,7 +718,7 @@ public class AgentBindingService implements AgentBindingResolver { // refuses ("Tool not found: search"). Observed 2026-05-01 on the // Code Reviewer agent — the model called search → got // not-found → gave up before ever reaching renderDocx. - "search", + "web_search", "browser_use", "read_file", "send_file", diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java index b19a2da0..cc3dd43c 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java @@ -412,18 +412,22 @@ public class NodeStreamingChatHelper { return ErrorType.BILLING; } // RFC-009 P3.2: MODEL_NOT_FOUND — provider rejects the requested model id. - // Includes DashScope's "[InvalidParameter] url error, please check url" - // (https://help.aliyun.com/zh/model-studio/error-code#error-url) which despite - // the wording is the provider rejecting an unknown/unsupported model id on - // the native protocol. Splitting this out from CLIENT_ERROR lets us hand off - // to the fallback chain instead of terminating — a different provider may - // recognize the model name (or have an equivalent default). + // DashScope signals an unknown/unsupported model id specifically as + // "[InvalidParameter] url error, please check url" + // (https://help.aliyun.com/zh/model-studio/error-code#error-url). Splitting this + // out from CLIENT_ERROR lets us hand off to the fallback chain instead of + // terminating — a different provider may recognize the model name (or have an + // equivalent default). + // + // Note: we match on the specific "url error" wording rather than a bare + // "InvalidParameter", because DashScope reuses the InvalidParameter code for + // request-shape problems that have nothing to do with the model id (an illegal + // tool name, or an unsupported parameter) — those are handled as CLIENT_ERROR + // below so a healthy model is not evicted from the failover pool. if (msg.contains("Model not exist") || msg.contains("model_not_found") || msg.contains("Model not found") || msg.contains("does not exist") - || msg.contains("[InvalidParameter]") - || msg.contains("InvalidParameter") || msg.contains("url error") // Volcano Ark: model exists but the user's account hasn't opened it, // or the id isn't valid for this region. Both are hard failures — @@ -432,9 +436,16 @@ public class NodeStreamingChatHelper { || msg.contains("InvalidEndpointOrModel")) { return ErrorType.MODEL_NOT_FOUND; } - // Client errors (400 Bad Request — unsupported format, invalid params, etc.) — NOT retryable + // Client errors (400 Bad Request — unsupported format, invalid params, etc.) — NOT retryable. + // DashScope's remaining "InvalidParameter" responses are request-shape bugs, e.g. a reserved + // or illegal tool name ("Tool names are not allowed to be [search]") or an unsupported + // parameter. These fail identically on every provider, so classifying them as CLIENT_ERROR + // (rather than MODEL_NOT_FOUND) keeps the model in the failover pool and surfaces the real + // cause instead of a misleading "model not available" message. if (msg.contains("400") || msg.contains("Bad Request") - || msg.contains("invalid_request_error") || msg.contains("unsupported")) { + || msg.contains("invalid_request_error") || msg.contains("unsupported") + || msg.contains("Tool names are not allowed") + || msg.contains("InvalidParameter")) { return ErrorType.CLIENT_ERROR; } // Server errors and transient TLS / socket-level network hiccups. diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/PlanGenerationNode.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/PlanGenerationNode.java index 104259e3..7d4ed73e 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/PlanGenerationNode.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/PlanGenerationNode.java @@ -70,26 +70,30 @@ public class PlanGenerationNode implements NodeAction { 硬性规则: 1. 只返回一个 JSON 对象;不允许 markdown 代码块、不允许任何 JSON 以外的文字。 2. 不要解释,不要寒暄,不要说"我来...""我先..."。 - 3. 不确定时优先选择"单步",而不是拆成多步。 + 3. 判断依据是"目标是否由多个明显独立的子任务/交付物组成",而不是难度高低: + 单个连贯动作不要拆,但目标确实分成多个部分时也不要硬压成一步。 三类分流: - (A) 直接回答 — 纯知识问答,模型凭自身知识即可回答,不需要任何工具、不需要读文件、不需要查询当前状态。 + (A) 直接回答 — 简单的纯知识问答:凭自身知识用一两段话即可答完,不需要任何工具、不需要读文件、 + 不需要查询当前状态,且目标本身不包含多个需要分别完成的子任务。 + (注意:成段的分析、对比、方案、规划、教程等通常不属于此类,应走 B 或 C。) 输出:{"needs_planning": false, "direct_answer": "<你的回答>"} - (B) 单步任务 — 需要工具,但本质是一个连贯动作(一次文件读取 / 一次搜索 / 一次命令 / 一次记忆读写 / 一次计算)。 - 执行器会在这一步内部迭代调用多次工具,你**不要**提前拆分。 + (B) 单步任务 — 本质是一个连贯动作(一次文件读取 / 一次搜索 / 一次命令 / 一次记忆读写 / 一次计算 / + 一段集中产出)。执行器会在这一步内部迭代调用多次工具,你**不要**提前拆分。 输出:{"needs_planning": true, "steps": ["<将用户目标复述为一句清晰可执行的指令>"]} - (C) 多步任务 — 用户目标包含 2 个及以上明显独立、必须先后完成的子任务(例如"先调研 A 再调研 B 然后对比"、 - "读配置、迁移数据、验证结果")。子任务之间如果可以合并,应当合并。 + (C) 多步任务 — 用户目标包含 2 个及以上明显独立、需要先后完成的子任务或交付物(例如"先调研 A 再调研 B + 然后对比"、"读配置、迁移数据、验证结果"、"分阶段制定计划"、"产出由若干独立部分组成的方案")。 + 这是规划型智能体的主路径——当目标确实由多个部分组成时就走这里。 输出:{"needs_planning": true, "steps": ["步骤1", "步骤2", ...]}(2 到 6 个步骤) 关键原则: - 单工具调用绝对不拆成多步。例:"读 A 文件并总结" 是单步(B),不是两步。 - 默认不要把 MEMORY.md / PROFILE.md / 技能文件读取当成独立步骤;仅当用户明确询问偏好、历史决策或长期约束时才加入。 - 每个步骤必须是可执行动作,不写"思考一下""确认一下"之类的空话。 - - 解析不出来时,视作(B) 单步;宁愿单步也不要无脑拆分。 + - 多部分、多阶段、需要逐步推进的目标走(C);真正单一原子动作走(B);只有简单一问一答才用(A)。 """; public PlanGenerationNode(ChatModel chatModel, PlanningService planningService, diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/WebSearchTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/WebSearchTool.java index 213634d7..95981389 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/WebSearchTool.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/WebSearchTool.java @@ -21,7 +21,12 @@ public class WebSearchTool { private final WebSearchService webSearchService; - @Tool(description = "Search the internet for latest information. Use when querying real-time news, latest data, or uncertain facts. " + // Tool name is pinned to "web_search" rather than the method-derived "search": + // DashScope's native protocol reserves the function name "search" and rejects the + // whole request with "InvalidParameter: Tool names are not allowed to be [search]", + // which breaks tool use for every qwen/DashScope-native model that has this tool bound. + @Tool(name = "web_search", + description = "Search the internet for latest information. Use when querying real-time news, latest data, or uncertain facts. " + "Supports optional freshness, language, count parameters.") public String search( @ToolParam(description = "Search keywords") String query, diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V138__rename_search_tool_to_web_search.sql b/mateclaw-server/src/main/resources/db/migration/h2/V138__rename_search_tool_to_web_search.sql new file mode 100644 index 00000000..835cf030 --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/h2/V138__rename_search_tool_to_web_search.sql @@ -0,0 +1,7 @@ +-- Rename the built-in web-search tool from "search" to "web_search". +-- DashScope's native protocol reserves the function name "search" and rejects any +-- request that declares a tool with that name ("InvalidParameter: Tool names are not +-- allowed to be [search]"), which broke tool use for every qwen/DashScope-native model +-- that had this tool bound. Migrate existing agent bindings to the new name so they +-- keep resolving after the tool was renamed in code. Idempotent. +UPDATE mate_agent_tool SET tool_name = 'web_search' WHERE tool_name = 'search'; diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V138__rename_search_tool_to_web_search.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V138__rename_search_tool_to_web_search.sql new file mode 100644 index 00000000..835cf030 --- /dev/null +++ b/mateclaw-server/src/main/resources/db/migration/mysql/V138__rename_search_tool_to_web_search.sql @@ -0,0 +1,7 @@ +-- Rename the built-in web-search tool from "search" to "web_search". +-- DashScope's native protocol reserves the function name "search" and rejects any +-- request that declares a tool with that name ("InvalidParameter: Tool names are not +-- allowed to be [search]"), which broke tool use for every qwen/DashScope-native model +-- that had this tool bound. Migrate existing agent bindings to the new name so they +-- keep resolving after the tool was renamed in code. Idempotent. +UPDATE mate_agent_tool SET tool_name = 'web_search' WHERE tool_name = 'search'; diff --git a/mateclaw-server/src/main/resources/messages.properties b/mateclaw-server/src/main/resources/messages.properties index d9d632dd..db083456 100644 --- a/mateclaw-server/src/main/resources/messages.properties +++ b/mateclaw-server/src/main/resources/messages.properties @@ -37,11 +37,11 @@ tool.execute_shell_command.desc=\u5728\u672c\u5730\u670d\u52a1\u5668\u4e0a\u6267 tool.execute_shell_command.param.command=\u8981\u6267\u884c\u7684 Shell \u547d\u4ee4 tool.execute_shell_command.param.timeoutSeconds=\u8d85\u65f6\u79d2\u6570\uff0c\u9ed8\u8ba4 60 \u79d2 -tool.search.desc=\u5728\u4e92\u8054\u7f51\u4e0a\u641c\u7d22\u6700\u65b0\u4fe1\u606f\u3002\u5f53\u9700\u8981\u67e5\u8be2\u5b9e\u65f6\u65b0\u95fb\u3001\u6700\u65b0\u6570\u636e\u6216\u4e0d\u786e\u5b9a\u7684\u4e8b\u5b9e\u65f6\u4f7f\u7528\u6b64\u5de5\u5177\u3002\u652f\u6301 freshness\u3001language\u3001count \u53ef\u9009\u53c2\u6570\u3002 -tool.search.param.query=\u641c\u7d22\u5173\u952e\u8bcd -tool.search.param.freshness=\u65f6\u95f4\u8303\u56f4\u8fc7\u6ee4: day (\u4eca\u5929), week (\u672c\u5468), month (\u672c\u6708), year (\u4eca\u5e74) -tool.search.param.language=\u8bed\u8a00\u504f\u597d: zh-CN (\u4e2d\u6587), en (\u82f1\u6587) -tool.search.param.count=\u6700\u5927\u7ed3\u679c\u6570\u91cf: 1-10, \u9ed8\u8ba4 5 +tool.web_search.desc=\u5728\u4e92\u8054\u7f51\u4e0a\u641c\u7d22\u6700\u65b0\u4fe1\u606f\u3002\u5f53\u9700\u8981\u67e5\u8be2\u5b9e\u65f6\u65b0\u95fb\u3001\u6700\u65b0\u6570\u636e\u6216\u4e0d\u786e\u5b9a\u7684\u4e8b\u5b9e\u65f6\u4f7f\u7528\u6b64\u5de5\u5177\u3002\u652f\u6301 freshness\u3001language\u3001count \u53ef\u9009\u53c2\u6570\u3002 +tool.web_search.param.query=\u641c\u7d22\u5173\u952e\u8bcd +tool.web_search.param.freshness=\u65f6\u95f4\u8303\u56f4\u8fc7\u6ee4: day (\u4eca\u5929), week (\u672c\u5468), month (\u672c\u6708), year (\u4eca\u5e74) +tool.web_search.param.language=\u8bed\u8a00\u504f\u597d: zh-CN (\u4e2d\u6587), en (\u82f1\u6587) +tool.web_search.param.count=\u6700\u5927\u7ed3\u679c\u6570\u91cf: 1-10, \u9ed8\u8ba4 5 tool.create_cron_job.desc=\u521b\u5efa\u5b9a\u65f6\u4efb\u52a1\u3002\u4efb\u52a1\u5c06\u5728\u6307\u5b9a\u65f6\u95f4\u81ea\u52a8\u8fd0\u884c\u5e76\u5411\u5f53\u524d Agent \u53d1\u9001\u89e6\u53d1\u6d88\u606f\u3002\u4f7f\u7528 5 \u5b57\u6bb5 cron \u8868\u8fbe\u5f0f\uff1a\u5206 \u65f6 \u65e5 \u6708 \u5468\u3002 tool.list_cron_jobs.desc=\u5217\u51fa\u6240\u6709\u5b9a\u65f6\u4efb\u52a1\uff0c\u5305\u542b\u540d\u79f0\u3001cron \u8868\u8fbe\u5f0f\u3001\u4e0b\u6b21\u8fd0\u884c\u65f6\u95f4\u3001\u542f\u7528\u72b6\u6001\u3002 diff --git a/mateclaw-server/src/main/resources/messages_en.properties b/mateclaw-server/src/main/resources/messages_en.properties index e8fa7aeb..823dd2f9 100644 --- a/mateclaw-server/src/main/resources/messages_en.properties +++ b/mateclaw-server/src/main/resources/messages_en.properties @@ -37,11 +37,11 @@ tool.execute_shell_command.desc=Execute a shell command on the local server. For tool.execute_shell_command.param.command=Shell command to execute tool.execute_shell_command.param.timeoutSeconds=Timeout in seconds, default 60 -tool.search.desc=Search the internet for latest information. Use when querying real-time news, latest data, or uncertain facts. Supports optional freshness, language, count parameters. -tool.search.param.query=Search keywords -tool.search.param.freshness=Time range filter: day (today), week (this week), month (this month), year (this year) -tool.search.param.language=Language preference: zh-CN (Chinese), en (English) -tool.search.param.count=Max results: 1-10, default 5 +tool.web_search.desc=Search the internet for latest information. Use when querying real-time news, latest data, or uncertain facts. Supports optional freshness, language, count parameters. +tool.web_search.param.query=Search keywords +tool.web_search.param.freshness=Time range filter: day (today), week (this week), month (this month), year (this year) +tool.web_search.param.language=Language preference: zh-CN (Chinese), en (English) +tool.web_search.param.count=Max results: 1-10, default 5 tool.create_cron_job.desc=Create a scheduled task (cron job). Runs automatically at specified time and sends trigger message to current agent. Use 5-field cron: minute hour day month weekday. tool.list_cron_jobs.desc=List all scheduled tasks with name, cron expression, next run time, and enabled status.