1. 从「用嘴点单」到「填单子」:Function Calling 到底解决了什么
如果你正在做 Agent 相关开发,大概率遇到过这样的场景:让模型输出一段文本,然后用正则去匹配Action: xxx,再手动解析后面的 JSON 参数。Demo 阶段跑得挺欢,一旦工具数量上到十几个,或者模型多打了几个字、换了个格式,整条链路就开始飘。
这就是 Function Calling(函数调用)要解决的核心问题。用餐厅点菜来类比特别直观:顾客(LLM)负责看菜单、做决定;菜单(tools 定义)写清每道菜的名字、做法、配料;服务员(你的 Agent 代码)负责把顾客的要求记成标准单子,然后去后厨(真实业务系统)执行。
没有 Function Calling 的时候,顾客只能用嘴描述:“我要那个鸡蛋炒番茄、不放糖、加葱花的菜。”服务员听着容易误解。有了 Function Calling,菜单上写着“番茄炒蛋(含配料说明)”,顾客只需要说“番茄炒蛋,一份,不放辣”,服务员把这句话记成一张标准单子,后厨照单做菜。
一句话定义:Function Calling = 模型根据工具菜单(tools),输出结构化的调用意图 {工具名 + 参数},真正执行的是你的代码。关键在“意图”两个字——模型只是“说它想调 get_weather”,它不会真的去调。就像顾客只负责点菜,绝不进后厨。
一次完整的 FC 调用包含四个角色、五个回合:用户提问 → Agent 把对话历史 + 工具菜单发给 LLM → LLM 输出结构化 tool_calls(工具名 + 参数)→ Agent 执行工具 → 工具结果以 role=tool 放回对话 → LLM 输出最终回答。注意第 3 步和第 6 步:LLM 永远只输出“意图”,调不调、调的结果什么样,LLM 一概不碰,动手的全是 Agent 代码。
这套机制适合谁?任何在做 Agent、智能助手、自动化工作流的开发者。无论你用的是 OpenAI 兼容接口、Claude 系列还是国内模型,Function Calling 都是标配能力。而要把这套链路稳定跑通,一个统一的 API Key 管理入口能省掉大量切换成本——TaoToken 就是干这个的,后面会给出具体配置。
2. TaoToken 前置准备:统一 Key 与 MCP 工具调用环境搭建
在动手写 Function Calling 代码之前,先把“服务员”的工牌办好。TaoToken 的作用是提供一个统一的 API 入口,让你用同一个 Key 访问多种模型,不用在多个平台之间来回切换 Key 和 Base URL。对于 Function Calling 这种需要反复调试工具定义、对比不同模型表现的场景,统一 Key 能显著降低环境切换的摩擦。
2.1 获取 API Key 与确认 Base URL
第一步,打开 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=),注册后在控制台创建 API Key。拿到 Key 之后,记下两个关键信息:
- Base URL:
https://taotoken.net/api - API Key:形如
sk-xxxxxxxx的字符串
这两个值后面会写进环境变量和配置文件。注意 Base URL 不要加 UTM 参数,直接用https://taotoken.net/api即可。
2.2 环境变量配置
推荐用环境变量管理 Key,避免硬编码到代码里。Linux/macOS 下在~/.bashrc或~/.zshrc追加:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"Windows PowerShell 用:
$env:TAOTOKEN_API_KEY="sk-你的Key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"配置完执行source ~/.bashrc或重开终端,用echo $TAOTOKEN_API_KEY确认生效。
2.3 安装依赖
Python 环境下安装 OpenAI SDK(TaoToken 兼容 OpenAI 接口格式):
pip install openai如果你打算用 MCP 协议接入工具,再装一个 MCP 客户端库:
pip install mcp2.4 验证 Key 是否可用
写一个最小脚本确认 Key 和 Base URL 能通:
import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "回复 OK 两个字母"}], ) print(resp.choices[0].message.content)如果输出OK,说明前置环境已经就绪。如果报 401,检查 Key 是否复制完整、有没有多余空格;如果报连接错误,检查 Base URL 是否写成了https://taotoken.net/api(不要带路径后缀)。
2.5 关于模型选择
Function Calling 对模型的指令遵循能力有要求。实测下来,gpt-4o-mini、gpt-4o、claude-3.5-sonnet 这类模型在工具选择准确率上表现稳定。你可以通过 TaoToken 的模型对话页面(https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite)先手动测试几个模型对同一组工具定义的反应,再决定生产用哪个。
3. 可复制配置:MCP 工具定义 JSON 与统一 Key 接入片段
这一节给出可以直接复制运行的配置。核心是三件套:Base URL、API Key、Model ID。无论你是在 Cline、Claude Code 还是自己写的 Agent 里接入,这三个值都是必须的。
3.1 工具定义 JSON(菜单)
先定义一个天气查询工具,这是最经典的 Function Calling 示例:
{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市、指定日期的天气。当用户询问天气、气温、是否下雨、要不要带伞时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如:北京、上海" }, "date": { "type": "string", "description": "日期,例如:明天、2026-08-07" } }, "required": ["city", "date"] } } }这段 JSON 就是“菜单”:一道菜叫get_weather,description 写清了“什么情况下点这道菜”,parameters 写明了每个参数叫什么、什么类型、必不必须。description 里给例子(“例如:北京”)比写一百字抽象说明都管用,模型是看例子猜意图的。
3.2 MCP 工具定义格式
如果你用 MCP 协议,工具定义会包一层 server 配置。以 Cline 的 MCP 配置为例,在cline_mcp_settings.json中:
{ "mcpServers": { "weather-server": { "command": "python", "args": ["-m", "weather_mcp_server"], "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }MCP Server 内部再暴露 tools 列表,格式和上面的 JSON 一致。MCP 的价值在于标准化——机票系统、酒店系统、支付系统各自实现一个 MCP Server,任何支持 MCP 的 Agent 都能直接调用,不用各写各的适配。
3.3 Claude Code / Codex 的 settings 配置
如果你用 Claude Code 或 Codex 这类编码 Agent,配置文件通常在~/.claude/settings.json或项目根目录的.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-3.5-sonnet" } }Codex 的auth.json格式:
{ "openai_api_key": "sk-你的Key", "openai_base_url": "https://taotoken.net/api", "model": "gpt-4o-mini" }三件套齐全:Base URL 指向 TaoToken,Key 用统一 Key,Model ID 按需选择。这样无论你切到哪个 Agent 工具,配置逻辑都是一致的。
3.4 完整可运行的 Function Calling 代码
把上面的工具定义和 Key 配置串起来:
import json import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) def get_weather(city: str, date: str) -> str: mock = {"北京": {"明天": "小雨,18~25℃"}} return mock.get(city, {}).get(date, "查不到该城市天气") tools = [{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市、指定日期的天气。当用户询问天气、气温、是否下雨、要不要带伞时使用。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,例如:北京"}, "date": {"type": "string", "description": "日期,例如:明天"}, }, "required": ["city", "date"], }, }, }] messages = [{"role": "user", "content": "北京明天天气怎么样?"}] for _ in range(5): resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools, ) msg = resp.choices[0].message if not msg.tool_calls: print("最终回答:", msg.content) break messages.append(msg) 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": str(result), })对比之前用正则解析文本协议的写法,这里tool_calls是接口返回的强类型字段,永远不可能“格式不对”。参数通过arguments字段传递,标准 JSON,不用手写字符串拼接。
4. 验证请求与成功结果:一次完整工具调用与结果回填
配置写好了,接下来验证整条链路是否跑通。这一节把每一步的请求和响应都摊开看,方便你对照排查。
4.1 第一次请求:发菜单
Agent 把对话历史和工具菜单一起发给模型。请求体核心部分:
{ "model": "gpt-4o-mini", "messages": [ {"role": "user", "content": "北京明天天气怎么样?"} ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市、指定日期的天气。当用户询问天气、气温、是否下雨、要不要带伞时使用。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,例如:北京"}, "date": {"type": "string", "description": "日期,例如:明天"} }, "required": ["city", "date"] } } } ] }4.2 模型返回:收单子
模型不会返回一大段话,而是返回结构化 tool_calls:
{ "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\", \"date\": \"明天\"}" } } ] }拿到tool_calls字段就说明模型想调工具。arguments是个字符串,里面是 JSON 参数,用json.loads解析即可。
4.3 执行工具并回填结果
Agent 代码执行get_weather(city="北京", date="明天"),得到结果“小雨,18~25℃”,然后以role=tool放回对话:
{ "role": "tool", "tool_call_id": "call_abc123", "content": "小雨,18~25℃" }tool_call_id必须和上面单子的id对上,模型才知道“我那张单子出菜了,结果是这样”。
4.4 第二次请求:模型生成最终回答
把 assistant 的 tool_calls 消息和 tool 结果消息都追加到 messages,再次请求模型。这次模型不再调工具,直接输出:
北京明天有小雨,气温 18~25℃,记得带伞。4.5 成功标志
整条链路跑通的标志是:控制台打印出“最终回答:北京明天有小雨,气温 18~25℃,记得带伞。”如果卡在某一步,对照下一节的排查清单。
4.6 多工具并行验证
Function Calling 的一个隐藏福利是原生支持多工具并行。模型可以一次性输出两个 tool_calls,比如同时查航班和酒店:
{ "role": "assistant", "tool_calls": [ {"id": "call_1", "type": "function", "function": {"name": "search_flight", "arguments": "{\"departure_city\":\"上海\",\"arrival_city\":\"北京\",\"date\":\"2026-08-07\"}"}}, {"id": "call_2", "type": "function", "function": {"name": "search_hotel", "arguments": "{\"city\":\"北京\",\"checkin\":\"2026-08-07\"}"}} ] }你的代码可以并行执行这两个工具,省一半时间。验证时可以在工具定义里加一个search_hotel,观察模型是否会在合适场景下同时输出两个 tool_calls。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
这一节对照真实报错,给出排查路径。大部分问题集中在 Key 配置、Base URL 格式、模型返回解析这三类。
5.1 401 Unauthorized
报错信息:
openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key', 'type': 'invalid_request_error'}}原因通常是 Key 没配好。排查顺序:
第一,确认环境变量是否生效。执行echo $TAOTOKEN_API_KEY,如果输出为空,说明source没执行或写错了文件。Windows 下用echo $env:TAOTOKEN_API_KEY。
第二,确认 Key 没有多余空格或换行。从控制台复制时容易带上尾部空格,用repr(os.environ["TAOTOKEN_API_KEY"])打印出来看。
第三,确认 Base URL 写对了。必须是https://taotoken.net/api,不要写成https://taotoken.net/api/v1或带其他路径。如果 Base URL 错了,请求会打到错误端点,也可能返回 401。
5.2 local proxy failed / Connection error
报错信息:
openai.APIConnectionError: Connection error.或者:
httpx.ConnectError: [Errno 111] Connection refused这类错误通常是本地网络配置问题。排查:
第一,确认 Base URL 是https://taotoken.net/api,不是http://也不是localhost。如果你之前配过其他工具的代理设置,检查环境变量HTTP_PROXY、HTTPS_PROXY是否指向了不可用的地址,临时unset HTTP_PROXY HTTPS_PROXY再试。
第二,确认 DNS 能解析。ping taotoken.net看是否通。
第三,如果公司网络有防火墙,确认 443 端口出站没有被拦。
5.3 reading 'choices' / KeyError: 'choices'
报错信息:
KeyError: 'choices'或者:
AttributeError: 'NoneType' object has no attribute 'choices'这通常说明响应体不是预期的 OpenAI 格式。原因可能是:
第一,Base URL 配错了,请求打到了某个返回 HTML 的端点。检查resp的原始内容:print(resp)或print(response.text)。
第二,模型名写错了。如果 model ID 不存在,某些网关会返回错误结构。确认你用的 model ID 在 TaoToken 的模型列表里存在。
第三,请求被中间层拦截返回了错误页。检查是否有额外的 header 或参数不被支持。
5.4 OAuth / token 过期
报错信息:
Error: OAuth token expired或者:
invalid_grant如果你用的是 Claude Code 或 Codex 这类带 OAuth 流程的工具,报这个错说明登录态过期了。解决方式是重新走一遍登录流程,或者在 settings.json 里改用 API Key 方式(ANTHROPIC_API_KEY/openai_api_key)而不是 OAuth token。用 TaoToken 的统一 Key 可以绕过 OAuth 过期问题,因为 Key 是长期有效的。
5.5 模型不调工具,直接回答
现象:模型没有返回 tool_calls,而是直接输出了一段文本回答。
排查:
第一,检查 tools 参数是否真的传进去了。打印请求体确认。
第二,检查工具 description 是否写得太模糊。如果 description 是“查询数据”这种,模型不知道什么时候该用,可能选择不调。改成“当用户询问天气、气温、是否下雨时使用”这种明确场景描述。
第三,检查用户提问是否真的需要工具。如果用户问“你好”,模型不调工具是正常的。
第四,换一个指令遵循能力更强的模型试试。有些小模型对 Function Calling 支持不完整。
5.6 参数幻觉:出发到达填反
现象:用户说“上海飞北京”,模型输出departure_city: 北京, arrival_city: 上海。
这是 LLM 的概率本质决定的,它是在预测下一个词,不是查数据库。对策分三层:
第一层,schema 兜底。在 parameters 里用enum限定可选值,模型填错直接校验失败。
第二层,业务校验。执行前检查“出发地≠到达地”“日期在今天之后”。
第三层,反馈修正。校验失败时,把错误信息以 tool 结果喂回模型:
{"error": "出发城市不能等于到达城市,请重新确认用户意图"}模型收到错误后会重新生成参数。
6. 稳定跑通链路之后:从 Function Calling 到生产级 Agent
把上面的配置和代码跑通,你已经掌握了 Function Calling 的核心链路。但要从 Demo 走到生产,还有几件事必须做。
6.1 写操作二次确认
工具分两类:读操作(查天气、查航班,无害)和写操作(下单、付款、删数据,有后果)。写操作绝不能“模型说调就调”——模型可能被 prompt 注入诱导,也可能真的理解错。
生产里的下单流程长这样:模型请求调用下单工具 → 参数校验(必填项齐不齐、金额合不合理)→ 用户二次确认(“确认订东航 MU5101,¥890 吗?”)→ 执行下单。两道闸:参数校验 + 用户确认。读操作放行,写操作必过闸。
6.2 工具粒度设计
工具该拆多细?两个极端都是坑。
太细的例子:search_flight_early(早班机)、search_flight_late(晚班机)、search_flight_direct(直飞)、search_flight_transfer(中转)。功能上和search_flight+ 参数time_range="早"完全一样,但工具列表会爆炸,模型每次都要在一堆相似工具里挑,更容易挑错。
太粗的例子:一个query工具想干所有事。模型不知道啥时候用,参数也没法定义清楚。
经验法则:一个函数只干一件完整的事,把变化留给参数。航班查询是一个完整的事,一个工具,早/晚班交给time_range参数;天气查询是另一件事,另一个工具。两个工具之间职责不重叠,模型就不会纠结。
6.3 工具结果也是上下文
工具返回的结果是放回对话的,一个复杂接口可能返回几百个字段的 JSON,直接把上下文撑爆。这是上下文管理要解决的问题——滑动窗口、摘要压缩、截断策略。你在 Function Calling 阶段就要有这个意识:工具返回值尽量精简,只保留模型决策需要的字段,不要把整个 API 响应原样塞回去。
6.4 用 TaoToken 统一管理多模型
生产环境往往需要对比不同模型的表现,或者按成本/延迟做路由。TaoToken 的统一 Key 让你不用为每个模型单独配 Key 和 Base URL,切换模型只需要改model参数。对于 Function Calling 这种需要反复调试工具定义、对比工具选择准确率的场景,这个便利性很实在。
如果你在搭长期运行的编码 Agent 或自动化工作流,可以看看 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite),按需选择模型和配额。API Key 管理在控制台(https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite),接入文档在文档页(https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite)。
6.5 下一步:上下文管理
聊了这么久,Agent 的“手”(工具)基本讲透了。但你可能已经注意到一个问题:每次工具结果都要放回对话,对话会越来越长,模型早晚“失忆”。下一篇讲 Agent 落地第一天敌——上下文管理:滑动窗口、摘要压缩、截断策略,让 Agent 记住该记住的、忘掉该忘掉的。
在那之前,先把这篇的 Function Calling 链路跑通。把工具定义 JSON 复制到你的项目里,用 TaoToken 的统一 Key 配好环境变量,跑一次完整的“发菜单 → 收单子 → 执行 → 回填”流程。跑通之后,你再看任何 Agent 框架的工具调用部分,都会觉得清晰很多。