My Little World

Function Calling

为什么要大模型连接外部世界?

大模型两大缺陷:

  1. 并非知晓一切
    a. 训练数据不可能什么都有。垂直、非公开数据必有欠缺
    b. 不知道最新信息。大模型的训练周期很长,且更新一次耗资巨大,还有越训越傻的风险。所以 ta 不可能实时训练。
    ⅰ. GPT-3.5 知识截至 2021 年 9 月
    ⅱ. GPT-4-turbo 知识截至 2023 年 12 月
    ⅲ. GPT-4o 知识截至 2023 年 10 月
  2. 没有「真逻辑」。它表现出的逻辑、推理,是训练文本的统计规律,而不是真正的逻辑,所以有幻觉。

所以:大模型需要连接真实世界,并对接真逻辑系统执行确定性任务。

ChatGPT 用 Actions 连接外部世界

划重点:

  1. 通过 Actions 的 schema,GPT 能读懂各个 API 能做什么、怎么调用(相当于人读 API 文档)
  2. 拿到 prompt,GPT 分析出是否要调用 API 才能解决问题(相当于人读需求)
  3. 如果要调用 API,生成调用参数(相当于人编写调用代码)
  4. ChatGPT(注意,不是 GPT)调用 API(相当于人运行程序)
  5. API 返回结果,GPT 读懂结果,整合到回答中(相当于人整理结果,输出结论)

把 AI 当人看!

Function Calling 的机制

原理和 Actions 一样,只是使用方式有区别。

Function Calling 完整的官方接口文档:
https://platform.openai.com/docs/guides/function-calling

Function Calling 机制图示

划重点:

  1. Function Calling 中的函数与参数的描述也是一种 Prompt
  2. 这种 Prompt 也需要调优,否则会影响函数的召回、参数的准确性,甚至让 GPT 产生幻觉
  3. 函数声明是消耗 token 的。要在功能覆盖、省钱、节约上下文窗口之间找到最佳平衡
  4. Function Calling 不仅可以调用读函数,也能调用写函数。但官方强烈建议,在写之前,一定要有真人做确认
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
from math import *

def get_completion(messages, model="gpt-3.5-turbo"):
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.7,
tools=[{ # 用 JSON 描述函数。可以定义多个。由大模型决定调用谁。也可能都不调用
"type": "function",
"function": {
"name": "sum",
"description": "加法器,计算一组数的和",
"parameters": {
"type": "object",
"properties": {
"numbers": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
}
}],
)
return response.choices[0].message

prompt = "Tell me the sum of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10."

messages = [
{"role": "system", "content": "你是一个数学家"},
{"role": "user", "content": prompt}
]
response = get_completion(messages)

# 把大模型的回复加入到对话历史中。必须有
messages.append(response)

# 如果返回的是函数调用结果,则打印出来
if (response.tool_calls is not None):
# 是否要调用 sum
tool_call = response.tool_calls[0]
if (tool_call.function.name == "sum"):
# 调用 sum
args = json.loads(tool_call.function.arguments)
result = sum(args["numbers"])

# 把函数调用结果加入到对话历史中
messages.append(
{
"tool_call_id": tool_call.id, # 用于标识函数调用的 ID
"role": "tool",
"name": "sum",
"content": str(result) # 数值 result 必须转成字符串
}
)

# 再次调用大模型
print("=====最终 GPT 回复=====")
print(get_completion(messages).content)

print("=====对话历史=====")
print_json(messages)

更多练习
本地单函数调用
本地多Function 调用(根据name 识别调用不同函数)
通过 Function Calling 查询单数据库
用 Function Calling 实现多表查询(把多表的描述给进去就好了)
Stream 模式(流式(stream)输出不会一次返回完整 JSON 结构,所以需要拼接后再使用,拿到什么就输出什么可减少用户等待时间,调用时client.chat.completions.create设置stream=True即可