OpenAI开发系列(十二):Function calling功能的流程优化与多轮对话实现

本文详细介绍了如何优化Functioncalling流程,通过让大模型自动生成函数的JSONSchema描述,提高了编写效率。同时,通过封装类实现了多轮对话中函数调用的自动化,简化了messages的拼接过程,提升了开发效率。

全文共7000余字,预计阅读时间约25~40分钟 | 满满干货(附代码),建议收藏!

本文目标:围绕Chat Completion模型的Function calling功能进行更高层次的函数封装,实现一个能够调用外部函数的多轮对话助理

写在前面:本文内容的复现过程,如果有条件的,建议使用gpt 4接口,输出稳定,gpt3.5不太稳定,但运行几次也能得到标准结果

如果存在Rate limit 报错,是OpenAI的速率限制,可以绑定信用卡后解除,以保证程序正常运行

image-20230825131304191

代码下载地址

一、Function calling 流程优化思路

OpenAI开发系列(十一):Function calling功能的实际应用流程与案例解析这篇文章中详细解释了Function calling的用法,当大模型激活Function calling功能时,其完整的推理流程应该是这样的:

5

当大模型激活Function Calling功能时,其推理过程也会发生相应的改变,即:根据大模型返回的函数和函数参数,在本地完成函数计算,然后再将计算过程和结果保存为message并追加到messages后面,并第二次调用Chat Completion模型分析函数的计算结果,并最终根据函数计算结果输出用户问题的答案。

尽管这种方式是有效的,对于目前的Function Calling实现流程,尽管步骤明确,但涉及多个代码环节以完成一个完整流程。在需要高频使用此功能的应用场景中,这种复杂性会显著降低使用和开发效率。因此,对该流程的优化是一定要做的事。

总的来说,可以想到的优化的方向有两个:提升functions参数的编写效率和优化不断拼接messages的这个过程。

还是使用上篇文章我创建的函数内容来说明,我稍微做了一些修改,在函数命名上及其对应的JSON Schema描述上分别使用后缀_function和_describe来更好的区分,代码如下:

# 1.定义功能函数
def calculate_total_age_function(input_json):
    """
    从给定的JSON格式字符串(按'split'方向排列)中解析出DataFrame,计算所有人的年龄总和,并以JSON格式返回结果。

    参数:
    input_json (str): 包含个体数据的JSON格式字符串。

    返回:
    str: 所有人的年龄总和,以JSON格式返回。
    """

    # 将JSON字符串转换为DataFrame
    df = pd.read_json(input_json, orient='split')

    # 计算所有人的年龄总和
    total_age = df['Age'].sum()

    # 将结果转换为字符串形式,然后使用json.dumps()转换为JSON格式
    return json.dumps({
   
   "total_age": str(total_age)})

# 2.将功能函数存储至外部函数仓库
function_repository = {
   
   
            "calculate_total_age_function": calculate_total_age_function,
        }


# 3.构建功能函数的Json Schema描述
calculate_total_age_describe = {
   
   "name": "calculate_total_age_function",
                      "description": "计算年龄总和的函数,从给定的JSON格式字符串(按'split'方向排列)中解析出DataFrame,计算所有人的年龄总和,并以JSON格式返回结果。",
                      "parameters": {
   
   "type": "object",
                                     "properties": {
   
   "input_json": {
   
   "type": "string",
                                                             "description": "执行计算年龄总和的数据集"},
                                                   },
                                     "required": ["input_json"],
                                    },
                     }


# 4. 添加到functions列表中,在对话过程中作为函数库传递给function参数
functions = [calculate_total_age_describe]

如果真的理解了OpenAI开发系列(十一):Function calling功能的实际应用流程与案例解析这篇文章的内容,上述代码应该很容易理解,此处就不重复说明了 。

上面这份代码虽然逻辑上没有什么问题,但其实存在两点问题:

  1. 一个外部函数库function_repository必然是包含大量不同的功能函数,逐个手工编写其对应的JSON Schema会非常复杂。
  2. 与模型的交互过程中,关心的是最后的输出结果,所以向大模型提问时中间有几次调用模型的过程,体现出来没有任何意义。

对于上述两点,我能想到的优化方式是:

  1. 通过提示工程,让大模型根据传入的功能函数代码自动编写出其Json Schema描述,创建想用的functions参数
  2. 以代码形式,将人工拼接的messages过程用函数做好封装,把中间过程作为一个黑箱子闭合起来

下面是我针对这两方面的优化过程。

二、优化一:如何实现functions参数值的自动构建

  • Step 1:明确需求

在请求大模型完成特定需求前,首先要确认其是否具备相应能力,这就像不可能指望一个小学生解决高考题一样。对于我的需求,我希望大模型能自动根据功能函数代码生成对应的JSON Schema描述。因此,首先我需要确认大模型是否理解什么是JSON Schema对象。

  • Step 2:确定大模型的知识储备
response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {
   
   "role": "user", "content": "你知道Json Schema对象吗?如果知道的话,请详细的描述一下"}
  ]
)

看下大模型的回复:

response.choices[0].message['content']

image-20230824145944981

可以明显看到,大模型对JSON Schema的结构的一些关键点还是非常清楚。

需要明确的是,生成JSON Schema对象描述主要依赖于两个关键因素:函数的说明文档和其代码逻辑,包括输入参数和返回值。虽然大模型在代码解析方面无疑是强大的,但生成准确的JSON Schema仍然受到函数文档清晰度和变量、返回值规范性的严格限制。

所以有理由相信:**只要详细的编写每个函数的函数说明,并且通过合理的提示让模型理解functions参数结构,同时借助模型本身对JSON Schema的理解,是能够让Chat Completions模型很好的解析功能函数并生成其标准的JSON Schema描述的。**这也是为什么我在上一篇文章中强调,编写功能函数时应遵循良好和规范的编程风格。

  • Step 3:提取功能函数的函数说明

在Python中,可以通过一个内置的inspect模块来提取函数的文档字符串(docstring)。

import inspect

# 使用inspect模块提取文档字符串
function_declaration = inspect.getdoc(calculate_total_age_function)

看下提取结果:

image-20230824151959493

  • Step 4: 编写提示词,生成函数对应的JSON Schema描述

根据功能函数的说明编写提示词,引导模型生成正确的JSON Schema描述,如下是我写的提示词:

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-16k-0613",
  messages=[
    {
   
   "role": "system", "content": "你是一位优秀的数据分析师,现在有一个函数的详细声明如下:%s" % function_declaration},
    {
   
   "role": "user", "content": "请根据这个函数声明,为我生成一个JSON Schema对象描述。这个描述应该清晰地标明函数的输入和输出规范。具体要求如下:\
                                1. 在JSON Schema对象中,设置函数的参数类型为'object'.\
                                2. 'properties'字段如果有参数,必须表示出字段的描述. \
                                3. 从函数声明中解析出函数的描述,并在JSON Schema中以中文字符形式表示在'description'字段.\
                                4. 识别函数声明中哪些参数是必需的,然后在JSON Schema的'required'字段中列出这些参数. \
                                5. 输出的应仅为符合上述要求的JSON Schema对象内容,不需要任何上下文修饰语句. "}
  ]
) 

看下大模型的输出结果:
image-20230824155659425

可以看出,大模型已经按照提示词的第5条要求仅输出了 JSON格式对象,可以通过json.loads方法将其转化为python对象,直观的看一下:

json.loads(response.choices[0].message['content'])

看下结果:

image-20230824160033842

通过对比手动编写的结果,两者是高度一致的,这就验证了模型能够根据函数的参数说明正确识别计算函数的参数格式,并输出对应的JSON Schema对象。

  • Step 5:利用Few-Shot提示法提升输出稳定性

一套精心设计的提示词能极大地提升模型输出的稳定性和准确性。尽管如此,即便在GPT-3.5或GPT-4的接口下,输出仍有可能出现不稳定的情况。为了增加输出稳定性,结合系统角色

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法小陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值