OpenAI Function Calling 特性有什么用

OpenAI最近发布了一次更新,3.5可以支持16k的token,更新了gpt-3.5-turbo-0613 和 gpt-4-0613两个模型,同时这两个模型在chat completion的api中增加了一个叫 Function Calling 的新功能,本篇文章对其功能进行探究,并分析其作用。

我的新书《LangChain编程从入门到实践》 已经开售!推荐正在学习AI应用开发的朋友购买阅读!
LangChain编程从入门到实践

Function Calling 接口说明

  1. chat接口的messages数组除了原有的systemuserassisant,新增了role参数选项function
  2. chat接口新增functions参数,格式为数组,意味着可以传入一组函数定义,模型将智能选择用哪个
  3. 每个function支持三个参数:name(函数名)、description(函数功能说明)和parameters(模型输出的数据格式说明)
  4. chat接口新增了function_call参数,默认值为none,可设置为auto
  5. chat接口的function_call用于决定是否启用函数式回答。如果值为none,则不需要传入functions参数,如果值为auto,你需要提供一些函数供模型选择。只有在function_callauto,且functions包含函数数组,且模型根据你的函数功能说明匹配到函数时,返回的message中才会包含function_call
  6. 通过 LLM 调用函数并获取输出后,需要将结果传回给模型以生成自然语言回复(非必须)。此时,应在请求的messages中添加一个roleassisantmessage,包含function_call信息(即模型返回的数据),同时需要添加一个rolefunction的数据,包含本地函数执行的结果

Show Me Code

下面通过代码实践的方式进行探索

下面以费用统计应用为例,告诉AI:“今天买了一斤肉,花了多少钱”,正常思路来说,交互流程应该是这样的:

  • 用户向LLM输入prompt
  • AI进行智能分析
  • 返回结构化的数据(每个子项是什么,数量是多少)
  • 通过现实生活中的实时物价计算
  • 传入LLM获得自然语言解答
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import openai
import json
from enum import Enum

class BaseTool(Enum):
Bookkeeping = "record_price"
RecordingTask = "record_task"

# record_price是用来给Function Calling调用的函数,
# 这个函数接收两个必填的参数,category类目(string类型),count 数量(int类型)
def record_price(category, count):
print(category, count)
print("调用获取实时物价的 API")
return count*12

def funtion_call_conversation(message):
messages = [
{"role": "user", "content": "今天买了一斤肉,花了多少钱"}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages = message,
temperature=0,
functions=[
{
"name": BaseTool.Bookkeeping.value,
"description": "返回物品的数量",
"parameters": {
"type": "object",
"properties": {
"category": {"type": "string","description": "类目",},
"count": {"type": "integer", "description": "数量"},
},
"required": ["category","count"],
},
}
],
function_call="auto",
)
message = response["choices"][0]["message"]
print(message)
if(message.get("function_call")):
function_name = message["function_call"]["name"]
if function_name == BaseTool.Bookkeeping.value:
arguments = json.loads(message["function_call"]["arguments"])
price = record_price(arguments.get('category'), arguments.get('count'))
messages.append(message)
messages.append({"role": "function", "name": BaseTool.Bookkeeping.value, "content": price})
print(messages)
role_function_conversation(messages)

def role_function_conversation(message):
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages = message,
temperature=0,
# name字段表示要调用的函数名,description表示函数描述(让 LLM 读得懂的函数说明)
# paramters是一个符合JSON Schema格式的对象,用来描述这个函数的入参信息
functions=[
{
"name": BaseTool.Bookkeeping.value,
"description": "返回物品的数量",
"parameters": {
"type": "object",
"properties": {
"category": {"type": "string","description": "类目",},
"count": {"type": "integer", "description": "数量"},
},
"required": ["category","count"],
},
}
],
function_call="auto",
)
message = response["choices"][0]["message"].get("content")
print(message)


if __name__ == "__main__":
funtion_call_conversation()

执行结果解读

  1. LLM 分析后命中了函数签名描述,就会返回给我们 function_call 这个字段以及函数签名中我们预定义的字段信息:
  • category返回给了类目是猪肉
  • count返回数量是 1 (斤)
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "role": "assistant",
    "content": null,
    "function_call": {
    "name": "record_price",
    "arguments": "{\n \"category\": \"\u8089\",\n \"count\": 1\n}"
    }
    }
  1. 拿到参数,调用 record_price 进行费用统计的操作即可;如果没有命中函数签名描述,就不会返回function_call字段,也就不需要进行任何操作:

    1
    2
    3
    4
    5
    if(message.get("function_call")):
    function_name = message["function_call"]["name"]
    if function_name == BaseTool.Bookkeeping.value:
    arguments = json.loads(message["function_call"]["arguments"])
    price = record_price(arguments.get('category'), arguments.get('price'))
  2. 通过外部实时价格 API 获取到实时物价信息,将其作为rolefunction的数据,组合成最终的 messages列表,再次传入模型接口:

    1
    2
    3
    4
    5
    6
    7
    8
    [{'role': 'user', 'content': '今天买了一斤肉,花了多少钱'}, <OpenAIObject at 0x103365540> JSON: {
    "role": "assistant",
    "content": null,
    "function_call": {
    "name": "record_price",
    "arguments": "{\n \"category\": \"\u8089\",\n \"count\": 1\n}"
    }
    }, {'role': 'function', 'name': 'record_price', 'content': '12'}]
  3. 最后返回预期可控的结果 今天买了一斤肉,花了12元。

存在问题

  1. 函数描述是会被计入 token 的
  2. 潜在的风险:分析不准确,出现无法命中函数的情况

参考

OpenAI Function Calling 特性有什么用

https://liduos.com/openai-function-call-how-work.html

作者

莫尔索

发布于

2023-06-15

更新于

2024-05-19

许可协议

评论