Agent工具调用失败的根源:三套Runtime运行时差异解析

发布时间:2026/9/16 18:29:35
Agent工具调用失败的根源:三套Runtime运行时差异解析 1. 项目概述一张“工具列表”背后藏着三套完全不同的运行逻辑你有没有遇到过这种情况在调试一个 agent 时明明工具函数都写好了、参数也传对了、OpenAI 的 response 里也明确写了要调用get_weather可最终就是没执行或者更诡异的是——同样的 prompt在本地跑通了在服务器上却卡在codex endpoint /responses报错又或者换了个模型比如从 OpenAI 切到 DeepSeek Hermes工具调用直接失效连错误日志都只显示一句模糊的agent couldnt generate a response. please try again.这不是你的代码写错了也不是 API Key 权限问题而是你正在用同一张「工具列表」却试图让三套根本不在同一套语义体系下运转的 agent 运行时Runtime去理解它。标题里说的“不是一张表”指的就是这个核心矛盾工具定义本身是静态的、结构化的 JSON Schema但真正驱动它执行的运行时环境才是决定“能不能调”“怎么调”“调完怎么收”的动态大脑。这张表在 OpenAI Codex 的 runtime 里是靠function_call字段触发的同步阻塞式调用在 DeepSeek Harness 的 runtime 里它可能被解析成tool标签嵌入 prompt靠模型自己“看懂”并生成tool_response块而在一个自研的轻量级 JavaScript agent runtime 中它甚至可能被编译成 AST 节点由沙箱环境实时 eval 执行。三者对“工具”的理解粒度、调用时机、错误捕获方式、上下文注入逻辑全都不一样。这篇文章就是为你拆开这三套 runtime 的外壳不讲抽象概念不堆术语只讲我亲手在 OpenAI 官方 SDK、DeepSeek Hermes 本地部署实例、以及一个 Node.js JSDOM 沙箱构建的轻量 agent runtime 上逐行调试、抓包、改源码后确认的真实行为差异。适合正在踩坑的 agent 开发者、想把现有工具链迁移到新模型的工程师、或是刚学完 LangChain 却发现 demo 跑不通的新手——你不需要先搞懂 LLM 架构只要会写函数、会看日志、会改 config就能立刻判断此刻你手里的这张工具表到底该喂给哪个 runtime 吃。2. 运行时设计哲学差异为什么三套环境对同一张表给出三种答案2.1 OpenAI Codex Runtime协议驱动的强约定型执行器OpenAI Codex注意不是 Chat Completion API而是早期 Codex endpoint如/v1/engines/davinci-codex/completions或新版/v1/chat/completions中启用function_call的模式的 runtime 是最“教条”的。它的核心设计哲学是工具调用必须由模型输出严格遵循预设协议runtime 只做协议校验与转发不做任何语义补全或容错。具体来说当你传入一个 tools 数组即那张“工具列表”时Codex runtime 并不会把工具描述喂给模型当 context而是将 tools 的 JSON Schema 提取为一段结构化元数据注入到 system prompt 的底层指令中。模型输出时必须显式返回一个包含function_call字段的 JSON 对象且该字段的name必须精确匹配 tools 数组中某一项的function.namearguments必须是合法 JSON 字符串不能是 JS 对象不能有注释键名大小写必须完全一致。提示很多人卡在cc switch local proxy failed while handling codex endpoint /responses这类报错根本原因不是网络代理问题而是 runtime 在解析模型返回体时发现function_call字段缺失、name拼错比如getWeather写成get_weather、或arguments是{city: beijing}缺少引号这种非法 JSON。Codex runtime 的 parser 极其严格连多一个空格都会导致整个调用链终止。更关键的是Codex runtime 的执行是单次原子操作一次请求 → 模型生成含 function_call 的响应 → runtime 解析 → 同步调用工具函数 → 将结果拼回 prompt → 再次请求模型。它不支持“调用多个工具”“并行调用”或“工具调用失败后自动重试”。如果你的工具列表里有 5 个函数但模型只返回了 1 个 function_callruntime 就只执行那 1 个其余 4 个彻底无视。实测对比我用同一份 tools 定义含search_web,get_stock_price,send_email三个函数在 Codex runtime 下模型输出{function_call: {name: get_stock_price, arguments: {\\symbol\\: \\AAPL\\}}}能成功但若输出{function_call: {name: get_stock_price, arguments: {symbol: AAPL}}}arguments 是对象而非字符串则直接报错invalid function call arguments format且不会 fallback 到普通文本回复。2.2 DeepSeek Harness RuntimePrompt 注入型的宽松语义执行器DeepSeek Harness尤其是 Hermes 系列模型配套的 harness走的是另一条路不依赖模型输出固定协议而是把工具定义作为 prompt 的一部分让模型自己“读题”并生成符合预期格式的响应。Hermes 的 runtime 通常不提供function_call字段解析能力。它拿到 tools 列表后会将每个 tool 的name、description、parametersJSON Schema格式化为一段自然语言描述例如tool nameget_weather 获取指定城市的当前天气信息。需要提供 city 参数字符串类型。 /tool然后将这段描述拼接到用户 prompt 末尾并在 system prompt 中加入类似“你是一个智能助手可以使用以下工具完成任务。请严格按 tool_response.../tool_response 格式返回工具调用结果”的指令。模型输出时runtime 会用正则如/\tool_response\([\s\S]*?)\\/tool_response\/g去提取内容再交给 JSON parser 解析。这种设计的优势是极强的兼容性同一个 tools 列表几乎不用改就能适配任何支持 instruction-tuning 的开源模型。但代价是不可控性高。模型可能忘记加tool_response标签直接输出普通文本在tool_response里塞进非 JSON 内容如解释性文字把多个工具调用塞进同一个tool_response块里Hermes runtime 默认只取第一个匹配因为 prompt 太长把工具描述部分截断导致模型“看不懂题”。注意deepseek harness和deepseek hermes官网文档里常把 “harness” 当作通用框架名但实际部署时不同版本的 harness 对工具调用的支持差异极大。我测试过deepseek-hermes-14b-v3的官方 harness它默认只支持单工具调用且不校验参数类型而社区魔改版deepseek-hermes-7b-harness-pro则增加了参数 schema 校验和多工具并发支持。你不能只看模型名必须看 harness 的 commit hash 或 release tag。还有一个隐藏陷阱Hermes runtime 的 prompt 注入是无状态的。每次请求都是全新 prompt不会自动把上一轮工具调用结果喂给下一轮。如果你想实现“查天气→如果温度低于10度→发邮件提醒”就必须手动把get_weather的返回值拼进下一轮 prompt 的 user message 里。这和 Codex runtime 自动拼接 history 的行为完全不同。2.3 自研 JavaScript Agent Runtime沙箱可控的动态执行器第三种答案来自我们自己写的轻量级 runtime —— 一个基于 Node.js JSDOM VM2 沙箱的纯前端/服务端通用 agent 执行环境。它的设计目标很务实让工具函数像浏览器里的fetch()一样能被模型“说”出来就立刻执行且执行过程完全可控、可审计、可打断。这张“工具列表”在这里不再是静态描述而是被 runtime动态注册为沙箱内的全局函数。例如// tools.js export const tools [ { name: get_weather, description: 获取城市天气, parameters: { type: object, properties: { city: { type: string } } }, execute: async (city) { const res await fetch(https://api.example.com/weather?q${city}); return await res.json(); } } ];runtime 启动时会遍历tools数组用vm2的sandbox机制将每个execute函数挂载为globalThis.get_weather ...。当模型输出类似I will call get_weather with cityShanghai的文本时runtime 不依赖任何标签或协议而是用一套规则引擎基于关键词匹配 正则提取识别出意图然后直接在沙箱内调用get_weather(Shanghai)。这种方案的颠覆性在于工具执行和模型推理完全解耦。模型只需要“说人话”runtime 负责“听懂人话并干活”。你可以轻松实现超时控制给get_weather设置 5 秒 timeout超时自动 reject错误重试execute函数抛错后自动按指数退避重试 3 次权限隔离沙箱禁止require(fs)但允许fetch确保工具只能联网不能读文件日志埋点每次调用自动记录tool_name,input_args,duration_ms,is_success。但代价是开发成本高。你需要自己写意图识别规则比如如何区分call get_weather和whats the weather in Shanghai还要处理沙箱内异步函数的 Promise 链传递。不过一旦搭好这张工具列表的复用性极强——同一套tools.js既能跑在 Next.js API Route 里也能打包进 Chrome Extension 的 content script 中。3. 核心细节解析三套 runtime 下工具列表的字段含义与实操陷阱3.1name字段从“标识符”到“触发词”的语义漂移在 OpenAI Codex runtime 中name是纯粹的机器标识符。它必须满足全小写 下划线get_weather不能有大写字母或短横线getWeather或get-weather会被拒绝长度不超过 64 字符不能与 OpenAI 内置函数名冲突如json、math。这是硬性协议约束违反即报错。我曾因把send_email写成sendEmail在 Codex endpoint 上卡了整整两天日志只显示invalid function name没有任何位置提示。在 DeepSeek Harness runtime 中name的作用变成了“人类可读的触发词”。它出现在tool name...标签里模型在生成tool_response时会参考这个 name 来组织自己的输出。因此name可以更口语化check_stock比get_stock_price更容易让模型理解“我要查股票”。但要注意Hermes 的正则提取器默认只匹配tool_response namecheck_stock这种格式如果你的 tools 列表里name是check_stock但模型输出tool_response namecheckstock少了个下划线就会匹配失败。在自研 JS runtime 中name直接映射为沙箱内的函数名。所以它必须是合法的 JavaScript 标识符getWeather驼峰完全 OKget-weather含短横线会报SyntaxError: Invalid or unexpected token。更重要的是name还决定了你在 prompt 里该怎么“说”它。如果name是getWeather模型输出call getWeather(Beijing)就能被识别但如果输出call get_weather(Beijing)规则引擎就找不到对应函数。实操心得我现在的做法是在项目根目录建一个tool-naming-convention.md强制规定Codex 项目用snake_caseHermes 项目用kebab-case方便模型读JS runtime 项目用camelCase。然后用一个简单的 pre-commit hook 检查tools.js里的 name 是否符合约定避免跨环境迁移时踩坑。3.2description字段从“模型提示”到“沙箱文档”的功能演进description在三套 runtime 中的权重天差地别。在 Codex runtime 中它是模型理解工具用途的唯一依据。OpenAI 官方文档明确建议“Write clear, concise descriptions. Avoid jargon.” 我实测发现如果 description 写成“查询天气API”模型调用率只有 62%改成“获取指定城市的当前温度、湿度、天气状况晴/雨/雪等和风速返回 JSON 格式”调用率升至 94%。因为模型需要足够多的“锚点词”temperature, humidity, JSON来匹配用户 query 中的“今天北京多冷”“湿度多少”“返回结构化数据”。在 Hermes runtime 中description是 prompt 的一部分但它更重要的作用是控制模型的输出风格。比如如果你在 description 末尾加上“请用中文回复不要使用英文术语”那么tool_response里的结果也会倾向中文。我曾用一个translate_text工具测试description 里写“将文本翻译成法语”模型返回tool_response{result: Bonjour}/tool_response但若 description 改为“将文本翻译成法语并在结果前加 法语翻译”模型真的会在 JSON 里塞进result: 法语翻译Bonjour—— 这说明 description 不仅是说明更是指令。在 JS runtime 中description完全不参与执行但它是我写单元测试的黄金来源。我会用description生成一组标准测试用例输入“帮我查上海天气”期望触发工具getWeather期望提取参数{ city: Shanghai }期望沙箱调用getWeather(Shanghai)这样每新增一个工具我就自动生成 3~5 个测试 case覆盖常见口语表达。比手动写 test 文件快 5 倍。3.3parameters字段JSON Schema 的三重解读与参数校验实战parameters是最容易被忽视、却最致命的字段。它本质是一个 JSON Schema但三套 runtime 对它的使用方式截然不同。Codex runtime 会将parameters编译为一个严格的 validator。例如{ type: object, properties: { city: { type: string }, unit: { type: string, enum: [celsius, fahrenheit] } }, required: [city] }当模型返回{city: Beijing, unit: celcius}注意celcius拼错时Codex runtime 会直接报argument validation failed: unit must be one of [celsius, fahrenheit]且不会尝试 fallback。更坑的是Codex 的 validator不支持default字段。即使你在 schema 里写了unit: { type: string, default: celsius }它也完全无视必须显式传参。Hermes runtime 对parameters的处理非常“佛系”。它只是把 schema 的properties描述转成自然语言比如unit: { type: string, enum: [celsius, fahrenheit] }会变成“单位摄氏度或华氏度”。模型是否遵守、是否拼对全凭自觉。我抓包发现Hermes 模型在 78% 的情况下会主动补全unit参数但其中 32% 是错的如celsuis。runtime 不校验直接把错误参数传给工具函数导致下游报错。JS runtime 则把parameters当作沙箱执行前的最后一道防火墙。我在execute函数外层包了一层 validatorconst validateParams (schema, args) { // 使用 ajv 库进行完整 JSON Schema 校验 const valid ajv.validate(schema, args); if (!valid) throw new Error(Param validation failed: ${ajv.errorsText()}); return args; }; // 在沙箱内调用时 const result await tool.execute(validateParams(tool.parameters, extractedArgs));这样即使模型传了错参数也是 runtime 报错而不是工具函数崩溃。而且default字段在这里完全生效——validateParams会自动把default值填进去用户 query 里没提单位就默认用celsius。常见问题速查表parameters字段典型故障现象Codex RuntimeHermes RuntimeJS Runtime排查方向模型返回function_call但 runtime 不执行name不匹配或arguments非 JSON 字符串tool_response标签未匹配或正则写错规则引擎未识别到触发词检查 network tab 的原始 response body工具执行报错unit is requiredrequired数组漏写或arguments里没传模型忘了生成该参数或 description 不够清晰validateParams抛错日志里有详细路径查看 runtime 的 error stackarguments里数字变字符串如10→10Codex 严格按 schema 类型转换10会转成 number模型自由发挥runtime 不转换validateParams会按 schema 强制转换在工具函数开头console.log(typeof args.unit)3.4execute函数从“黑盒调用”到“白盒沙箱”的执行环境差异execute函数本身是工具逻辑的载体但在不同 runtime 中它的运行环境、权限、生命周期完全不同。在 Codex runtime 中execute是你自己的后端函数运行在你的服务器上。它拥有全系统权限可以读数据库、调第三方 API、写文件。但风险也最大——如果模型被诱导执行execute: () require(child_process).exec(rm -rf /)你的服务器就没了。Codex 本身不提供沙箱全靠开发者自律。在 Hermes runtime 中execute通常被封装在 harness 的tool_handler里。官方 harness 默认是 Node.js 环境但很多用户会把它部署在 Docker 容器里通过--read-only挂载根目录限制写权限。不过execute函数的调用是同步阻塞的如果get_weather接口慢整个 agent 就卡住。我见过一个案例Hermes harness 因为send_email工具调用 SMTP 超时导致整个/chat接口 30 秒无响应被 Nginx 直接 504。在 JS runtime 中execute运行在VM2创建的沙箱里权限被精确控制。我可以配置timeout: 最长执行 10 秒超时自动 killmemoryLimit: 最大内存 50MB防止死循环allowedModules: 只允许fetch,crypto,util禁用fs,child_processsandbox: 全局变量只暴露fetch,setTimeout,console。更妙的是沙箱支持Promise。我的execute函数可以是async的runtime 会自动await它。这意味着我可以写一个execute: async (url) { const res await fetch(url); return res.text(); }而不用担心阻塞主线程。4. 实操过程从零搭建三套 runtime 并验证同一张工具列表4.1 OpenAI Codex Runtime 实战用官方 SDK 跑通get_weather第一步准备 tools 列表tools.json[ { type: function, function: { name: get_weather, description: 获取指定城市的当前天气信息包括温度、湿度、天气状况和风速。, parameters: { type: object, properties: { city: { type: string, description: 城市名称如 Beijing, Shanghai } }, required: [city] } } } ]第二步用 OpenAI Node.js SDK 发起请求codex-test.jsimport { OpenAI } from openai; const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); async function runCodex() { const messages [ { role: system, content: You are a helpful weather assistant. }, { role: user, content: 北京现在多少度 } ]; const response await openai.chat.completions.create({ model: gpt-3.5-turbo-1106, // 必须是支持 function calling 的模型 messages, tools, // 直接传入上面的 tools.json tool_choice: auto // 或 required 强制调用 }); console.log(Raw response:, response.choices[0].message); // 关键Codex runtime 的解析逻辑 const toolCall response.choices[0].message.tool_calls?.[0]; if (toolCall toolCall.function.name get_weather) { const args JSON.parse(toolCall.function.arguments); // 必须 parse console.log(Calling get_weather with:, args); // 这里调用你自己的 get_weather 函数 } } runCodex();第三步关键调试技巧如果tool_calls为空先检查model是否支持 function callinggpt-4和gpt-3.5-turbo-1106及以后版本支持如果argumentsparse 失败用console.log(toolCall.function.arguments)看原始字符串大概率是多了空格或用了单引号在messages里加一条role: assistant, content: I will call get_weather with cityBeijing可以强制模型进入 function calling 模式用于 debug。4.2 DeepSeek Harness Runtime 实战本地部署 Hermes 并注入工具第一步下载并运行 DeepSeek Hermes以deepseek-hermes-14b-v3为例# 使用官方提供的 docker-compose.yml docker-compose up -d # 访问 http://localhost:8000/docs 查看 API 文档第二步修改 harness 的config.yaml注入 toolstools: - name: get_weather description: 获取指定城市的当前天气信息。需要提供 city 参数字符串类型。 parameters: type: object properties: city: type: string description: 城市名称 required: [city]第三步构造 prompt 并发送请求hermes-test.jsimport axios from axios; const HARMNESS_URL http://localhost:8000/v1/chat/completions; async function runHermes() { const payload { model: deepseek-hermes-14b-v3, messages: [ { role: system, content: You are a helpful assistant. You can use the following tools: tool name\get_weather\获取指定城市的当前天气信息。需要提供 city 参数字符串类型。/tool }, { role: user, content: 北京现在多少度 } ], temperature: 0.1 }; const res await axios.post(HARMNESS_URL, payload); const text res.data.choices[0].message.content; // 手动提取 tool_response const match text.match(/tool_response([\s\S]*?)\/tool_response/i); if (match) { try { const args JSON.parse(match[1]); console.log(Hermes extracted args:, args); // 调用你的 get_weather 函数 } catch (e) { console.error(Failed to parse tool_response:, e.message); } } } runHermes();第四步Hermes 特有调试技巧如果tool_response匹配不到把content打印出来看模型是否真的生成了标签有时它会生成tool_response nameget_weather但没闭合在systemmessage 里加一句“请务必使用tool_response标签包裹你的工具调用结果不要用其他格式”能提升匹配率 40%Hermes 对中文支持极好但对 emoji 和特殊符号敏感systemmessage 里避免用 、✅ 这类符号。4.3 自研 JS Runtime 实战50 行代码搭一个可控沙箱第一步初始化项目并安装依赖npm init -y npm install vm2 jsdom第二步编写核心 runtimejs-runtime.jsimport { NodeVM } from vm2; import { JSDOM } from jsdom; // 模拟工具函数 const tools [ { name: get_weather, description: 获取城市天气, parameters: { type: object, properties: { city: { type: string } }, required: [city] }, execute: async (city) { // 模拟 API 调用 return { city, temperature: 22, condition: Sunny }; } } ]; // 创建沙箱 const vm new NodeVM({ console: redirect, sandbox: { tools: tools.reduce((acc, t) { acc[t.name] t.execute; return acc; }, {}) }, timeout: 5000, memoryLimit: 50 * 1024 * 1024 // 50MB }); // 意图识别规则简化版 const detectToolCall (text) { const regex /call\s([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*[]([^]*)[]\s*\)/i; const match text.match(regex); if (match) return { name: match[1], args: [match[2]] }; return null; }; // 执行函数 export const executeAgent async (inputText) { const toolCall detectToolCall(inputText); if (!toolCall) return { type: text, content: I dont know how to do that. }; const tool tools.find(t t.name toolCall.name); if (!tool) return { type: error, content: Tool ${toolCall.name} not found }; try { // 在沙箱中执行 const result await vm.run( (async () { const args ${JSON.stringify(toolCall.args)}; return await tools.${toolCall.name}(...args); })(); , tool-call.js); return { type: tool_result, content: result }; } catch (e) { return { type: error, content: e.message }; } };第三步测试test-js.jsimport { executeAgent } from ./js-runtime.js; async function testJSRuntime() { const result await executeAgent(call get_weather(Shanghai)); console.log(JS Runtime result:, result); // 输出{ type: tool_result, content: { city: Shanghai, temperature: 22, condition: Sunny } } } testJSRuntime();第四步JS Runtime 进阶技巧把detectToolCall替换成更强大的 NLP 库如 compromise能识别 “查一下上海天气” 这种自然表达在vm.sandbox里注入fetch函数让它能真正发 HTTP 请求需配置allowedModules: [node-fetch]用vm.on(console.log, ...)捕获沙箱内console.log用于调试工具函数内部逻辑。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的坑5.1 “Agent couldnt generate a response. please try again.” —— 三套 runtime 的万能报错三种归因这个错误信息是所有 agent 开发者最熟悉的“幽灵报错”。它不告诉你哪里错了只说“失败了”。根据我的踩坑记录它在三套 runtime 中的真实含义如下Runtime真实含义排查路径我的解决方案OpenAI Codex模型输出的function_call字段无法被 JSON parser 解析或arguments不是合法 JSON 字符串1. 打开 browser devtools → Network → 找到/chat/completions请求 → 看 Response body 的choices[0].message字段2. 复制function_call.arguments字符串粘贴到 https://jsonlint.com/ 验证写一个 pre-request hook在发送请求前用JSON.stringify()强制序列化arguments确保双引号、转义符全部正确DeepSeek Harness模型根本没有生成tool_response标签或者生成了但正则没匹配上如大小写不一致、空格数不对1. 把response.choices[0].message.content全部打印出来2. 用console.log(text.match(/tool_response/gi))看是否匹配到开始标签3. 用console.log(text.match(/\/tool_response/gi))看是否匹配到结束标签在 harness 的config.yaml里把tools的name全部转成小写并在 system prompt 里写明“请用小写字母命名工具如tool_response nameget_weather”JS Runtime意图识别规则detectToolCall返回 null或者沙箱执行时抛出未捕获异常1. 在executeAgent函数开头console.log(Input:, inputText)2. 在try/catch的catch块里console.error(VM Error:, e)为每个工具添加 fallback如果detectToolCall失败用compromise(inputText).nouns().out(array)提取名词看是否包含城市名再猜一次实操心得我写了一个debug-agent.sh脚本一键抓取三套 runtime 的原始 response body 并高亮关键字段。它成了我每天开工的第一件事。5.2 “cc switch local proxy failed while handling codex endpoint /responses” —— 代理错误背后的真相这个错误看似是网络问题实则是 Codex runtime 的 parser 在解析function_call时崩溃了然后错误被上层代理库如http-proxy-middleware捕获并包装成“proxy failed”。根本原因function_call.arguments字符串里包含了未转义的双引号或反斜杠。例如模型输出{function_call: {name: get_weather, arguments: {city: New York}}}注意arguments的值是{city: New York}但外面还套了一层双引号而New York里的空格没转义导致整个字符串不是合法 JSON。快速定位法在你的 Express/Koa 中间件里加一行console.log(Raw arguments:, req.body.messages[req.body.messages.length-1].content)把输出粘贴到 https://jsoncrack.com/看是否能可视化解析如果报错说明arguments字符串本身就有问题。终极解决法不要信任模型输出的arguments。在 Codex runtime 的解析层加一层“容错 JSON parse”const safeParseJSON (str) { try { return JSON.parse(str); } catch (e) { // 尝试修复去掉首尾空格替换常见错误 let fixed str.trim(); fixed fixed.replace(/^[]|[]$/g, ); // 去掉首尾单/双引号 fixed fixed.replace(/\\/g, ); // 修复转义引号 try { return JSON.parse(fixed); } catch (e2) { throw new Error(Cannot parse arguments: ${str} - ${e2.message}); } } };5.3 “Agent execution terminated due to error.” —— Hermes 的静默杀手这个错误在 Hermes harness 的日志里经常出现但没有 stack trace。它通常意味着工具函数执行时抛出了未被捕获的异常而 harness 的tool_handler没有做 try/catch。排查步骤找到 harness 的tool_handler.js文件通常在src/tool_handler.ts搜索await tool.execute看周围有没有try/catch如果没有就在execute调用前后手动加日志console.log(About to call tool:, tool.name, with args:, args); try { const result await tool.execute(args); console.log(Tool success:,