LLM系列 | 11: LangChain危矣?亲测ChatGPT函数调用功能:以天气问答为例

简介

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

  • 04:ChatGPT Prompt编写指南
  • 05:如何优化ChatGPT Prompt?
  • 06:ChatGPT Prompt实践:文本摘要&推断&转换
  • 07:ChatGPT Prompt实践:以智能客服邮件为例
  • 08:ChatGPT Prompt实践:如何用ChatGPT构建点餐机器人?
  • 09:基于ChatGPT构建智能客服系统(query分类&安全审核&防注入)
  • 10:如何编写思维链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,langchain,chatgpt,人工智能,LLM,AI,NLP)