OpenAI | Function calling 上手体验

2023-11-22 10:55:26 浏览数 (1)

引言

LLM的基本使用方式是 text in -> text out , 输出是 text 。 但是很多时候,我们希望它能以固定的格式输出,以便解析成结构化的数据,传递给后续的处理逻辑。

在 OpenAI 发布 Function calling 之前,我们可能会议文本输入的方式,在Prompt中要求LLM格式化输出,或者通过LangChain框架提供的 Parsers 相关的抽象。现在,OpenAI 提供了 Function calling 用于将LLM的输出格式化成 Function calling 所需要的参数。

Function calling 介绍

简单的说, Function calling 就是基于(自定义)函数调用所需要的参数,输出可识别的格式化的输出。

在 API 调用中,我们可以描述一个函数(函数名、参数等),并使模型智能选择输出一个包含调用一个或多个函数的参数的 JSON 对象。 Chat Completions API 不会直接调用函数;它只会生成 JSON。我们可以在代码中使用该JSON对象调用我们描述的函数。

举个例子:

我们希望构建一个邮件小助手,可以根据用户的输入发送邮件。用户的输入类似”请向xxx@163.com发送邮件,内容是:请明天上午9:00到学校礼堂参加会议“。 发送邮件需要调用外部API,通常会在代码里封装这样的参数 send_email(to: string, body: string)

可以看到,send_email(to: string, body: string) 需要两个参数:收件人 to 和邮件内容 body 。 这两个参数需要用户的输入中提取。

我们描述好send_email(to: string, body: string) 并传递给 Chat Completions API ,它的返回内容会包含以下内容:

代码语言:javascript复制
function=Function(arguments='{"to": "xxx@163.com", "body": "请明天上午9:00到学校礼堂参加会议"}', name='send_email')

可以看到OpenAI从用户的文本输入中提取出了调用send_email 所需要的参数收件人 to 和邮件内容 body,并以JSON的形式赋值给了 arguments 字段。

Function calling 使用流程

我们以查询指定城市指定日期天气为例,介绍 Function calling 的使用流程,

天气查询使用高德的API。

定义函数

代码语言:javascript复制
def get_weather(city, date):
    base_url = "<https://restapi.amap.com/v3/weather/weatherInfo>"
    api_key =  os.getenv("AMAP_API_KEY") # 高德API的API-KEY从环境变量获取
    params = {
        'key': api_key,
        'city': city,
        'extensions': 'base',  # 可根据需求选择 'base' 或 'all'
        'output': 'json',
        'date': date  # 指定日期,格式如:2023-01-01
    }
    
    try:
        response = requests.get(base_url, params=params)
        data = response.json()
        logger.info(data) 
        if data['status'] == '1':
            weather_info = data['lives'][0]  # 如果选择了 'base' 扩展,使用 'lives';选择 'all' 扩展,使用 'forecasts'
            # logger.debug(f"城市: {weather_info['city']}")
            # logger.debug(f"日期: {date}")
            # logger.debug(f"天气: {weather_info['weather']}")
            # logger.debug(f"温度: {weather_info['temperature']} ℃")
            # logger.debug(f"风力: {weather_info['windpower']} 级")
            return json.dumps(weather_info)
        else:
            print("查询天气信息失败,错误码:"   data['infocode'])
    except Exception as e:
        print("查询天气信息时发生错误:"   str(e))

为 OpenAI 的API描述函数

Function calling 支持以数组形式传入多个函数描述。

代码语言:javascript复制
tools = [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "查询指定城市,指定日期的天气情况",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {
                            "type": "string",
                            "description": "要查询的城市名称,如:北京",
                        },
                        # "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                        "date": {
                            "type": "string",
                            "description": "要查询的日期,示例:2023-11-23", 
                        }
                    },
                    "required": ["city", "date"],
                },
            },
        }
    ]

completions API 提取JSON格式的参数

代码语言:javascript复制
client = OpenAI()

## 我们需要将用户的输入一并传递给OpenAI,以便从中提取函数调用所需要的参数。
messages = [
            {
                "role": "user", 
                "content": "深圳11月1号天气怎么样"
            }
        ]

### 第一次调用 Chat Completions API, 提取参数
response = client.chat.completions.create(
        model="gpt-3.5-turbo-1106",
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto is default, but we'll be explicit
    )

response_message = response.choices[0].message
tool_calls = response_message.tool_calls # 包含了函数调用所需要的信息

response里的tool_calls,包含了我们描述的函数相关信息,以及所需要的参数。

调用自定义函数

从前面的response中提取参数,并调用 get_weather(city, date):

代码语言:javascript复制
if tool_calls:
        tool_call = tool_calls[0]
        function_args = json.loads(tool_call.function.arguments)
        function_response = get_weather(
            city=function_args.get("city"),
            date=function_args.get("date"),
        )
        return function_response

输出结果:

代码语言:javascript复制
**(llm) ➜  function_calling python 01_function_calling.py
{"province": "\u5e7f\u4e1c", "city": "\u6df1\u5733\u5e02", "adcode": "440300", "weather": "\u6674", "temperature": "23", "winddirection": "\u4e1c\u5317", "windpower": "\u22643", "humidity": "67", "reporttime": "2023-11-22 10:31:09", "temperature_float": "23.0", "humidity_float": "67.0"}
(llm) ➜  function_calling**

0 人点赞