
1. 从一次“工具调用失败”说起ReAct Agent 到底难在哪你可能已经用大模型写过不少对话应用但只要尝试让它真正“动手做事”——查数据库、调接口、读文件、发消息——就会立刻撞上一堵墙。用户说“帮我看看昨天订单里退款率最高的商品”模型回你一段听起来很合理但完全编造的分析你给它挂上几个函数它又开始在参数里塞不存在的字段或者干脆把函数名拼错。这不是模型不够聪明而是 ReAct Agent 的工程链路里有三个层次的东西被混在一起谈了Function Calling、MCP、Skills。先把这三个词用一句话拆开。Function Calling 是模型输出结构化工具调用的能力它决定了“模型能不能稳定地告诉你要调哪个函数、传什么参数”。MCP 是一套标准化协议解决的是“外部系统怎么以统一方式被模型发现和调用”它把过去每个系统单独写适配代码的成本压下来。Skills 则是用文字定义任务流程和资源让模型在需要时自动加载一段“操作手册”把多步骤、带规则的流程交给模型动态执行。三者不是并列关系而是层层叠加Function Calling 是地基MCP 和 Skills 都是在地基上盖的不同楼层。我见过太多项目卡在第一步模型返回的tool_calls字段解析不出来或者arguments是空字符串。这类问题往往不是模型的问题而是请求体里tools数组的 schema 写错了或者tool_choice参数没设对。更隐蔽的是当你同时挂载多个工具时模型可能因为工具描述太相似而选错。这些都需要一个稳定的 API 通道来反复调试而不是每次都在网络层浪费时间。这篇文章要交付的是一个最小可用的 ReAct Agent 跑通路径。你会看到完整的工具定义 JSON、MCP 服务注册步骤、Skills 加载逻辑以及如何通过 TaoToken 统一 Key 通道完成 Function Calling 的验证请求。适合已经了解大模型基础调用、想往 Agent 方向落地的开发者。不需要你提前配好复杂环境跟着步骤走能独立跑通一个能调用外部工具的智能体。2. TaoToken 统一 Key 通道ReAct Agent 接入前的必要准备在写第一行 Agent 代码之前得先把模型调用通道理顺。ReAct Agent 的特点是请求频繁、工具调用轮次多如果每次调试都卡在鉴权或网络层效率会非常低。TaoToken 在这里的角色是一个统一 Key 通道你用它拿到一个 API Key就可以在同一个入口下调用多种模型不用为每个模型单独维护一套鉴权逻辑。先明确你要拿到的三件套Base URL、API Key、Model ID。这三样在后续所有配置里都会反复出现缺一个都跑不通。Base URL 是https://taotoken.net/api注意这个地址不带任何查询参数直接作为请求前缀使用。API Key 需要你登录后在控制台创建创建时建议按项目命名比如react-agent-dev方便后续排查是哪个环境在调用。Model ID 则取决于你当前想用的模型在模型列表里能看到具体标识。拿到 Key 之后不要急着写 Agent 逻辑先用一个最简单的 curl 请求验证通道是否通畅。这一步能帮你排除掉大部分环境问题。请求体里只放一个用户消息不挂任何工具看模型是否能正常返回文本。如果这一步就报 401说明 Key 没生效或者请求头格式不对如果报连接超时检查 Base URL 是否写成了带路径的地址。curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer YOUR_API_KEY \ -H Content-Type: application/json \ -d { model: YOUR_MODEL_ID, messages: [ {role: user, content: 回复 ok 两个字母即可} ] }预期返回是一个标准的 chat completion 结构choices[0].message.content里应该有ok。如果这一步成功说明通道没问题可以进入工具调用阶段。如果失败先看 HTTP 状态码401 是鉴权问题404 是路径写错429 是频率限制。把错误码和返回体里的error.message一起看基本能定位到原因。这里有个容易踩的坑有些人会把 Base URL 写成https://taotoken.net/api/v1然后在代码里又拼一次/v1/chat/completions结果路径变成/api/v1/v1/chat/completions直接 404。记住 Base URL 只到/api后面的路径由 SDK 或你的请求代码补全。另外API Key 不要硬编码在代码里提交到仓库用环境变量或者本地配置文件管理。通道验证通过后你还需要确认一件事当前使用的模型是否支持 Function Calling。不是所有模型都默认开启工具调用能力有些需要在请求里显式声明。你可以在模型对话页面先手动测试一轮带工具的请求确认模型能返回tool_calls字段。这个动作能帮你省掉后面大量“为什么模型不调工具”的排查时间。3. 可复制配置Function Calling 工具定义与 MCP 服务注册现在进入核心配置环节。ReAct Agent 的工具能力由两部分组成一是直接写在请求里的 Function Calling 工具定义二是通过 MCP 协议注册的外部服务。两者在模型看来都是“可调用的工具”但注册方式和适用场景不同。先看 Function Calling 的工具定义。你需要构造一个tools数组每个工具包含type、function.name、function.description、function.parameters。参数用 JSON Schema 描述类型、必填项、枚举值都要写清楚。描述字段非常关键模型靠它判断什么时候该调这个工具。写得太模糊模型会乱调写得太窄模型该调的时候不调。下面是一个查询天气的工具定义你可以直接复制到请求体里{ model: YOUR_MODEL_ID, messages: [ {role: user, content: 北京今天天气怎么样} ], tools: [ { type: function, function: { name: get_weather, description: 查询指定城市指定日期的天气信息返回温度和天气状况, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如 北京、上海 }, date: { type: string, description: 日期格式 YYYY-MM-DD默认今天 } }, required: [city] } } } ], tool_choice: auto }tool_choice设为auto表示让模型自己决定是否调用工具。如果你在调试阶段想强制模型调用某个工具可以设为{type: function, function: {name: get_weather}}。但生产环境建议保持auto否则模型会失去自主判断能力。接下来是 MCP 服务注册。MCP 的核心价值在于把外部系统封装成标准化的工具端点模型通过 MCP Client 发现这些工具再通过 Function Calling 触发调用。注册一个 MCP 服务通常需要三步定义服务描述文件、启动 MCP Server、在 Agent 侧配置连接。以本地文件系统 MCP 服务为例你需要一个配置文件告诉 Agent 这个 MCP Server 怎么启动、暴露哪些能力。下面是一个 TOML 格式的配置片段路径和字段名按你的实际环境调整[mcp_servers.filesystem] command npx args [-y, modelcontextprotocol/server-filesystem, /Users/yourname/workspace] env { API_KEY ${TAOTOKEN_API_KEY} }这个配置的意思是启动一个文件系统 MCP Server允许模型读取/Users/yourname/workspace下的文件。command和args是启动命令env里可以注入环境变量。配置写好后Agent 启动时会自动拉起这个 MCP Server并把它的工具列表合并到模型的可用工具里。如果你用的是 Claude Code 或类似的编码 Agent配置入口通常在settings.json或项目根目录的.mcp.json。以 Claude Code 为例你需要在settings.json里加入{ mcpServers: { filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /Users/yourname/workspace], env: { API_KEY: YOUR_TAOTOKEN_API_KEY } } } }注意这里的三件套要写全Base URL 在 Agent 的模型配置里指定为https://taotoken.net/apiAPI Key 用你创建的那个Model ID 填你验证过的模型标识。MCP Server 本身不直接调模型它只负责暴露工具模型调用还是走 TaoToken 通道。Skills 的配置方式又不一样。Skills 不需要启动独立进程它是一组文档和脚本放在特定目录下模型通过一个加载函数按需读取。你可以在项目里建一个skills/目录每个 Skill 一个子目录里面放SKILL.md和可选的脚本文件。SKILL.md的头部用 YAML 写元数据正文写操作流程。--- name: rotate_pdf description: 将 PDF 文件按指定角度旋转并保存为新文件 --- ## 使用场景 当用户需要调整 PDF 页面方向时使用。 ## 步骤 1. 确认输入文件路径和旋转角度 2. 调用 scripts/rotate.py 执行旋转 3. 返回输出文件路径模型在启动时会读取所有 Skill 的name和description当用户请求匹配到某个 Skill 时模型通过 Function Calling 调用load_skill函数把对应的SKILL.md内容加载进上下文然后按文档里的步骤执行。整个过程不需要你写额外的调度代码模型自己判断。4. 验证请求跑通一次完整的 ReAct 工具调用循环配置写好后最关键的一步是验证模型真的能完成“思考—调用—观察—再思考”的循环。ReAct 的精髓在于模型不是一次性输出最终答案而是先输出工具调用请求拿到结果后再决定下一步。你要验证的就是这个多轮交互是否顺畅。先发一个带工具的请求看模型是否返回tool_calls。用第 3 节的天气工具定义发出去之后预期返回的choices[0].message里content为nulltool_calls数组里有一个对象function.name是get_weatherarguments是类似{city: 北京}的 JSON 字符串。如果模型直接返回了文本答案而没有调工具说明工具描述不够清晰或者模型不支持 Function Calling。拿到tool_calls后你的代码需要做三件事解析arguments、执行本地函数、把结果以role: tool的消息追加到对话里。下面是一个 Python 示例展示完整的循环import json import requests API_URL https://taotoken.net/api/v1/chat/completions HEADERS { Authorization: Bearer YOUR_API_KEY, Content-Type: application/json } def get_weather(city, dateNone): return f{city}今天天气25°C晴 tools [{ type: function, function: { name: get_weather, description: 查询指定城市指定日期的天气信息, parameters: { type: object, properties: { city: {type: string, description: 城市名称}, date: {type: string, description: 日期 YYYY-MM-DD} }, required: [city] } } }] messages [{role: user, content: 北京今天天气怎么样}] while True: payload { model: YOUR_MODEL_ID, messages: messages, tools: tools, tool_choice: auto } resp requests.post(API_URL, headersHEADERS, jsonpayload).json() msg resp[choices][0][message] messages.append(msg) if not msg.get(tool_calls): print(最终回答, msg[content]) break for call in msg[tool_calls]: fn_name call[function][name] args json.loads(call[function][arguments]) if fn_name get_weather: result get_weather(**args) else: result f未知工具{fn_name} messages.append({ role: tool, tool_call_id: call[id], content: result })这段代码跑通后你会看到模型先返回工具调用代码执行get_weather并把结果塞回对话模型再基于结果生成最终回答。这就是一个最小可用的 ReAct 循环。实测下来只要工具描述准确、参数 schema 完整模型选对工具的概率很高。如果你同时挂了 MCP 工具和本地 Function Calling 工具验证时要注意工具名是否冲突。MCP 工具通常带前缀比如filesystem.read_file本地工具是get_weather一般不会撞名。但如果你自己写的工具名和 MCP 工具名重复模型可能选错。建议在工具命名上做区分比如本地工具统一加local_前缀。验证成功后你可以把tool_choice改回auto并增加多轮对话的上下文管理。ReAct Agent 的上下文会随着工具调用轮次增长如果对话太长需要做截断或摘要否则会超出模型的上下文窗口。一个简单的策略是只保留最近 N 轮的工具调用记录更早的用摘要替代。5. 本篇常见错误排查401、local proxy failed 与 choices 解析异常即使配置看起来没问题实际跑的时候还是会遇到各种报错。这一节把最常见的几类错误和排查路径列出来你遇到问题时可以对照着看。401 Unauthorized是最常见的。返回体里通常有{error: {message: Invalid API key}}。先检查 API Key 是否复制完整有没有多余空格。然后确认请求头格式是Authorization: Bearer YOUR_KEYBearer 和 Key 之间有一个空格。如果 Key 是在环境变量里确认环境变量名和代码里读的一致。还有一种情况是 Key 被禁用或过期去控制台看 Key 状态。local proxy failed这类报错通常出现在 Agent 框架启动 MCP Server 的时候。错误信息里会带spawn或ENOENT意思是找不到启动命令。比如配置里写了command npx但系统 PATH 里没有 npx就会报这个。解决办法是写绝对路径或者先确认 Node.js 环境已安装。如果是 Python 的 MCP Server确认python或python3在 PATH 里。另外MCP Server 启动超时也会报类似错误可以适当增加超时时间。reading choices of undefined是解析响应时最常见的 JavaScript 报错。意思是resp.choices是 undefined你却在读resp.choices[0]。根本原因通常是请求失败但代码没检查 HTTP 状态码直接拿错误响应体去解析。修复方法是在解析前先判断resp.ok或resp.status 200失败时打印完整响应体。另一个原因是流式响应没处理完就解析如果你用了stream: true需要按 SSE 格式逐块读取不能直接JSON.parse整个响应。OAuth 相关报错一般出现在 MCP Server 需要鉴权的场景。错误信息里带OAuth token missing或invalid_grant。检查 MCP 配置里的env是否传入了正确的 token以及 token 是否过期。有些 MCP Server 需要单独的 OAuth 流程不能直接用 API Key 替代。如果你用的是 Claude Code 的 MCP 配置确认settings.json里的env字段和 Server 文档要求的一致。模型不调用工具是另一类高频问题。表现是模型直接返回文本tool_calls为空。排查顺序先确认模型支持 Function Calling再检查tools数组是否为空或格式错误然后看工具描述是否足够具体把“查询天气”改成“查询指定城市指定日期的天气信息返回温度和天气状况”往往就能解决最后确认tool_choice不是none。工具调用参数解析失败通常是因为arguments不是合法 JSON。模型偶尔会输出带注释或尾逗号的 JSON直接JSON.parse会抛异常。稳妥的做法是用容错解析库或者在解析失败时把原始字符串返回给模型让它重新生成。更根本的办法是在工具定义里把参数类型和格式写清楚减少模型自由发挥的空间。下面这张表把常见错误和对应动作列在一起方便你快速对照报错关键词可能原因处理动作401 UnauthorizedKey 无效或请求头格式错检查 Key 和 Bearer 格式local proxy failedMCP Server 启动命令找不到用绝对路径或确认环境reading choices请求失败未检查状态码先判断 HTTP 状态再解析OAuth token missingMCP 鉴权信息缺失检查 env 中的 tokentool_calls 为空工具描述不清或模型不支持细化描述并确认模型能力6. 从最小可用到持续迭代ReAct Agent 的下一步跑通最小循环之后你手里已经有一个能调用外部工具的智能体了。但真实场景里工具数量会增长任务复杂度会上升你需要考虑的是怎么让这套机制持续可用。这里给几个实际迭代中验证过的方向。第一把工具定义和 MCP 配置分离管理。本地 Function Calling 工具适合高频、轻量、逻辑简单的调用比如格式转换、简单计算。MCP 适合对接外部系统比如数据库、文件系统、第三方 API。Skills 适合多步骤、带规则的流程比如“生成周报”这种需要按固定顺序执行多个操作的任务。三者混用时在工具描述里写清楚各自的适用边界模型选错的概率会明显下降。第二给工具调用加日志和回放。每次tool_calls的入参和返回结果都记下来出问题时能快速定位是模型选错工具、参数传错还是工具本身执行失败。日志里带上tool_call_id方便和模型响应关联。如果发现某个工具经常被误调回去改描述而不是改代码逻辑。第三控制上下文长度。ReAct 循环每多一轮消息列表就长一截。当工具返回结果很大时比如读了一个长文件上下文会迅速膨胀。一个实用做法是只把工具结果的关键字段塞回对话完整结果存到外部模型需要时再通过另一个工具查询。这样既保留了信息又不撑爆窗口。第四Skills 的SKILL.md要写得像给新人的操作手册。步骤编号、每步的输入输出、异常情况怎么处理都写清楚。模型读文档的能力很强但前提是文档本身结构清晰。我试过把一段模糊的“处理数据”描述改成带编号的七步流程任务成功率从一半左右提升到稳定执行。最后别忘了定期验证通道。TaoToken 的 Key 和模型列表可能会有更新每隔一段时间用第 2 节的 curl 命令跑一次基础请求确认通道正常。如果要做长期编码或 Agent 任务可以关注 Coding Plan 相关的入口把模型调用和工具编排放在一个稳定的通道下管理。需要查具体模型能力时模型对话页面可以直接测试工具调用行为比在代码里反复试错快得多。接入文档里有完整的参数说明和示例配置 MCP 或 Skills 时对照着看能少走很多弯路。