Agent 跑 Function CallingBase URL 和 Key 千万别散落两处。先到 TaoToken 这边https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册、创建 Key然后回到 04、05、06 三节里的示例代码把模型请求的入口统一成https://taotoken.net/api。这么做不是为了少写两行配置而是为了让「第一次带 tools 的请求」和「工具跑完后的第二次请求」走同一条路。很多人照着原文敲完messages、role、tools单步调试都能过一旦把 Agent 串起来就出问题文字接龙那段写的 Demo 用了一个地址Function Calling 那段抄来的代码又用了另一个地址最后 Agent 里加了个query_order工具返回值拼回messages时忘了换成同一个通道。报错看起来像 messages 拼错其实是请求打到了不同地方。这篇把接入这一件事说透概念部分还是回到原文那三节去啃。1. 04 里的 messages 与 role真正卡住人的是「第二次请求」1.1 文字接龙示例messages 是唯一的上下文载体原文 04 用文字接龙讲messages这个类比很准模型自己不带记忆你每次把整个数组重新发一遍它才「记得」前面说了什么。role只有三种基础角色system定规矩user提要求assistant是模型自己的历史发言。数组的顺序就是对话的时间线顺序错了模型接的话就会跑偏。这套机制在单轮对话里没毛病问题出在 Function Calling 上。一次带工具的任务至少要发两次请求第一次把tools一起发过去让模型决定调哪个函数第二次把工具的执行结果追加进messages再问一遍。两次请求面对的是同一份上下文数组如果第一次走 A 通道、第二次走 B 通道模型看到的内容没变但后面统计用量、看日志、排查超时的时候你会发现两次请求根本对不上号。1.2 tools 声明与 role: tool 是同一套协议的两半tools里描述的是「有什么函数可用」参数结构用 JSON Schema 写模型不会真的去执行你的函数它只是在回复里吐出tool_calls告诉你「我想调query_order参数是{order_id: A10086}」。真正执行的是你自己的代码执行完把结果包成{role: tool, tool_call_id: ..., content: ...}塞回messages。这就是为什么接入配置必须先定下来。role: tool这条消息只对「同一次会话的后续请求」有意义tool_call_id也是上一轮返回的 ID。换通道等于换了一个不认这个 ID 的服务端模型会当成一堆无意义的文本处理回复自然驴唇不对马嘴。1.3 把 Key 和 Base URL 从业务代码里抽出来写 Demo 时最省事的做法是把 Key 硬编码在client OpenAI(api_keysk-xxx)里再顺手把base_url也写死。等要用 Agent 跑真实流程代码里可能有三四个文件各有一份配置改一次地址要全局搜索。正确姿势是抽两个环境变量TAOTOKEN_API_KEY和TAOTOKEN_MODEL代码里只留base_urlhttps://taotoken.net/api。这样第一次请求和第二次请求共用同一个 client 实例通道天然统一。2. 去 TaoToken 创建 Key把 base_url 定成 https://taotoken.net/api2.1 准备材料一把 Key 和一个模型 ID打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 完成注册进控制台创建 API Key复制出来后写进环境变量代码里一律用占位符YOUR_API_KEY别把真 Key 提交到仓库。export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_MODELYOUR_MODEL_ID模型 ID 不用猜也不要用别人博客里的旧名字。去 TaoToken 的模型广场看你当前账号可用的列表复制对应 ID 填进TAOTOKEN_MODEL。模型上下架是会变的以模型广场当时列表为准比记名字靠谱。2.2 base_url 填 https://taotoken.net/api不要写官网地址也不要加 /v1这是最容易搞混的一点。给人点的链接是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 填进代码里的base_url是https://taotoken.net/api两者不是一个东西。把官网地址填进base_url请求会打到页面上在https://taotoken.net/api后面再补一个/v1路径会变成双份版本号通常直接 404。提示判断标准很简单——凡是「注册、创建 Key、看模型列表、看用量」都是官网地址凡是「填进 OpenAI SDK、Codex、Claude Code 的参数」都是https://taotoken.net/api末尾不带斜杠也不带/v1。2.3 通道统一之后原文的 tools 逻辑一行都不用改TaoToken 在这里只负责一件事给 Agent 的每一次模型请求提供同一把 Key 和同一个入口。原文里的messages怎么拼、role怎么排、tools的 schema 怎么写全部保持原样。你要改的只有base_url和api_key两个参数加上把模型 ID 换成模型广场里的真实值。3. 把 05 的 Function Calling 示例接到这条通道上3.1 第一次请求声明 tools模型只回 tool_calls先照着原文的结构写一个query_order的工具声明然后用同一个 client 发第一次请求import json import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) tools [ { type: function, function: { name: query_order, description: 按订单号查询订单当前状态返回 JSON 字符串, parameters: { type: object, properties: { order_id: {type: string, description: 订单号} }, required: [order_id], }, }, } ] messages [ {role: system, content: 你是订单助手需要查订单时调用 query_order。}, {role: user, content: 帮我看看订单 A10086 现在是什么状态}, ] resp client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL], messagesmessages, toolstools, tool_choiceauto, ) msg resp.choices[0].message print(msg.tool_calls)第一次请求结束后msg.content通常是空的msg.tool_calls里有模型挑中的函数名和参数。这一步没有任何工具被执行模型只是「点了菜」。3.2 第二次请求把工具结果追加进 messages 再问一遍原文 05 的重点就在这一步。工具是你自己执行的模型碰不到你的订单库。给它一个本地桩函数把返回值包成role: tool追加进去def query_order(order_id: str) - str: # 演示用桩函数。真实项目里这一步在你的服务端/RPC 里跑 # 不要让模型直接连生产库也不要让它生成 SQL 去执行。 return json.dumps( {order_id: order_id, status: 已发货, carrier: 顺丰}, ensure_asciiFalse, ) messages.append(msg.model_dump(exclude_noneTrue)) for call in msg.tool_calls: args json.loads(call.function.arguments) result query_order(**args) messages.append({ role: tool, tool_call_id: call.id, content: result, }) final client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL], messagesmessages, toolstools, ) print(final.choices[0].message.content)第二次请求的messages里多了两条一条是带tool_calls的 assistant 消息一条是role: tool的结果消息。这两条必须成对出现tool_call_id也要对得上模型才知道「我刚才点的那个单结果回来了」。3.3 query_order 绕一圈之后通道始终没变从用户提问到模型决定调用query_order到你的代码执行查询再到把结果拼回上下文发起第二次请求整条链路里模型请求只发生两次且都由同一个 client 发出。原文 06 把这条链路升级成 Agent 之后步骤会变多但「每次模型请求都走同一个入口」这个前提不能破。注意诊断类 SQL、编译、跑脚本这些动作都放在你本地或者测试环境里执行把输出或报错贴回对话让模型帮你解释。不要写成「让 Agent 连上库自己执行」——这既不是原文的意图也不安全。4. 06 的 Agent模型、记忆、规划、工具共用一条通道4.1 上下文记忆就是 messages 数组别丢原文 06 把「上下文记忆」列成 Agent 的一个组成部件落到代码层面它就是那个不断变长的messages数组。多轮工具调用之后数组里会堆着 user、assistant、tool 三种角色的消息顺序和配对关系一个都不能乱。如果中间某次请求换了通道服务端返回的tool_calls结构可能对不上tool_call_id就断了链。所以把 client 做成模块级单例把base_url和 Key 从环境变量读是所有 Agent 示例都该有的基础习惯。这件事做对以后记忆那部分你不用额外写持久化逻辑也能跑通单次会话。4.2 任务规划本质是多次 Function Calling 的排列「任务规划」听起来玄实际就是让模型把一个大目标拆成几步每一步对应一次或多次工具调用。比如「查一下 A10086 状态如果已发货就把物流单号也取出来」模型可能规划成先调query_order拿到carrier之后再调query_logistics。每一轮都是「发请求 → 拿 tool_calls → 本地执行 → 拼回 messages → 再发请求」这个循环。这正好说明为什么接入必须统一循环每多跑一圈就多一次模型请求。如果配置散落在不同文件跑到第三轮时你自己都不确定这次用的是哪把 Key、哪个模型。把入口固定成https://taotoken.net/api循环跑多少轮都只需要看一处配置。4.3 工具数量增多时schema 别顺手改坏原文提到「工具调用」时用的例子很精简。真实 Agent 里 tools 会有五六个这时候description写得含糊模型就容易挑错函数或者传错参数。参数 JSON Schema 里required要写清楚别指望模型自己补默认值。这一步和接入通道无关但和接入错误长得很像——模型开始胡说八道时先检查tools声明再检查base_url和模型 ID。5. 跑完整 Agent 之前先用最小 chat 请求验一次5.1 curl 一条最小请求确认地址和 Key 都没填错正式写业务逻辑前先用命令行打一发最简单的请求。这一步能把「地址写错」「Key 无效」「模型名不存在」三个问题一次性暴露出来curl https://taotoken.net/api/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -d { model: YOUR_MODEL_ID, messages: [{role: user, content: 只回复两个字收到}] }能正常拿到回复说明 Key 有效、https://taotoken.net/api这个入口可用、模型 ID 也选对了。接下来再跑第 3 节那段带 tools 的代码出问题的范围就缩小到messages拼装和tool_call_id配对上了。5.2 Python 里加两个断言避免把错误配置带进 Agent环境变量最容易出现的问题是多打一个空格、复制时带上引号。在脚本开头加两行检查比事后猜半天强import os assert os.environ.get(TAOTOKEN_API_KEY), 缺少 TAOTOKEN_API_KEY去官网控制台创建一个 assert not os.environ[TAOTOKEN_API_KEY].startswith(http), 这里应该填 Key不是地址第二条断言是有用的把地址和 Key 填反是常见错误尤其在 Agent 代码里同时有两个环境变量的时候。5.3 第二次请求失败先看 tool_call_id如果第一次请求成功、第二次请求报参数错误八成是tool_call_id没对上。检查两点messages.append(msg.model_dump(exclude_noneTrue))有没有漏掉循环里是不是给每条tool_calls都补了一条role: tool的消息。模型返回几个tool_calls你就得补几条结果消息数量必须一致。6. 排障Agent 链路上常见的四类报错6.1 401 和 404 分工明确401 基本都是 Key 的问题Key 没带、带错、复制时多了引号或空格或者Authorization头写成了别的字段。404 基本都是地址的问题base_url填成了官网地址或者末尾多加了/v1。这两类错误不会出现在第二次请求里只出现在第一次如果第一次能通、第二次报错那基本可以排掉这两项。6.2 模型 ID 报错与模型广场对不上报「模型不存在」时先确认你填的 ID 是模型广场里当前可用的而不是从别的文章抄来的旧名字或自己拼的后缀。模型上下架会调整改配置前习惯性打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 看一眼列表比反复试错快得多。6.3 返回内容里混了工具调用的文本有时候模型没有产出标准tool_calls而是在content里写了一段类似 JSON 的文字。这通常意味着tools的 schema 描述不够清楚或者tool_choice设置得太松。先把函数描述写具体再考虑把tool_choice限定成具体函数名做对照测试。6.4 第一次通、第二次超时或空回复排查顺序是messages是不是太长工具返回的content是不是塞了一大段原始日志tools是不是每次请求都完整带上。第二次请求同样需要tools参数漏掉的话模型会以为工具已经不可用了只能硬答。7. 跑通之后把调试壳和用量对一遍7.1 用 Claude Code 或 Codex 当调试壳时填的还是同一个入口如果你习惯在命令行里对着代码改 Agent 逻辑可以把 Claude Code 的~/.claude/settings.json指到同一条通道注意这里填的是接口地址不是官网地址{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID } }Codex 那边对应的是~/.codex/config.toml注意它用的是另一套变量名别把ANTHROPIC_*套过来model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY这两个壳都只帮你写代码、读报错、改配置不会替你连生产库或执行诊断脚本。跑 SQL、编译、重启服务这些动作仍然是在你自己机器上执行把结果贴回来继续问。7.2 下一步先看这次调用有没有记上账配置保存后去 TaoToken 模型对话 用同一把 Key 发一条测试消息确认模型 ID 和地址都对得上。如果想长期拿它跑 Agent 调试可以在 Coding Plan 里看套餐是否够用Key 不够就再建几把入口在 控制台 API Keys。Claude Code 的环境变量写法对照 接入文档 更省事。回到 Agent 本身下一步其实不是继续堆工具而是把messages的拼装逻辑单独抽成一个函数把工具执行的部分抽成注册表。这样以后加第十个工具、换模型、改地址都只动一处。工具越多配置越要收口这话在第一次跑query_order的时候就该记住。
