1 函数调用
虽然大模型能解决很多问题,但大模型并不能知晓一切。比如,大模型不知道最新消息(GPT-3.5 的知识截至 2021年9月,GPT-4 是 2023 年12月)。另外,大模型没有“真逻辑”。它表现出的逻辑、推理,是训练文本的统计规律,而不是真正的逻辑,所以有幻觉。所以大模型需要连接真实世界,并对接真逻辑系统。这就需要用到“函数调用”。
函数调用(Function Calling)可以使LLM具有与外部API交互的能力。让用户能够使用高效的外部工具、与外部API进行交互。其使用机制如下:
关于function calling,有以下几点需要注意:
- 在最新版本的OpenAI API中,可以使用
tools
参数对函数进行描述。并让大模型智能地选择输出包含函数参数的JSON对象来调用一个或多个函数。 - 最新的GPT模型(
gpt-3.5-turbo-0125
andgpt-4-turbo-preview
)可以自动检测何时应该调用函数(还有一个相关的参数tool_choice
,一般不用自己设置),还可以输出更加符合函数签名的JSON串。 - GPT不负责调用和执行外部函数,需要用户自己完成。
2 使用GPT进行函数调用
在使用GPT模型进行函数调用时,需要用到tools
参数进行函数声明,关于该参数有以下几点需要说明:
- 该参数可以接收一系列JSON组成的array,一个函数对应一个JSON,当前最多可以接受128个函数。
- JSON串的结构如下:
其中parameters
参数的写法要遵循JSON Schema格式,具体可以参考:https://blog.csdn.net/yeshang_lady/article/details/137146295
2.1 使用函数调用完成加法计算
大模型可以做加法是因为大模型记住了简单加法的统计规律,但大模型无法保证每次都能得到正确的加法计算结果。这里我们使用函数调用来完成加法计算。具体代码如下:
from openai import OpenAI
from dotenv import load_dotenv,find_dotenv
import json
from math import *
_=load_dotenv(find_dotenv())
client=OpenAI()
def get_completion(messages,model="gpt-3.5-turbo"):
response=client.chat.completions.create(
model=model,
messages=messages,
temperature=0.7,
tools=[{
"type":"function",
"function":{
"name":"sum",
"description":"加法器,计算一组数的和",
"parameters":{
"type":"object",
"properties":{
"numbers":{
"type":"array",
"items":{"type":"number"}
}
}
}
}
}],
)
return response.choices[0].message
prompt="计算这些数据的和:345,2313,89,632."
messages=[
{"role":"system","content":"你是一个数学家"},
{"role":"user","content":prompt}
]
response=get_completion(messages)
print(response)
#GPT模型第一次的回复中有关于函数调用信息,包括GPT生成的函数调用的参数,所以这些信息需要返回给GPT模型。
messages.append(response)
if response.tool_calls is not None:
tool_call=response.tool_calls[0]
if tool_call.function.name=="sum":
args=json.loads(tool_call.function.arguments)
result=sum(args["numbers"])
messages.append({
"tool_call_id":tool_call.id,
"role":"tool",
"name":"sum",
"content":str(result)
})
print(get_completion(messages).content)
其结果如下:
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_vYramfrhZX7kLZLYhqDiFVHP’, function=Function(arguments=‘{“numbers”:[345,2313,89,632]}’, name=‘sum’), type=‘function’)])
这些数据的和是3379.
2.2 同时启动多个函数调用
借助上述加法函数的代码,可以一次启动同一个函数的多次调用,具体代码如下:
from openai import OpenAI
import json
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
client=OpenAI()
def run_conversation(prompt,model='gpt-3.5-turbo'):
messages=[{"role":"user","content":prompt}]
tools=[{
"type": "function",
"function": {
"name": "sum",
"description": "加法器,计算一组数的和",
"parameters": {
"type": "object",
"properties": {
"numbers": {"type": "array", "items": { "type": "number"}}
}
}
}
}
]
response=client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto",
)
response_message=response.choices[0].message
messages.append(response_message)
print(response_message)
tool_calls=response_message.tool_calls
if tool_calls:
for tool_call in tool_calls:
function_name=tool_call.function.name
function_args=json.loads(tool_call.function.arguments)
function_response=sum(function_args.get("numbers"))
messages.append(
{"tool_call_id":tool_call.id,
"role":"tool",
"name":function_name,
"content":str(function_response)}
)
second_response=client.chat.completions.create(
model=model,
messages=messages,
)
return second_response.choices[0].message.content
if __name__=="__main__":
prompt="小明第一天买了5本书2个苹果,第二天买了3本书4个橘子,第三天买了7个梨和10本书,那么小明总共买了多个水果和多少本书?"
print(prompt)
print("====GPT回复====")
print(run_conversation(prompt))
其执行结果如下:
小明第一天买了5本书2个苹果,第二天买了3本书4个橘子,第三天买了7个梨和10本书,那么小明总共买了多个水果和多少本书?
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_XB4SBFVfhMtyeo4zRu1lvpim’, function=Function(arguments=‘{“numbers”: [2, 4, 7]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_d0B4e1j7Fhi1OPxxH9skJRAi’, function=Function(arguments=‘{“numbers”: [5, 3, 10]}’, name=‘sum’), type=‘function’)])
GPT回复: 小明总共买了13个水果和18本书。
关于这段代码,需要注意一点:
- 这段代码中的模型使用的是
gpt-3.5-turbo
,更确切的说是最新的gpt-3.5-turbo-0125
。OpenAI官方已经将gpt-3.5-turbo
指向了gpt-3.5-turbo-0125
。但如果使用的是国内代理的key的话,可能gpt-3.5-turbo
和gpt-3.5-turbo-0125
还是两个不同的模型,那运行上述代码时可能会遇到如下错误(输出second_response
可以看到报错信息):
Invalid parameter: messages with role ‘tool’ must be a response to a preceeding message with ‘tool_calls’
2.3 同时定义多个函数调用
假设我们现在同时定义了加法和乘法的函数调用,让大模型自动完成加法和乘法的调用。具体代码如下:
from openai import OpenAI
import json
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
client=OpenAI()
def math_multiply(number,price):
return number*price
def run_conversation(prompt,model='gpt-3.5-turbo-0125'):
messages=[{"role":"user","content":prompt}]
tools=[{
"type": "function",
"function": {
"name": "sum",
"description": "加法器,计算一组数的和",
"parameters": {
"type": "object",
"properties": {
"numbers": {"type": "array", "items": { "type": "number"}}
}
}
}
},
{"type":"function",
"function":{
"name":"multiply",
"description":"乘法器,计算两个数的乘积",
"parameters":{
"type":"object",
"properties":{
"price":{"type":"number","description":"一种物品的价格"},
"number":{"type":"integer","description":"一种物品的数量"},
},
"required":["price","number"],
}
}
}
]
response=client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto",
)
response_message=response.choices[0].message
available_function={
"sum":sum,
"multiply":math_multiply,
}
while response_message.tool_calls:
print(response_message)
messages.append(response_message)
tool_calls=response_message.tool_calls
for tool_call in tool_calls:
function_name=tool_call.function.name
function_args=json.loads(tool_call.function.arguments)
function=available_function[function_name]
if function==sum:
function_response=function(function_args.get("numbers"))
elif function==math_multiply:
function_response=function(function_args.get('number'),function_args.get('price'))
messages.append(
{"tool_call_id":tool_call.id,
"role":"tool",
"name":function_name,
"content":str(function_response)}
)
response_message=client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
).choices[0].message
return response_message.content
if __name__=="__main__":
prompt="小明第一天买了5本书和7个苹果,第二天买了3本书和2个苹果,第三天买了18本书和20个苹果,每一本书的价格是65元/本,每一个苹果的价格是7.5元/个,那么小明总共花了多少钱?"
print(prompt)
print("GPT回复:",run_conversation(prompt))
其执行结果如下(从输出内容可以知道,大模型先调用了两次加法运算完成书籍数量和水果数量的计算,接着调用两次乘法完成书本总价和水果总价的计算,最后调用加法完成总成本的计算。):
小明第一天买了5本书和7个苹果,第二天买了3本书和2个苹果,第三天买了18本书和20个苹果,每一本书的价格是65元/本,每一个苹果的价格是7.5元/个,那么小明总共花了多少钱?
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_xkGwaSApyRoTXmsNFhrtNQci’, function=Function(arguments=‘{“numbers”: [5, 3, 18]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_DQEKyCWNJT2JysmqH25OGLRO’, function=Function(arguments=‘{“numbers”: [7, 2, 20]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_5VlzZ8U5EhDixYnbwhh2Lljf’, function=Function(arguments=‘{“price”: 65, “number”: 26}’, name=‘multiply’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_qN12sj2Ze7TvcuF0vzcwZH9H’, function=Function(arguments=‘{“price”: 7.5, “number”: 29}’, name=‘multiply’), type=‘function’)])
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_XIdn5N1lhcRy8vG0UodSMfO9’, function=Function(arguments=‘{“numbers”:[1690,217.5]}’, name=‘sum’), type=‘function’)])
GPT回复: 小明总共花了1907.5元。
最后需要注意一点,是否执行函数调用由大模型自己决定。