大模型系列:OpenAI使用技巧_在合成的函数调用数据上进行微调


这个笔记本介绍了如何进行微调以提高函数调用的准确性和可靠性。
您可以在 这里找到有关函数调用的更多信息,
以及有关微调的更多信息 here

为了提供背景,以上的函数调用笔记本中:

functions是聊天完成API中的可选参数,可用于提供函数规范。其目的是使模型能够生成符合提供规范的函数参数。请注意,API实际上不会执行任何函数调用。开发人员需要使用模型输出来执行函数调用。

函数调用是一个非常强大的工具,当它按预期工作时。然而,我们发现随着函数数量的增加和任务复杂性的增加,函数调用变得不太准确(例如:更多的幻觉调用和错误调用)。

在进行函数调用的微调之前,最好从以下几个方面开始:

  • 改进函数定义。使其更清晰,更与其他函数区分开来。
  • 尝试使用提示工程:通常更详细的提示可以帮助模型调用正确的函数。

如果上述步骤无法将函数调用改进到令人满意的水平,那么可以尝试进行函数调用的微调。

概述

本笔记本包含三个部分:

  • 评估基线函数调用性能: 在给定的函数上评估开箱即用的 gpt-3.5-turbo 模型(假设由于延迟和成本原因,我们不能将 gpt-4 用作无人机驾驶员)
  • 生成合成数据: 使用 gpt-4 创建“黄金”提示和函数调用集,用作训练数据
  • 微调: 运行微调作业,并评估微调模型

注意:本笔记本提供了一个示例,说明如何仅凭函数列表创建合成训练数据以进行函数调用微调。虽然实际生产测试评估更可取,但这种方法可以产生强大的结果,并可与实际训练数据一起使用。

获取基准函数调用性能

# !pip install tenacity
# !pip insta openai
# !pip install typing

# 导入所需的库和模块
import openai  # 导入openai库
import numpy as np  # 导入numpy库
import json  # 导入json库
import os  # 导入os库
from openai import OpenAI  # 导入OpenAI类
import itertools  # 导入itertools库
from tenacity import retry, wait_random_exponential, stop_after_attempt  # 导入tenacity库中的retry、wait_random_exponential和stop_after_attempt函数
from typing import Any, Dict, List, Generator  # 导入typing库中的Any、Dict、List和Generator类
import ast  # 导入ast库

# 创建OpenAI客户端实例
client = OpenAI()

实用工具

让我们定义实用函数来调用聊天完成 API,一个用于获取完成,另一个用于获取函数调用。

# 定义一个函数,函数名为get_chat_completion,接收三个参数:messages、model、max_tokens、temperature、stop和functions
def get_chat_completion(
    messages: list[dict[str, str]],
    model: str = "gpt-4",
    max_tokens=500,
    temperature=1.0,
    stop=None,
    functions=None,
) -> str:
    # 定义一个字典params,包含model、messages、max_tokens、temperature和stop这些键值对
    params = {
        'model': model,
        'messages': messages,
        'max_tokens': max_tokens,
        'temperature': temperature,
        'stop': stop,
    }
    # 如果有functions这个参数,将其加入params字典中
    if functions:
        params['tools'] = functions

    # 调用client.chat.completions.create方法,传入params字典作为参数,返回一个completion对象
    completion = client.chat.completions.create(**params)
    # 返回completion对象中的第一个choices元素的message属性
    return completion.choices[0].message

基准测试

让我们建立一个智能无人机副驾驶员。我们希望能够给副驾驶员下达指令,并让它调用该指令的函数,或者如果该指令不可行,则拒绝该请求。
我们可以首先为副驾驶员定义一个系统提示。

# 定义一个字符串常量,表示无人机系统的提示信息
DRONE_SYSTEM_PROMPT = """You are an intelligent AI that controls a drone. Given a command or request from the user,
call one of your functions to complete the request. If the request cannot be completed by your available functions, call the reject_request function.
If the request is ambiguous or unclear, reject the request."""

现在让我们为副驾驶可以执行的所有操作定义函数。

# 代码注释

# 定义一个函数列表,包含多个函数
function_list = [
    {
        "type": "function",
        "function": {
            "name": "takeoff_drone",
            "description": "Initiate the drone's takeoff sequence.",  # 启动无人机的起飞序列
            "parameters": {
                "type": "object",
                "properties": {
                    "altitude": {
                        "type": "integer",
                        "description": "Specifies the altitude in meters to which the drone should ascend.",  # 指定无人机应该上升到的高度(以米为单位)
                    }
                },
                "required": ["altitude"],  # 参数中必须包含altitude字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "land_drone",
            "description": "Land the drone at its current location or a specified landing point.",  # 将无人机降落在当前位置或指定的降落点
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "enum": ["current", "home_base", "custom"],
                        "description": "Specifies the landing location for the drone.",  # 指定无人机的降落位置
                    },
                    "coordinates": {
                        "type": "object",
                        "description": "GPS coordinates for custom landing location. Required if location is 'custom'.",  # 自定义降落位置的GPS坐标。如果location为'custom',则必填。
                    },
                },
                "required": ["location"],  # 参数中必须包含location字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "control_drone_movement",
            "description": "Direct the drone's movement in a specific direction.",  # 指导无人机朝特定方向移动
            "parameters": {
                "type": "object",
                "properties": {
                    "direction": {
                        "type": "string",
                        "enum": ["forward", "backward", "left", "right", "up", "down"],
                        "description": "Direction in which the drone should move.",  # 无人机应该移动的方向
                    },
                    "distance": {
                        "type": "integer",
                        "description": "Distance in meters the drone should travel in the specified direction.",  # 无人机应该在指定方向上行驶的距离(以米为单位)
                    },
                },
                "required": ["direction", "distance"],  # 参数中必须包含direction和distance字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_drone_speed",
            "description": "Adjust the speed of the drone.",  # 调整无人机的速度
            "parameters": {
                "type": "object",
                "properties": {
                    "speed": {
                        "type": "integer",
                        "description": "Specifies the speed in km/h.",  # 指定速度(以千米/小时为单位)
                    }
                },
                "required": ["speed"],  # 参数中必须包含speed字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "control_camera",
            "description": "Control the drone's camera to capture images or videos.",  # 控制无人机的相机以拍摄图像或视频
            "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["photo", "video", "panorama"],
                        "description": "Camera mode to capture content.",  # 拍摄内容的相机模式
                    },
                    "duration": {
                        "type": "integer",
                        "description": "Duration in seconds for video capture. Required if mode is 'video'.",  # 视频捕捉的持续时间(以秒为单位)。如果模式为'video',则必填。
                    },
                },
                "required": ["mode"],  # 参数中必须包含mode字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "control_gimbal",
            "description": "Adjust the drone's gimbal for camera stabilization and direction.",  # 调整无人机的云台以进行相机稳定和方向调整
            "parameters": {
                "type": "object",
                "properties": {
                    "tilt": {
                        "type": "integer",
                        "description": "Tilt angle for the gimbal in degrees.",  # 云台的俯仰角(以度为单位)
                    },
                    "pan": {
                        "type": "integer",
                        "description": "Pan angle for the gimbal in degrees.",  # 云台的水平角(以度为单位)
                    },
                },
                "required": ["tilt", "pan"],  # 参数中必须包含tilt和pan字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_drone_lighting",
            "description": "Control the drone's lighting for visibility and signaling.",  # 控制无人机的照明以提高可见性和信号传递
            "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["on", "off", "blink", "sos"],
                        "description": "Lighting mode for the drone.",  # 无人机的照明模式
                    }
                },
                "required": ["mode"],  # 参数中必须包含mode字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "return_to_home",
            "description": "Command the drone to return to its home or launch location.",  # 命令无人机返回到其家庭或起飞位置
            "parameters": {"type": "object", "properties": {}},  # 参数为空对象
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_battery_saver_mode",
            "description": "Toggle battery saver mode.",  # 切换电池节能模式
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle battery saver mode.",  # 切换电池节能模式
                    }
                },
                "required": ["status"],  # 参数中必须包含status字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_obstacle_avoidance",
            "description": "Configure obstacle avoidance settings.",  # 配置避障设置
            "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle obstacle avoidance.",  # 切换避障模式
                    }
                },
                "required": ["mode"],  # 参数中必须包含mode字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_follow_me_mode",
            "description": "Enable or disable 'follow me' mode.",  # 启用或禁用'follow me'模式
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle 'follow me' mode.",  # 切换'follow me'模式
                    }
                },
                "required": ["status"],  # 参数中必须包含status字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calibrate_sensors",
            "description": "Initiate calibration sequence for drone's sensors.",  # 启动无人机传感器的校准序列
            "parameters": {"type": "object", "properties": {}},  # 参数为空对象
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_autopilot",
            "description": "Enable or disable autopilot mode.",  # 启用或禁用自动驾驶模式
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle autopilot mode.",  # 切换自动驾驶模式
                    }
                },
                "required": ["status"],  # 参数中必须包含status字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "configure_led_display",
            "description": "Configure the drone's LED display pattern and colors.",  # 配置无人机的LED显示模式和颜色
            "parameters": {
                "type": "object",
                "properties": {
                    "pattern": {
                        "type": "string",
                        "enum": ["solid", "blink", "pulse", "rainbow"],
                        "description": "Pattern for the LED display.",  # LED显示的模式
                    },
                    "color": {
                        "type": "string",
                        "enum": ["red", "blue", "green", "yellow", "white"],
                        "description": "Color for the LED display. Not required if pattern is 'rainbow'.",  # LED显示的颜色。如果模式为'rainbow',则不需要。
                    },
                },
                "required": ["pattern"],  # 参数中必须包含pattern字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "set_home_location",
            "description": "Set or change the home location for the drone.",  # 设置或更改无人机的家庭位置
            "parameters": {
                "type": "object",
                "properties": {
                    "coordinates": {
                        "type": "object",
                        "description": "GPS coordinates for the home location.",  # 家庭位置的GPS坐标
                    }
                },
                "required": ["coordinates"],  # 参数中必须包含coordinates字段
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "reject_request",
            "description": "Use this function if the request is not possible.",  # 如果请求不可行,请使用此函数
            "parameters": {"type": "object", "properties": {}},  # 参数为空对象
        },
    },
]

首先,让我们看一下如何使用一些直接可行的提示进行函数调用,然后再尝试一个明显不可能的请求,调用’reject_request’函数。

# 定义一个列表,包含四个字符串,分别是无人机的指令
straightforward_prompts = ['Land the drone at the home base', 
                           'Take off the drone to 50 meters', 
                           'change speed to 15 kilometers per hour', 
                           'turn into an elephant!']
# 遍历直接的提示语料
for prompt in straightforward_prompts:
    # 创建一个空列表用于存储消息
    messages = []
    # 添加系统角色的消息,内容为DRONE_SYSTEM_PROMPT
    messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
    # 添加用户角色的消息,内容为当前的提示语料
    messages.append({"role": "user", "content": prompt})
    # 调用get_chat_completion函数,传入模型参数为"gpt-3.5-turbo",消息参数为messages,函数列表参数为function_list,并将返回值赋给completion变量
    completion = get_chat_completion(model="gpt-3.5-turbo", messages=messages, functions=function_list)
    # 打印当前的提示语料
    print(prompt)
    # 打印completion的tool_calls属性中的第一个元素的function属性
    print(completion.tool_calls[0].function, '\n')
Land the drone at the home base
Function(arguments='{\n  "location": "home_base"\n}', name='land_drone') 

Take off the drone to 50 meters
Function(arguments='{\n  "altitude": 50\n}', name='takeoff_drone') 

change speed to 15 kilometers per hour
Function(arguments='{\n  "speed": 15\n}', name='set_drone_speed') 

turn into an elephant!
Function(arguments='{}', name='reject_request') 

很好!这个模型在这些请求上表现得相当不错。现在让我们尝试一些更困难的请求:几乎可行且与无人机相关的请求,但无人机实际上无法执行,飞行员应该拒绝。

# 定义一个列表,包含了一些具有挑战性的任务
challenging_prompts = ['Play pre-recorded audio message',
                       'Initiate live-streaming on social media',
                       'Scan environment for heat signatures',
                       'Enable stealth mode',
                       "Change drone's paint job color"]
completion

ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_6rwNKuIgO7l8Izw5qoFcuSuk', function=Function(arguments='{}', name='reject_request'), type='function')])
# 遍历挑战性提示列表
for prompt in challenging_prompts:
  # 创建一个空列表用于存储消息
  messages = []
  # 向消息列表中添加系统提示
  messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
  # 向消息列表中添加用户提示
  messages.append({"role": "user", "content": prompt})
  # 调用get_chat_completion函数,获取聊天完成结果
  completion = get_chat_completion(model="gpt-3.5-turbo",messages=messages,functions=function_list)
  # 打印当前提示
  print(prompt)
  try:
    # 如果存在工具调用,则打印第一个工具调用的函数
    print(completion.tool_calls[0].function,'\n')
    print('\n')
  except:
    # 如果不存在工具调用,则打印第一个工具调用的内容
    print(completion.tool_calls[0].content,'\n')
    print('\n')
Play pre-recorded audio message
Function(arguments='{}', name='reject_request') 



Initiate live-streaming on social media
Function(arguments='{\n"mode": "video"\n}', name='control_camera') 



Scan environment for heat signatures
Function(arguments='{\n  "mode": "photo"\n}', name='control_camera') 



Enable stealth mode
Function(arguments='{\n  "pattern": "solid",\n  "color": "black"\n}', name='configure_led_display') 



Change drone's paint job color
Function(arguments='{\n  "pattern": "solid",\n  "color": "blue"\n}', name='configure_led_display') 

现在我们遇到了一些问题。
在这里,模型应该拒绝所有这些请求,因为根据功能,它们是不可能的,然而相反,模型调用了与请求有些相关但不正确的功能。当要求开始“向社交媒体直播”时,模型将摄像头设置为视频模式,并在要求“更改油漆颜色”时将LED灯的颜色更改为蓝色…


在这个简单的例子中,更多的提示工程可能会解决其中一些问题,但为了说明微调如何提高性能,我们将演示如何使用微调。此外,随着功能数量和复杂性的增加,微调的影响也越来越大。

生成合成数据

辅助函数

我们想要生成每个函数的每个调用,以便我们拥有所有潜在调用的完全覆盖,以创建合成数据。然后,我们将使用 gpt-4 来提供调用每个调用的提示,并将该提示 - 函数调用对用作训练数据。

使用固定枚举为函数生成每个调用更加简单,但对于像 control_gimbal 这样的函数,我们需要设置 tiltpan 整数值,因此为了生成这些合成调用,我们将首先设置一个占位符,然后稍后使用 gpt-4 来提出合理的值。

# 创建一个整数类型的占位符变量,用于填充整数值
placeholder_int = 'fill_in_int'

# 创建一个字符串类型的占位符变量,用于填充字符串值
placeholder_string = 'fill_in_string'

以下函数接受函数列表中的所有函数,并查看每个函数参数的所有潜在调用。这些函数还考虑了“required”参数,以确保所有调用都是可行的。

# 生成所有可能的参数排列组合

## generate_permutations函数
### 参数:params(参数字典,包含必需和可选字段)
### 返回:一个生成器,每次生成一个排列组合

### 从参数中提取必需字段
### required_fields = params中的'required'字段,如果没有'required'字段,则为空列表

### 生成必需字段的排列组合
### required_permutations = 调用generate_required_permutations函数,传入params和required_fields作为参数

### 根据每个必需字段的排列组合生成可选字段的排列组合
### 遍历required_permutations中的每个必需字段的排列组合
### 调用generate_optional_permutations函数,传入params和当前必需字段的排列组合作为参数
### 使用yield from语句将生成器的结果返回给调用者
def generate_permutations(params: Dict[str, Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:

    required_fields = params.get('required', [])


    required_permutations = generate_required_permutations(params, required_fields)

    for required_perm in required_permutations:
        yield from generate_optional_permutations(params, required_perm)


## generate_required_permutations函数
### 参数:params(参数字典),required_fields(必需字段列表)
### 返回:必需字段的排列组合列表

### 获取每个必需字段的所有可能值
### required_values = 调用get_possible_values函数,传入params和每个必需字段作为参数,得到的结果组成的列表

### 从可能值中生成排列组合
### 使用itertools.product函数,传入required_values作为参数,得到的结果是一个生成器
### 使用dict(zip(required_fields, values))将必需字段和对应的值组成字典,得到的结果组成的列表
def generate_required_permutations(params: Dict[str, Dict[str, Any]], required_fields: List[str]) -> List[Dict[str, Any]]:

    required_values = [get_possible_values(params, field) for field in required_fields]

    return [dict(zip(required_fields, values)) for values in itertools.product(*required_values)]


## generate_optional_permutations函数
### 参数:params(参数字典),base_perm(基本排列组合字典)
### 返回:一个生成器,每次生成一个可选字段的排列组合

### 确定可选字段,通过从所有属性中减去基本排列组合的字段得到
### optional_fields = params中的'properties'字段减去base_perm的字段

### 遍历所有可选字段的组合
### 使用itertools.combinations函数,传入optional_fields和r(从0到可选字段数量的范围)作为参数,得到的结果是一个生成器
### 使用itertools.chain.from_iterable函数将生成器展开成一个迭代器

#### 生成当前字段组合的可能值的乘积
#### 使用itertools.product函数,传入get_possible_values函数和field_subset作为参数,得到的结果是一个生成器

##### 创建一个新的排列组合,将基本排列组合和当前字段的值组成字典
##### new_perm = {**base_perm, **dict(zip(field_subset, values))}

##### 使用yield语句将新的排列组合返回给调用者

def generate_optional_permutations(params: Dict[str, Dict[str, Any]], base_perm: Dict[str, Any]) -> Generator[Dict[str, Any], None, None]:


    optional_fields = set(params['properties']) - set(base_perm)

    for field_subset in itertools.chain.from_iterable(itertools.combinations(optional_fields, r) for r in range(len(optional_fields) + 1)):

        for values in itertools.product(*(get_possible_values(params, field) for field in field_subset)):

            new_perm = {**base_perm, **dict(zip(field_subset, values))}

            yield new_perm
            
## get_possible_values函数
### 参数:params(参数字典),field(要获取可能值的字段)
### 返回:可能值的列表

### 从参数中提取字段的信息
### field_info = params中的'properties'字段的field字段

### 根据字段的类型或'enum'的存在确定并返回可能值
### 如果field_info中存在'enum'字段,则返回field_info['enum']
### 否则,根据field_info['type']的值确定并返回可能值
### 如果field_info['type']是'integer',则返回[placeholder_int]
### 如果field_info['type']是'string',则返回[placeholder_string]
### 如果field_info['type']是'boolean',则返回[True, False]
### 如果field_info['type']是'array'且'enum'在field_info['items']中存在,则返回所有可能的组合
### 使用itertools.combinations函数,传入field_info['items']['enum']和i(从1到enum值的数量的范围)作为参数,得到的结果是一个生成器
### 使用list(combo)将生成器的结果转换为列表
### 返回所有可能的组合的列表
### 如果以上条件都不满足,则返回空列表

def get_possible_values(params: Dict[str, Dict[str, Any]], field: str) -> List[Any]:

    field_info = params['properties'][field]

    if 'enum' in field_info:
        return field_info['enum']
    elif field_info['type'] == 'integer':
        return [placeholder_int]
    elif field_info['type'] == 'string':
        return [placeholder_string]
    elif field_info['type'] == 'boolean':
        return [True, False]
    elif field_info['type'] == 'array' and 'enum' in field_info['items']:
        enum_values = field_info['items']['enum']
        all_combinations = [list(combo) for i in range(1, len(enum_values) + 1) for combo in itertools.combinations(enum_values, i)]
        return all_combinations
    return []

让我们先为每个函数生成每个调用。

Prompts:

# 定义了一个字符串常量INVOCATION_FILLER_PROMPT,用于填充函数调用的示例和说明
# 字符串中包含了一个占位符{invocation},用于接收函数调用的示例
# 字符串中包含了一个占位符{function},用于接收函数的定义
INVOCATION_FILLER_PROMPT = """
1) Input reasonable values for 'fill_in_string' and 'fill_in_int' in the invocation here: {invocation}. Reasonable values are determined by the function definition. Use the
the entire function provided here :{function} to get context over what proper fill_in_string and fill_in_int values would be.
Example:

Input: invocation: {{
    "name": "control_camera",
    "arguments": {{
      "mode":"video",
      "duration":"fill_in_int"
    }}
}},
function:{function}

Output: invocation: {{
    "name": "control_camera",
    "arguments": {{
      "mode":"video",
      "duration": 30
    }}
}}


MAKE SURE output is just a dictionary with keys 'name' and 'arguments', no other text or response.

Input: {invocation}
Output:
"""

# 定义了一个字符串常量COMMAND_GENERATION_PROMPT,用于生成与函数和参数相关的命令、问题或陈述
# 字符串中包含了一个占位符{invocation},用于接收函数调用的示例
COMMAND_GENERATION_PROMPT= """
You are to output 2 commands, questions or statements that would generate the inputted function and parameters.
Please make the commands or questions natural, as a person would ask, and the command or questions should be varied and not repetitive.
It should not always mirror the exact technical terminology used in the function and parameters, rather reflect a conversational and intuitive request.
For instance, the prompt should not be 'turn on the dome light', as that is too technical, but rather 'turn on the inside lights'.
Another example, is the prompt should not be 'turn on the HVAC', but rather 'turn on the air conditioning'. Use language a normal driver would use, even if
it is technically incorrect but colloquially used.

RULES: ALWAYS put a backwards slash before an apostrophe or single quote '. For example, do not say don't but say don\'t.
Prompts MUST be in double quotes as well.

Example

Input: {{'name': 'calibrate_sensors','arguments': {{}}'' }}
Prompt: ["The sensors are out of whack, can you reset them", "The calibration of the drone is off, fix it please!"]

Input: {{'name': 'set_autopilot','arguments': {{'status': 'off'}}}}
Prompt: ["OK, I want to take back pilot control now","Turn off the automatic pilot I'm ready control it"]

Input: {invocation}
Prompt:
"""

在下面的代码片段中,我们生成每个函数的调用,除了rejection_request函数。为了进行有效的微调,我们需要正确标记的数据。我们可以手动编写示例并标记数据,或者我们可以借助gpt-4生成合成数据。从经验上看,gpt-4需要更多的帮助来获得生成reject_request函数的良好逼真示例,因此我们将在下一步中完成这个任务…

# 初始化变量
input_objects = []  # 存储输入对象的列表
all_but_reject = [f for f in function_list if f.get('name') != 'reject_request']  # 从function_list中筛选出除'reject_request'外的所有函数

# 遍历所有函数(除'reject_request'外)
for function in all_but_reject:
    func_name = function['function']['name']  # 获取函数名
    params = function['function']['parameters']  # 获取函数参数

    # 生成参数的所有排列组合,并遍历
    for arguments in generate_permutations(params):
        # 如果参数中包含'fill_in_int'或'fill_in_str'
        if any(val in arguments.values() for val in ['fill_in_int', 'fill_in_str']):
            # 创建输入对象
            input_object = {
                "name": func_name,
                "arguments": arguments
            }
            # 创建消息列表,包含用户角色和内容,其中内容使用格式化字符串填充
            messages = [{"role": "user", "content": INVOCATION_FILLER_PROMPT.format(invocation=input_object,function=function)}]
            # 调用get_chat_completion函数,获取聊天补全结果,并将结果赋值给input_object
            input_object = get_chat_completion(model='gpt-4',messages=messages, max_tokens=200,temperature=.1).content
        else:
            # 创建输入对象
            input_object = {
                "name": func_name,
                "arguments": arguments
            }

        # 将输入对象添加到input_objects列表中
        input_objects.append(input_object)

现在我们已经拥有了所有的调用,让我们使用gpt-4来生成能够产生这些调用的提示。

# 定义一个函数create_commands,传入参数invocation_list
def create_commands(invocation_list):
    # 定义一个空列表example_list
    example_list = []
    # 使用enumerate函数遍历invocation_list,i为索引,invocation为元素
    for i, invocation in enumerate(invocation_list):
        # 打印进度条,使用round函数保留一位小数
        print(f'\033[34m{np.round(100*i/len(invocation_list),1)}% complete\033[0m')
        # 打印当前invocation
        print(invocation)

        # 格式化提示信息,将invocation插入到COMMAND_GENERATION_PROMPT中
        request_prompt = COMMAND_GENERATION_PROMPT.format(invocation=invocation)

        # 定义一个字典列表messages,包含一个字典元素,role为user,content为request_prompt
        messages = [{"role": "user", "content": f"{request_prompt}"}]
        # 调用get_chat_completion函数,传入messages和temperature参数,获取completion
        completion = get_chat_completion(messages,temperature=0.8).content
        # 定义一个字典command_dict,包含Input和Prompt两个键值对
        command_dict = {
            "Input": invocation,
            "Prompt": completion
        }
        # 将command_dict添加到example_list中
        example_list.append(command_dict)
    # 返回example_list
    return example_list
# 创建训练样本,输入参数为input_objects
training_examples_unformatted = create_commands(input_objects)
[34m0.0% complete[0m
{'name': 'takeoff_drone', 'arguments': {'altitude': 100}}
[34m1.8% complete[0m
{'name': 'land_drone', 'arguments': {'location': 'current'}}
[34m3.5% complete[0m
{'name': 'land_drone', 'arguments': {'location': 'home_base'}}
[34m5.3% complete[0m
{'name': 'land_drone', 'arguments': {'location': 'custom'}}
[34m7.0% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'forward', 'distance': 50}}
[34m8.8% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'backward', 'distance': 10}}
[34m10.5% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'left', 'distance': 10}}
[34m12.3% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'right', 'distance': 10}}
[34m14.0% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'up', 'distance': 10}}
[34m15.8% complete[0m
{'name': 'control_drone_movement', 'arguments': {'direction': 'down', 'distance': 10}}
[34m17.5% complete[0m
{'name': 'set_drone_speed', 'arguments': {'speed': 20}}
[34m19.3% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'photo'}}
[34m21.1% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'photo', 'duration': 0}}
[34m22.8% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'video'}}
[34m24.6% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'video', 'duration': 60}}
[34m26.3% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'panorama'}}
[34m28.1% complete[0m
{'name': 'control_camera', 'arguments': {'mode': 'panorama', 'duration': 0}}
[34m29.8% complete[0m
{'name': 'control_gimbal', 'arguments': {'tilt': 45, 'pan': 90}}
[34m31.6% complete[0m
{'name': 'set_drone_lighting', 'arguments': {'mode': 'on'}}
[34m33.3% complete[0m
{'name': 'set_drone_lighting', 'arguments': {'mode': 'off'}}
[34m35.1% complete[0m
{'name': 'set_drone_lighting', 'arguments': {'mode': 'blink'}}
[34m36.8% complete[0m
{'name': 'set_drone_lighting', 'arguments': {'mode': 'sos'}}
[34m38.6% complete[0m
{'name': 'return_to_home', 'arguments': {}}
[34m40.4% complete[0m
{'name': 'set_battery_saver_mode', 'arguments': {'status': 'on'}}
[34m42.1% complete[0m
{'name': 'set_battery_saver_mode', 'arguments': {'status': 'off'}}
[34m43.9% complete[0m
{'name': 'set_obstacle_avoidance', 'arguments': {'mode': 'on'}}
[34m45.6% complete[0m
{'name': 'set_obstacle_avoidance', 'arguments': {'mode': 'off'}}
[34m47.4% complete[0m
{'name': 'set_follow_me_mode', 'arguments': {'status': 'on'}}
[34m49.1% complete[0m
{'name': 'set_follow_me_mode', 'arguments': {'status': 'off'}}
[34m50.9% complete[0m
{'name': 'calibrate_sensors', 'arguments': {}}
[34m52.6% complete[0m
{'name': 'set_autopilot', 'arguments': {'status': 'on'}}
[34m54.4% complete[0m
{'name': 'set_autopilot', 'arguments': {'status': 'off'}}
[34m56.1% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid'}}
[34m57.9% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'red'}}
[34m59.6% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'blue'}}
[34m61.4% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'green'}}
[34m63.2% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'yellow'}}
[34m64.9% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'white'}}
[34m66.7% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink'}}
[34m68.4% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'red'}}
[34m70.2% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'blue'}}
[34m71.9% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'green'}}
[34m73.7% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'yellow'}}
[34m75.4% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'white'}}
[34m77.2% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse'}}
[34m78.9% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'red'}}
[34m80.7% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'blue'}}
[34m82.5% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'green'}}
[34m84.2% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'yellow'}}
[34m86.0% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'white'}}
[34m87.7% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow'}}
[34m89.5% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'red'}}
[34m91.2% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'blue'}}
[34m93.0% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'green'}}
[34m94.7% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'yellow'}}
[34m96.5% complete[0m
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'white'}}
[34m98.2% complete[0m
{'name': 'reject_request', 'arguments': {}}

现在让我们正确地格式化训练示例。有关用于函数调用微调的正确训练数据格式的更多文档,请参阅此处:https://platform.openai.com/docs/guides/fine-tuning/fine-tuning-examples

# 定义一个空列表,用于存储训练样本
training_examples = []

# 遍历未格式化的训练样本
for prompt in training_examples_unformatted:
    # 尝试将输入转换为字面值表达式
    try:
        prompt["Input"] = ast.literal_eval(prompt["Input"])
    # 如果无法转换,则跳过该样本
    except:
        continue
    # 将输入参数转换为JSON格式
    prompt['Input']['arguments']=json.dumps(prompt['Input']['arguments'])
    # 遍历提示语句
    for p in prompt['Prompt']:
        # 将提示语句、输入参数和系统提示语句添加到训练样本中
        training_examples.append({"messages": [{"role":"system","content":DRONE_SYSTEM_PROMPT
                                        },{"role":"user","content": p},
                            {"role":"assistant","function_call": prompt['Input']}],
                            "functions":[func['function'] for func in function_list]})

现在,回到拒绝函数。让我们生成一些几乎可能的提示,但应该导致调用decline_request函数。为了做到这一点,我们查询了gpt-4,要求与给定的函数列表相关但不完全可能的请求。

# 定义一个拒绝列表,包含了一些无法实现的功能
reject_list = ['Translate broadcast message to another language',  # 将广播信息翻译成其他语言
               'Automatically capture photos when face is detected',  # 当检测到人脸时自动拍照
               'Detect nearby drones',  # 检测附近的无人机
               'Measure wind resistance',  # 测量风阻
               'Capture slow motion video',  # 拍摄慢动作视频
               "Adjust drone's altitude to ground level changes",  # 根据地面高度变化调整无人机的高度
               'Display custom message on LED display',  # 在LED显示屏上显示自定义信息
               "Sync drone's time with smartphone",  # 将无人机的时间与智能手机同步
               'Alert when drone travels out of designated area',  # 当无人机飞出指定区域时发出警报
               'Detect moisture levels',  # 检测湿度水平
               'Automatically follow GPS tagged object',  # 自动跟随GPS标记的物体
               'Toggle night vision mode',  # 切换夜视模式
               'Maintain current altitude when battery is low',  # 当电池电量低时保持当前高度
               'Decide best landing spot using AI',  # 使用AI决定最佳着陆点
               "Program drone's route based on wind direction"]  # 根据风向规划无人机的路线
# 创建一个空列表reject_training_list,用于存储拒绝训练的数据
reject_training_list = []

# 遍历reject_list中的每个元素,将其格式调整后添加到reject_training_list中
for prompt in reject_list:
    # 调整格式,将prompt添加到messages列表中的user角色中,将DRONE_SYSTEM_PROMPT添加到messages列表中的system角色中
    # 将{"name": "reject_request","arguments": "{}"}添加到messages列表中的assistant角色中的function_call中
    # 将function_list中的每个元素的function添加到functions列表中
    reject_training_list.append({
        "messages": [
            {"role": "system", "content": DRONE_SYSTEM_PROMPT},
            {"role": "user", "content": prompt},
            {"role": "assistant", "function_call": {"name": "reject_request", "arguments": "{}"}}
        ],
        "functions": [func['function'] for func in function_list]
    })

现在将所有的训练样例组合在一起。

# 将训练样本和拒绝训练列表合并为一个总的训练列表
training_list_total = training_examples+reject_training_list
# 定义训练数据文件路径
training_file = 'data/drone_training.jsonl'

# 打开训练数据文件,以写入模式打开
with open(training_file, 'w') as f:
    # 遍历训练数据列表
    for item in training_list_total:
        # 将数据转换为json字符串
        json_str = json.dumps(item)
        # 将json字符串写入文件,并在末尾添加换行符
        f.write(f'{json_str}\n')

微调

最终,我们可以开始微调工作。

# 导入所需的模块和库

# 判断当前模块是否为主模块
if __name__ == "__main__":
    # 创建一个文件对象,并将训练文件以二进制形式打开
    file = client.files.create(
        file=open(training_file, "rb"),
        purpose="fine-tune",
    )
    # 获取文件的唯一标识符
    file_id = file.id
    # 打印文件的唯一标识符
    print(file_id)
    # 创建一个fine-tuning任务
    ft = client.fine_tuning.jobs.create(
        # 指定模型为"gpt-3.5-turbo"
        model="gpt-3.5-turbo",
        # 指定训练文件的唯一标识符
        training_file=file_id,
    )
file-CGMggG5iZYKTocwgCp7kV7C6

评估

太好了!我们为函数调用训练了一个微调模型。让我们看看它在我们的评估集上的表现,以便无人机助手自动拒绝提示。

# 遍历挑战性问题列表
for eval_question in challenging_prompts:
  # 创建一个空列表用于存储消息
  messages = []
  # 在消息列表中添加一个系统角色的消息,内容为 DRONE_SYSTEM_PROMPT
  messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
  # 在消息列表中添加一个用户角色的消息,内容为当前遍历到的挑战性问题
  messages.append({"role": "user", "content": eval_question})
  # 调用 get_chat_completion 函数,传入模型、消息列表和函数列表作为参数,返回一个聊天完成对象
  completion = get_chat_completion(model="ft:gpt-3.5-turbo-0613:openai-internal::8DloQKS2",messages=messages,functions=function_list)
  # 打印当前遍历到的挑战性问题
  print(eval_question)
  # 打印聊天完成对象中第一个工具调用的函数名,并换行
  print(completion.tool_calls[0].function.name,'\n')
Play pre-recorded audio message
reject_request 

Initiate live-streaming on social media
reject_request 

Scan environment for heat signatures
reject_request 

Enable stealth mode
reject_request 

Change drone's paint job color
reject_request 

太好了!原始模型只拒绝了5个请求中的1个,而经过优化的模型则拒绝了全部5个请求。

结论

恭喜!您现在已经准备好为函数调用微调您的模型了。我们迫不及待地想看看您会构建什么。

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

数智笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值