LLM系列 | 13: 亲测ChatGPT的重磅功能:函数调用 (以天气问答为例)

本文涉及的产品
大数据开发治理平台 DataWorks,不限时长
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,5000CU*H 3个月
简介: 本文以天气问答为例,实践方式介绍ChatGPT的函数调用

简介

春水碧于天,画船听雨眠。


春水碧于天,画船听雨眠.jpg

小伙伴们好,我是微信公众号《小窗幽记机器学习》的小编:卖五连鞭的小男孩。紧接前面几篇ChatGPT Prompt工程和应用系列文章:

更多、更新文章欢迎关注 微信公众号:小窗幽记机器学习。后续会持续整理模型加速、模型部署、模型压缩、LLM、AI艺术等系列专题,敬请关注。

今天这篇小作文以实战的方式介绍OpenAI最近升级的函数调用功能。

6月13日OpenAI重磅升级ChatGPT,主要包括新增 API函数调用、 更新和更可控制的gpt-4和gpt-3.5-turbo版本、新推出的gpt-3.5-turbo 支持16k的上下文输入、陆续开放gpt-4 API不再设置waitlist等等。今天这篇小作文主要着重介绍其中的 函数调用(Function calling) 功能。该功能一定程度上对LangChain构成威胁,新增的Function calling功能将使得LangChain里将近30%的代码可以删掉,且各个应用服务结果的稳定性将大大提升。当然,LangChain也迅速做出应对,在OpenAI官推发布更新的10分钟之内,LangChain立马宣布"已经在做兼容工作了",并且在不到一个小时就发布了新版本以支持OpenAI的这一新功能。此外,LangChain还提供将开发者已经写好的tools转换成OpenAI的functions的工具函数。关于LangChain的具体使用后续会补充介绍,小伙伴们可以关注留意下。

下面以天气问答为例,比如“今天深圳天气如何,适合爬山吗?”,介绍如何使用ChatGPT的API和函数调用实现一个天气问答服务。关于函数调用的更多细节可以查阅官方博客

准备工作

import json
import openai
import requests
import os
from tenacity import retry, wait_random_exponential, stop_after_attempt
from termcolor import colored

GPT_MODEL = "gpt-3.5-turbo-0613"
openai.api_key  = "sk-xxx"
os.environ['HTTP_PROXY'] = "xxx"
os.environ['HTTPS_PROXY'] = "xxx"

定义通用函数

@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, functions=None, function_call=None, model=GPT_MODEL):
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    json_data = {"model": model, "messages": messages}
    if functions is not None:
        json_data.update({"functions": functions})
        print("functions json_data=", json_data)
    if function_call is not None:
        json_data.update({"function_call": function_call})
        print("function_call json_data=", json_data)
    try:
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers=headers,
            json=json_data,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e

函数清单

将待调用的函数进行如下声明。其实就是接口定义,后续会根据description的语义描述选择对应的目标函数。

functions = [
    {
        "name": "get_current_weather",
        "description": "获取当前的天气",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "省市, 比如:深圳市",
                },
                "format": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,是摄氏温度还是华氏温度。从用户所在位置进行推断。",
                },
            },
            "required": ["location", "format"],
        },
    },
    {
        "name": "get_n_day_weather_forecast",
        "description": "获得未来N天的天气",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "省市, 比如:深圳市",
                },
                "format": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,是摄氏温度还是华氏温度。从用户所在位置进行推断。",
                },
                "num_days": {
                    "type": "integer",
                    "description": "预测的天数",
                }
            },
            "required": ["location", "format", "num_days"]
        },
    },
]

为进一步说明函数调用的使用,以下介绍2个版本,函数未定义真实定义函数之后ChatGPT的返回,小伙伴们记得对比两者的差异。

待调用函数未定义

使用函数中的默认地址

messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "今天的天气如何?"})
chat_response = chat_completion_request(
    messages, functions=functions
)
print("chat_response=", chat_response.json())
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print(assistant_message)

输出assistant_message结果如下:

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'get_current_weather',
  'arguments': '{\n  "location": "深圳市",\n  "format": "celsius"\n}'}}

可以看出,此时解析的结果是调用get_current_weather这个函数,但是由于该函数未定义,所以content字段为空。后文在定义待调用函数章节介绍。此外,如果在get_current_weather中默认城市改为北京,上述arguments字段中对应的location会自动变为北京

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'get_current_weather',
  'arguments': '{\n"location": "北京市",\n"format": "celsius"\n}'}}

应对信息缺失

如果咨询某个城市未来X天的天气,那么此时需要2个信息:城市和时间。

messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "深圳未来x天的天气如何?"})
chat_response = chat_completion_request(
    messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
assistant_message

输出结果如下:

{'role': 'assistant', 'content': '请问您要查询深圳未来几天的天气?'}

补充天数信息:

# 补充天数信息
messages.append({"role": "user", "content": "7天"})
chat_response = chat_completion_request(
    messages, functions=functions
)
print(chat_response.json()["choices"][0])

输出结果如下:

{'index': 0,
 'message': {'role': 'assistant',
  'content': None,
  'function_call': {'name': 'get_n_day_weather_forecast',
   'arguments': '{\n"location": "深圳市",\n"format": "celsius",\n"num_days": 7\n}'}},
 'finish_reason': 'function_call'}

同理,对于城市信息缺失也是如此处理。

查询目标城市未来天气

# 提供完整提供天数
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": "深圳未来5天的天气如何?"})
chat_response = chat_completion_request(
    messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print(assistant_message)

输出结果如下:

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'get_n_day_weather_forecast',
  'arguments': '{\n  "location": "深圳市",\n  "format": "celsius",\n  "num_days": 5\n}'}}

定义待调用函数

现在补充get_current_weather的定义。

待调用函数定义

# 补充 get_current_weather 的定义

# 随意实现了天气查询, 实际应该对接气象局api或者其他提供天气查询的api
def get_current_weather(location="北京市", format="celsius"):
    if location == "深圳市":
        return "龙舟雨,很大,超级大!温度20-25摄氏度"
    elif location == "北京市":
        return "风和日丽,阳光明媚!温度35-40摄氏度"
    return "安好,一切顺利,28摄氏度"


def get_current_weather_json(location="北京市", format="celsius"):
    if location == "深圳市":
        return { "temperature": 20, "unit": "celsius", "description": "龙舟雨,很大,超级大!温度20-25摄氏度"}
    elif location == "北京市":
        return { "temperature": 36, "unit": "celsius", "description": "风和日丽,阳光明媚!温度35-40摄氏度"}
    return {"temperature": 28, "unit": "celsius", "description": "安好,一切顺利,28摄氏度"}

test_text = "今天深圳天气如何,适合爬山吗?"
# 需要使用过程手动调用上述函数,再将结果加进入
messages = []
messages.append({"role": "system", "content": "请勿对要插入函数的值进行假设。如果用户的请求不明确,请要求澄清。"})
messages.append({"role": "user", "content": test_text})
chat_response = chat_completion_request(
    messages, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
messages.append(assistant_message)
print("assistant_message=",assistant_message)

输出结果如下:

assistant_message= {'role': 'assistant', 'content': None, 'function_call': {'name': 'get_current_weather', 'arguments': '{\n  "location": "深圳市",\n  "format": "celsius"\n}'}}

此时chat_response.json()["choices"][0]内容如下:

{'index': 0,
 'message': {'role': 'assistant',
  'content': None,
  'function_call': {'name': 'get_current_weather',
   'arguments': '{\n  "location": "深圳市",\n  "format": "celsius"\n}'}},
 'finish_reason': 'function_call'}

需要根据function_call的信息做函数调用判断,具体如下。

函数调用

full_chat_message = chat_response.json()["choices"][0]
print("full_chat_message=",full_chat_message)
fun_exec_result = ""
if full_chat_message["finish_reason"] == "function_call":
    # 判断 assistant_message["function_call"]["name"]执行不同函数
    if assistant_message["function_call"]["name"] == "get_current_weather":
        arguments = eval(assistant_message["function_call"]["arguments"])
        fun_exec_result = get_current_weather(arguments["location"])
        print("fun_exec_result=", fun_exec_result)
else:
    print("error, check")

if fun_exec_result:
    #将函数调用结果补充到会话中
    messages.append({"role": "user", "content": fun_exec_result})
    #再一次调用GPT
    next_chat_response = chat_completion_request(messages, functions=functions)
    next_full_chatmessage = next_chat_response.json()["choices"][0]
    print("next_full_chatmessage=", next_full_chatmessage)

输出结果如下:

next_full_chatmessage= {'index': 0, 'message': {'role': 'assistant', 'content': '根据当前天气情况,在深圳市天气很糟糕,下着大雨。温度在20-25摄氏度之间。这样的天气条件可能不适宜爬山,因为雨势大可能导致路滑、能见度低等不利条件。建议您等待天气好转后再考虑爬山。请注意安全。'}, 'finish_reason': 'stop'}

小结

这篇小作文以天气咨询为例,说明如何在使用ChatGPT过程中融合自定义函数的使用,从而为用户提供更多样的服务,类似插件。将自定义函数的结果添加到与ChatGPT对话的上下文中,从而具有更好的模块编排效果。这一定程度上是对三方LangChain能力的稀释,LangChain危矣+1!

更多、更新文章欢迎关注 微信公众号:小窗幽记机器学习。后续会持续整理模型加速、模型部署、模型压缩、LLM、AI艺术等系列专题,敬请关注。

相关实践学习
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
|
1月前
|
定位技术 UED
[亲测]解决ChatGPT邮箱注册失败,提示The email you provided is not supported
[亲测]解决ChatGPT邮箱注册失败,提示The email you provided is not supported
2731 0
|
10天前
|
存储 安全 算法
【chatgpt问答记录】双端队列、栈和函数调用栈
【chatgpt问答记录】双端队列、栈和函数调用栈
17 0
|
1月前
|
存储 人工智能 自然语言处理
OpenSearch LLM智能问答版全新升级
阿里云OpenSearch LLM智能问答版近期全新升级,新增最新版开源大模型、多模态模型、切片策略升级等产品能力。
1832 1
|
1月前
|
自然语言处理 机器人 Go
【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手
【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手
|
11天前
|
机器学习/深度学习 C++
【chatgpt问答记录】权重衰减vs正则化
【chatgpt问答记录】权重衰减vs正则化
15 2
|
11天前
|
机器学习/深度学习 自然语言处理 PyTorch
【chatgpt问答记录】前馈神经网络
【chatgpt问答记录】前馈神经网络
23 1
|
17天前
|
存储 缓存 安全
LLM应用实战:当图谱问答(KBQA)集成大模型(三)
本文主要是针对KBQA方案基于LLM实现存在的问题进行优化,主要涉及到响应时间提升优化以及多轮对话效果优化,提供了具体的优化方案以及相应的prompt。
261 1
|
1月前
|
Java API
java流式实现chatGPT会话功能
java流式实现chatGPT会话功能
80 1
|
1月前
|
人工智能 机器人 Android开发
ChatGPT新增朗读功能,可以语音播报生成结果
【2月更文挑战第15天】ChatGPT新增朗读功能,可以语音播报生成结果
84 1
ChatGPT新增朗读功能,可以语音播报生成结果
|
1月前
|
人工智能 API Python
ChatGPT系统课程 - 提示词的基本原则和使用场景之问答、提供样例、推理
ChatGPT系统课程 - 提示词的基本原则和使用场景之问答、提供样例、推理

热门文章

最新文章