前言
好家伙,OpenAI 这帮人真的是不让人睡觉。6月13号大半夜的,我又收到了那封熟悉的邮件——「OpenAI API Updates」。点开一看,好嘛,GPT-4 和 GPT-3.5 Turbo 都更新了,还降了价,但这都不是重点。重点是一个叫 Function Calling 的新功能。
之前写了那篇 ChatGPT API 初体验,当时就吐槽过 ChatGPT 不能直接调外部接口,输出的格式也不好控制。现在 OpenAI 直接给你整了个大活——让模型学会「打电话」叫函数了。
二话不说,赶紧来试一波。
Function Calling 到底是啥?
说人话就是:你给 ChatGPT 一堆函数的「说明书」,它看完之后,当你问它问题的时候,它会告诉你:「兄弟,你应该调这个函数,参数给你准备好了」。
举个栗子🌰:
- 你问:「今天北京天气咋样?」
- ChatGPT 回你:「你去调
get_current_weather 这个函数吧,location 填 北京」
- 你拿到参数,调你的天气 API,把结果扔回给 ChatGPT
- ChatGPT 再用自然语言把天气告诉你
是不是有种「我全都要」的感觉?模型负责理解意图和生成参数,你负责执行实际操作。这个设计真的妙,模型不用真的去调 API,它只需要「指挥」你去调就行。
就像 JoJo 里的替身使者一样——模型是本体,你写的应用就是它的替身,它指哪你打哪(笑)。
支持的模型
Function Calling 目前支持以下模型(2023年6月):
gpt-4-0613
gpt-3.5-turbo-0613
注意:旧版模型(gpt-3.5-turbo-0301、gpt-4-0314 等)不支持 Function Calling。你需要明确指定 0613 版本的模型,或者使用会自动升级到新版的别名 gpt-3.5-turbo / gpt-4。
实战:用天气查询来跑一遍
说了这么多,直接上代码。我们来实现一个最经典的例子:让 ChatGPT 帮我们查天气。
第一步:定义函数
先写一个假的天气查询函数(实际项目里你换成真实 API 就行):
1 2 3 4 5 6 7 8 9 10 11 12
| import json
def get_current_weather(location: str, unit: str = "celsius"): """获取指定城市的当前天气(模拟数据)""" weather_data = { "location": location, "temperature": "28", "unit": unit, "forecast": ["晴天", "微风"], } return json.dumps(weather_data, ensure_ascii=False)
|
第二步:告诉模型有哪些函数可用
这里就是 Function Calling 的核心了。你需要用 JSON Schema 的格式把函数的「说明书」传给 OpenAI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import openai
openai.api_key = "sk-你的key"
functions = [ { "name": "get_current_weather", "description": "获取指定城市的当前天气", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名,例如:北京、上海", }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], }, }, "required": ["location"], }, } ]
|
第三步:发送请求,处理 Function Call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| messages = [ {"role": "user", "content": "今天北京天气咋样?"} ]
response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, functions=functions, function_call="auto", )
response_message = response["choices"][0]["message"]
if response_message.get("function_call"): function_name = response_message["function_call"]["name"] function_args = json.loads(response_message["function_call"]["arguments"])
print(f"模型想调用函数: {function_name}") print(f"参数: {function_args}")
if function_name == "get_current_weather": function_response = get_current_weather( location=function_args.get("location"), unit=function_args.get("unit", "celsius"), )
|
这时候模型的返回大概长这样:
1 2 3 4 5 6 7 8
| { "role": "assistant", "content": null, "function_call": { "name": "get_current_weather", "arguments": "{\"location\": \"北京\", \"unit\": \"celsius\"}" } }
|
注意 {% emp content 是 null %},说明模型没有直接回答用户,而是选择「让函数来回答」。
第四步:把函数结果扔回给模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| messages.append(response_message) messages.append( { "role": "function", "name": function_name, "content": function_response, } )
second_response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, )
print(second_response["choices"][0]["message"]["content"])
|
最终输出类似:
北京今天天气晴朗,温度 28°C,伴有微风,非常适合外出活动哦!
整个流程就是:用户提问 → 模型说「调这个函数」→ 你调函数拿到结果 → 把结果交给模型 → 模型生成最终回答。
完整代码
把上面的代码整合一下,完整可运行版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import json import openai
openai.api_key = "sk-你的key"
def get_current_weather(location, unit="celsius"): weather_data = { "location": location, "temperature": "28", "unit": unit, "forecast": ["晴天", "微风"], } return json.dumps(weather_data, ensure_ascii=False)
functions = [ { "name": "get_current_weather", "description": "获取指定城市的当前天气", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "城市名"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, } ]
messages = [{"role": "user", "content": "今天北京天气咋样?"}]
response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, functions=functions, function_call="auto", )
response_message = response["choices"][0]["message"]
if response_message.get("function_call"): function_name = response_message["function_call"]["name"] function_args = json.loads(response_message["function_call"]["arguments"])
if function_name == "get_current_weather": function_response = get_current_weather( location=function_args.get("location"), unit=function_args.get("unit", "celsius"), )
messages.append(response_message) messages.append({ "role": "function", "name": function_name, "content": function_response, })
second_response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=messages, ) print(second_response["choices"][0]["message"]["content"])
|
踩坑注意事项
用了之后发现几个坑,给大家提个醒:
1. function name 的限制
函数名只能包含 a-z、A-Z、0-9、下划线 _ 和连字符 -,最长 64 个字符。千万别用中文、空格或者特殊符号,不然直接报错给你看。
2. 参数必须是合法的 JSON Schema
你的 parameters 字段必须是标准的 JSON Schema 格式。别想着随便写个对象糊弄过去,OpenAI 会校验的。properties 里的每个字段都需要有 type,required 数组也要配好。
3. 模型不一定会调函数
function_call 参数有三个选项:
"auto":模型自己决定(默认值)
"none":强制不调函数
{"name": "xxx"}:强制调指定的函数
如果你设了 "auto",模型可能会觉得不需要调函数就直接回答了。这不一定是 bug,可能是你的 prompt 没引导好,或者模型觉得能直接回答。
如果你想让模型「必须」调函数,可以用 function_call: {"name": "get_current_weather"} 来强制指定。但要注意,这样即使用户的问题跟天气无关,模型也会硬着头皮调这个函数,参数可能是瞎编的。
4. arguments 是字符串不是对象
这是一个很坑的地方!模型返回的 `function_call.arguments` 是一个 JSON 字符串,不是直接的对象。你需要自己 json.loads() 解析一下。别问我怎么知道的,我 debug 了半小时才发现这个坑。
5. content 为 null 是正常的
当模型决定调函数时,message.content 会是 null。别手贱去 .get("content") 就直接用了,记得先检查 function_call 字段。
6. 费用问题
Function Calling 整个流程至少要发两次请求(一次让模型决定调函数,一次把结果扔回去生成回答),所以 token 消耗会翻倍。如果模型判断失误多调了一次,那就是三倍。穷哥们悠着点用 💸
其他好玩的玩法
除了查天气,Function Calling 还能做很多事:
- 查数据库:把 SQL 查询封装成函数,让模型帮你写 SQL 并执行
- 发邮件:告诉模型收件人和内容,它帮你调发送函数
- 控制智能家居:「帮我把客厅灯关了」→ 调用智能家居 API
- 结构化数据提取:从一段文字里提取 JSON 格式的信息
说实话这个功能相当于给 ChatGPT 装上了手脚。之前它只会说「我无法访问互联网」,现在它能通过你的代码间接操控一切了。这不就是 EVA 的初号机配上阳电子炮的感觉吗?(二次元浓度拉满了属于是)
总结
Function Calling 是 OpenAI 在 2023 年 6 月放出的一个非常实用的功能更新。它解决了之前 LLM 最大的痛点之一——模型只能聊天,不能干活。虽然整个流程还是需要你写代码来中转,但这已经让 ChatGPT 从一个「嘴强王者」变成了能真正执行任务的「实干家」。
目前这个功能还比较新,后面肯定还会有更多玩法被社区挖掘出来。建议大家先跑通这个 demo,然后根据自己项目的需求去扩展。有啥问题评论区见~
参考资料