)
1. 从「只会说」到「能动手」Function Calling 到底解决什么问题Function Calling工具调用和 Tool Use 说的是同一件事让大模型在对话过程中主动输出一个结构化的「我要调用某个函数」的意图由你的程序去真正执行再把结果喂回模型让它继续推理。模型本身不联网、不查库、不点按钮它只负责「决定调什么、传什么参数」真正的动作永远发生在你的代码里。这套机制能做什么查天气、查订单、跑 SQL、发邮件、读写文件、控制设备、操作浏览器本质上任何你能写成函数的事情都能挂上去。适合谁做 Agent 应用的、做 AI 客服的、做 AI Coding 的、做数据分析助手的只要你的场景里模型需要「拿到实时数据」或「产生副作用」就绕不开它。我见过太多人卡在第一步本地跑通了 OpenAI 的 demo一换模型、一换通道tools字段就报错或者模型死活不返回tool_calls。这篇笔记就以 TaoToken 统一 Key/API 通道为例把「模型返回调用意图 → 本地执行函数 → 回传结果 → 模型给出最终回答」这条最小闭环完整跑一遍。你跟着做能拿到一个可复制、可验证、能扩展的 Tool Use 骨架。核心链路其实就四步你带上工具 schema 发请求 → 模型返回tool_calls→ 你在本地执行对应函数 → 把结果以role: tool的消息回传 → 模型输出最终答案。听起来简单但每一步都有坑下面逐个拆。2. 前置准备用 TaoToken 统一 Key 打通模型通道在写工具之前先把「模型通道」这件事解决掉。Tool Use 对模型能力有要求不是所有模型都能稳定返回结构化的tool_calls。如果你一会儿用 Claude、一会儿用 GPT、一会儿又想试 Qwen每个平台一套 Key、一套 SDK、一套字段名光适配就够喝一壶。TaoToken 在这里的价值就是「一个 Key、一个 Base URL兼容主流模型的调用格式」。你不需要为每个模型单独申请账号、单独改代码切换模型基本只改一个model字符串。对做 Tool Use 的人来说这点很关键——因为不同模型的工具调用格式差异OpenAI 的parametersvs Anthropic 的input_schema是最容易踩坑的地方统一通道能帮你把精力放在工具设计上而不是接口适配上。先拿到你的 Key。访问 API Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建一个新 Key 并复制保存。注意 Key 只在创建时完整显示一次丢了就重新建。然后确认你的接入地址。OpenAI 兼容格式的 Base URL 是https://taotoken.net/api这个地址不加任何 UTM 参数直接用于代码里的base_url。如果你用的是 Anthropic 原生 SDK 或 Claude Code 这类工具接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言、各框架的完整配置示例。环境变量建议这样设避免 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api装依赖Python 用 OpenAI 官方 SDK 即可因为它兼容 OpenAI 格式pip install openai到这里前置就绪。记住三件套Base URL、API Key、Model ID。后面所有配置都围绕这三个值展开。如果你还没决定用哪个模型可以先在模型对话页试一下工具调用能力https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 手动发一条带工具定义的请求看模型是否返回tool_calls确认没问题再写代码。3. 可复制配置工具 schema 与请求参数怎么写这一节是全文最该抄的部分。先定义一个最小工具——查天气。工具 schema 用 JSON Schema 描述模型完全靠description和parameters来理解「这个工具干什么、要传什么」。{ type: function, function: { name: get_weather, description: 查询指定城市的实时天气。当用户询问某地天气、温度、是否下雨时调用此工具。, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如「上海」「北京」 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位默认摄氏度 } }, required: [city] } } }几个必须注意的点description要写清楚「什么时候用」而不是「这是什么」模型靠它做路由决策parameters严格用 JSON Schemaenum能极大降低参数乱传的概率required把必填字段锁死。我试过把description写成「天气工具」四个字结果模型经常该调不调改成上面那种带触发场景的描述后命中率明显上升。然后是完整的请求配置。这里用 Pythonbase_url指向 TaoTokenimport json from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keysk-你的key ) tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气。当用户询问某地天气、温度、是否下雨时调用此工具。, parameters: { type: object, properties: { city: {type: string, description: 城市名称}, unit: {type: string, enum: [celsius, fahrenheit]} }, required: [city] } } } ] response client.chat.completions.create( modelclaude-sonnet-4-6, messages[{role: user, content: 上海现在天气怎么样}], toolstools, tool_choiceauto ) msg response.choices[0].message print(msg.tool_calls)tool_choice有三个常用值auto让模型自己决定调不调required强制必须调某个工具none禁止调用。调试阶段建议先用required确认链路通再切回auto。如果你用 Anthropic 原生格式字段名不一样工具定义里是input_schema而不是parameters返回的是content数组里的tool_use块而不是tool_calls。用 TaoToken 的 OpenAI 兼容通道时你统一按 OpenAI 格式写就行省去两套心智负担。这也是统一 Key 的实际好处——代码只写一遍。4. 跑通闭环一次完整的调用-执行-回传-验证现在把链路补全。模型返回tool_calls后你要做三件事解析参数、执行真实函数、把结果回传。先写一个假的天气函数真实场景换成你的 API 调用def get_weather(city: str, unit: str celsius): fake_db { 上海: {temp: 28, desc: 晴}, 北京: {temp: 25, desc: 多云} } data fake_db.get(city, {temp: 20, desc: 未知}) return {city: city, temperature: data[temp], unit: unit, description: data[desc]}然后是多轮循环。第一轮拿到tool_calls执行后把结果作为role: tool的消息追加进messages再发第二轮import json messages [{role: user, content: 上海现在天气怎么样}] # 第一轮 resp client.chat.completions.create( modelclaude-sonnet-4-6, messagesmessages, toolstools, tool_choiceauto ) msg resp.choices[0].message messages.append(msg) # 执行工具 if msg.tool_calls: for tc in msg.tool_calls: args json.loads(tc.function.arguments) result get_weather(**args) messages.append({ role: tool, tool_call_id: tc.id, content: json.dumps(result, ensure_asciiFalse) }) # 第二轮模型基于工具结果生成最终回答 final client.chat.completions.create( modelclaude-sonnet-4-6, messagesmessages, toolstools ) print(final.choices[0].message.content)预期输出类似「上海现在晴气温 28°C。」如果你看到这句话说明整条闭环通了。验证成功的三个标志第一轮响应里msg.tool_calls非空且function.name是get_weatherarguments是合法 JSON能直接json.loads第二轮模型没有再次调用工具而是直接给出自然语言回答。任何一环不对去下一节对号入座。想验证并行调用把问题改成「上海和北京天气怎么样」模型可能一次返回两个tool_calls你并行执行后一起回传即可。这是 Agent 降低延迟的关键优化值得单独测一次。5. 常见报错排查401、tool_calls 为空、参数解析失败报错一401 Unauthorized。最常见的原因是 Key 没设对或 Base URL 写错。检查api_key是不是完整的sk-开头字符串base_url是不是https://taotoken.net/api注意不要多加/v1或漏掉。如果你用环境变量确认export之后新开的终端能读到。还有一种情况是 Key 被删了或额度用尽去 API Keys 页面重新建一个。报错二msg.tool_calls是 None模型直接回答了。三种可能模型不支持工具调用换一个支持 Tool Use 的模型tool_choice设成了none工具description太模糊模型没意识到该调。排查顺序是先设tool_choicerequired强制调用如果这时能返回tool_calls说明是描述问题回去改description。报错三json.loads(tc.function.arguments)抛异常。说明模型返回的参数不是合法 JSON通常是 schema 约束不够。给参数加enum、pattern把required补全。另外注意有些模型会把参数包在 markdown 代码块里返回稳妥做法是先 strip 掉json 和再解析。报错四local proxy failed或连接超时。检查网络能否正常访问https://taotoken.net/api以及本地是否有其他程序占用了端口。如果你在容器里跑确认容器网络能出网。报错五第二轮模型又调了一次同样的工具陷入循环。加一个max_iter上限比如 10 次同时检测「相同工具 相同参数」连续出现 3 次就强制终止。更根本的办法是在 system prompt 里明确「不要重复调用已经成功返回结果的工具」。报错六OAuth 或鉴权相关错误。如果你用的是 Claude Code 这类工具它走的是另一套鉴权流程别把 API Key 直接塞进去。参考接入文档里的 Claude Code 配置说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 按里面的步骤配 Base URL、Key、Model ID 三件套。排查通用心法先确认通道通能不能正常对话再确认模型支持工具tool_choicerequired测试最后确认 schema 和解析逻辑。分层定位别一上来就怀疑模型。6. 把闭环扩展成 Agent下一步怎么走最小闭环跑通后你手里其实已经有了 Agent 的雏形。把「单轮工具调用」包进一个while循环每次模型返回tool_calls就执行、回传直到模型不再调用工具、直接给出最终答案这就是 ReAct 模式的基础形态。多步推理、并行调用、错误自纠都是在这个骨架上长出来的。几个实用的扩展方向工具数量超过 20 个时别一股脑全塞给模型先用一次轻量调用做「工具路由」选出相关的 3 到 5 个再进主循环工具返回结果太长时让工具自己做摘要或分页避免上下文爆炸给每个工具加超时和重试失败信息原样回传给模型它下一轮往往能自己换参数重试。如果你要长期做编码类或 Agent 类任务频繁调用、需要稳定额度可以看看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合这种持续性的开发场景。日常调试和验证模型能力用模型对话页就够了。最后留一个我踩过的坑工具的执行结果一定要序列化成字符串再放进content直接塞 Python dict 会报类型错误中文结果记得ensure_asciiFalse否则回传给模型的是转义后的乱码影响它理解。把这两个细节处理好你的 Tool Use 链路就稳了。