GPT function calling v2

news2024/11/18 17:38:58

原文:GPT function calling v2 - 知乎

OpenAI在2023年11月10号举行了第一次开发者大会(OpenAI DevDays),其中介绍了很多新奇有趣的新功能和新应用,而且更新了一波GPT的API,在1.0版本后的API调用与之前的0.X版本有了比较大的更新,尤其是GPT的function calling这个重要功能,所以这篇文章就来具体介绍如何使用新发布的API来实现function calling。虽然OpenAI在最新的API文档中将function calling改称为tools calling,但其实二者的差异不大,所以本文也还是继续使用function calling这个词来做相关的说明。

关于1.0版本之前的API使用,可以参考本人之前写的一篇文章,里面包含function calling的基本原理,流程和简单应用。

间断的连续:GPT function calling6 赞同 · 0 评论文章​编辑

1. Openai API basic

以openai最常使用的chat API而言,以下的代码片段能够直观地体现如何利用新的API来实现简单的交流:

import os
import json
import loguru
from openai import OpenAI

# Load from json configuration file
CONFIG_FILE = "configs/config.json"
API_KEY_TERM = "opeanai_api_key"
MODEL_TERM = "openai_chat_model"

try:
    with open(CONFIG_FILE) as f:
        configs = json.load(f)
    API_KEY = configs[API_KEY_TERM]
    MODEL = configs[MODEL_TERM]
except FileNotFoundError:
    loguru.logger.error(f"Configuration file {CONFIG_FILE} not found")

# Load from env variables
# API_KEY = os.environ.get("OPENAI_API_KEY")
# MODEL = "gpt-3.5-turbo-1106"

# Create new OpenAI client object
client = OpenAI(api_key=API_KEY)

# Get the response from OpenAI with system and user prompt
response = client.chat.completions.create(
    model = MODEL, 
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Introduce yourself."}
    ]
)

# Extract response messages
print(response.choices[0].message.content)

API_KEY和chat模型可以从一个JSON配置文件中读取,或者从环境变量中获取(这个完全取决于个人的开发习惯)。之后创建OpenAI客户端对象。使用这个client对象来使用chat.completions.create来向OpenAI发送消息。在得到回复之后,使用response.choices[0].message.content来抽取信息。在终端运行得到的结果如下:

2. Function calling

2.1. function calling recap

简单来说,function calling是让GPT或者是大语言模型能够使用外部工具的能力。在API调用中,用户可以向gpt-3.5或者gpt-4.0描述需要调用的函数声明(function declaration)包括函数的名称和函数所需的参数,然后让模型智能选择输出一个包含调用函数的JSON对象。模型之后会生成一个JSON文件,用户可以在代码中用来调用该函数。

换句话说就是GPT虽然不能直接访问和使用外部数据源或者工具,但GPT能够根据语境知道何时需要访问外部资源,而且能够生成符合满足API调用的格式文件(一般为JSON文件),让用户可以在自己的代码中利用生成的格式文件和声明好的函数根据语境自动实现某种功能,如写邮件,网络搜索或者是实时天气查询。

2.2. Single function calling

针对简单的任务,可以使用单一的function calling来实现,如实现某个文件夹中的文件查询,实现如下。

首先得先有一个用于查询特定文件的函数实现。这里有一个实现细节,那就是return的files要转类型至string,因为GPT目前只能识别和处理信息中的文本和字符串,不能处理诸如列表,数组和字典等数据结构。

def list_files_in_directory(directory: str):
    try:
        files = os.listdir(directory)
        return str(files) if files else "The directory is empty."
    except FileNotFoundError:
        print(f"Directory '{directory}' not found")
        return []

之后需要将该函数的签名,所需变量和返回值格式化为一个JSON格式:

tools = [
    {
        "type": "function",
        "function": {
            "name": "list_files_in_directory",
            "description": "List all files in a directory",
            "parameters": {
                "type": "object",
                "properties": {
                    "directory": {
                        "type": "string",
                        "description": "The name of directory to list files in"
                    },
                },
                "required": ["directory"],
            },
        },
    }
]

需要注意的是如果函数中不包含任何参数,也没有任何返回值,则可以写成以下格式:

tools = [
    {
        "type": "function",
        "function": {
            "name": "YOUR FUNCTION NAME”,
            "description": "YOUR FUNCTION DESCRIPTION",
            "parameters" : {"type": "object", "properties": {}}
        }
    },
]

对于不包含任何参数和返回值的function calling其实可以作为一种柔性的条件判断来使用,也就是说它可以用于检测是否触发了某种意图如结束谈话或者是打招呼等。

通过函数实现和定义好的JSON格式,GPT就可以在函数调用时正确地生成相对应的参数,之后就可以调用GPT的API来实现工具调用了。

messages = [
    {"role": "system", "content": "You are a friendly chatbot that can use external tools to offer reliable assistance to human beings."},
    {"role": "user", "content": "List all the files in a directory in '../tools'."},
]

response = client.chat.completions.create(
    model=OPENAI_CHAT_MODEL,
    messages=messages,
    tools=tools,
)

messages.append(response.choices[0].message)

此时,GPT返回的message中文本的内容是None,而是tool_calls则会包含需要调用的函数名和参数(与之前写好的函数实现是一致)。

ChatCompletion(id='chatcmpl-8cGeaG5CErdDUYDU2b5Pl4zZnKhXf', choices=[Choice(finish_reason='tool_calls', index=0, message=ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_k3GrVfDAt78erknnB8q1JNI3', function=Function(arguments='{"directory":"../tools"}', name='list_files_in_directory'), type='function')]), logprobs=None)], created=1704131172, model='gpt-3.5-turbo-1106', object='chat.completion', system_fingerprint='fp_772e8125bb', usage=CompletionUsage(completion_tokens=17, prompt_tokens=88, total_tokens=105))

至此,GPT需要得到函数执行后的结果来得到最后的response。也就是说需要得到函数执行后的实际结果并再次发送给GPT来最终生成回复。在这个过程中,需要不断地将得到的中间结果加入到message中以此来构筑上下文,否则GPT会返回Bad Request error。

available_tools = {
    "list_files_in_directory": list_files_in_directory,
}

tool_calls = response.choices[0].message.tool_calls

for tool_call in tool_calls:
    function_name = tool_call.function.name
    function_to_call = available_tools[function_name]
    function_args = json.loads(tool_call.function.arguments)
    function_response = function_to_call(**function_args)

    messages.append(
        {
            "tool_call_id": tool_call.id,
            "role": "tool",
            "name": function_name,
            "content": function_response,
        }
    )

之后发送新的message得到返回结果

second_response = client.chat.completions.create(
    model=OPENAI_CHAT_MODEL,
    messages=messages,
    tools=tools,
)

print(second_response.choices[0].message.content)

针对以下对应的文件目录得到的结果如下:

工程目录结构

GPT能够识别到给定目录下的文件

2.3. Sequential function calling

对于一些任务,仅使用一次function calling可能并不能实现最终的效果,如在上述的文件查询之后要求GPT能够去执行文件夹中的特定文件,从而得到某种结果。如果该文件不存在,就需要GPT自己去编写并且保存在该文件目录中,再去调用来得到结果。这就需要GPT能够多次调用不同的functions来实现最终的功能。这里可以用过while循环来实现,只有当finishi_resaon为“stop”的时候才停止生成。这里给出一种实现方式:

def list_files_in_directory(directory: str):
    try:
        files = os.listdir(directory)
        return str(files) if files else "The directory is empty."
    except FileNotFoundError:
        print(f"Directory '{directory}' not found")
        return []

def is_file_in_directory(directory, filename):
    file_path = os.path.join(directory, filename)
    return os.path.exists(file_path)

def save_to_file(filename, text):
    with open(filename, 'w') as f:
        f.write(text)
    return f"Content saved to {filename}"

def execute_python_file(filename):
    try:
        result = subprocess.run(['python', filename], capture_output=True, text=True, check=True)
        return result.stdout
    except subprocess.CalledProcessError as e:
        return f"Error: {e}"

tools = [
    {
        "type": "function",
        "function": {
            "name": "list_files_in_directory",
            "description": "List all files in a directory",
            "parameters": {
                "type": "object",
                "properties": {
                    "directory": {
                        "type": "string",
                        "description": "The name of directory to list files in"
                    },
                },
                "required": ["directory"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "is_file_in_directory",
            "description": "Check if a file exists in a directory",
            "parameters": {
                "type": "object",
                "properties": {
                    "directory": {
                        "type": "string",
                        "description": "The name of directory to check file in"
                    },
                    "filename": {
                        "type": "string",
                        "description": "The name of file to check"
                    }, 
                },
                "required": ["directory", "filename"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "save_to_file",
            "description": "Save content to a file",
            "parameters": {
                "type": "object",
                "properties": {
                    "filename": {
                        "type": "string",
                        "description": "The name of file to save content to"
                    },
                    "text": {
                        "type": "string",
                        "description": "The content to save to file"
                    },
                },
                "required": ["filename", "text"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "execute_python_file",
            "description": "Execute python file and get the output",
            "parameters": {
                "type": "object",
                "properties": {
                    "filename": {
                        "type": "string",
                        "description": "The name of file to execute"
                    },
                },
                "required": ["filename"],
            },
        },
    }
]

available_tools = {
    "list_files_in_directory": list_files_in_directory,
    "is_file_in_directory": is_file_in_directory,
    "save_to_file": save_to_file,
    "execute_python_file": execute_python_file,
}

system_prompt = f"""
You are a friendly chatbot that can use external tools to offer reliable assistance to human beings.
"""
user_prompt = f"""
Write a Python file called 'platform_info.py' to check hardware information on a device if 'platform_info.py' does not exist in the directory called '../tools'.
Otherwise, get the result by executing the 'platform_info.py' and warp the result in natural language.
"""

messages = [
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": user_prompt},
]

while True:
    response = client.chat.completions.create(
        model=OPENAI_CHAT_MODEL,
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )

    response_msg = response.choices[0].message
    messages.append(response_msg)
    print(response_msg.content) if response_msg.content else print(response_msg.tool_calls)

    finish_reason = response.choices[0].finish_reason
    if finish_reason == "stop":
        break

    tool_calls = response_msg.tool_calls
    if tool_calls:
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_tools[function_name]
            function_args = json.loads(tool_call.function.arguments)
            function_response = function_to_call(**function_args)

            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": str(function_response),
                }
            )

基于以上文件目录,上述的程序运行完之后得到的结果如下:

2.4. Parallel function calling

这个其实是这次API更新的一大亮点,那就是如何能够让GPT可以实现一个API的并行调用。例如当用户希望同时查询北京,上海,广州和深圳四个城市的天气时,如果使用上述的sequential function calling也是可以实现的,但其实在每一次的调用中,其调用的函数都是一样的,只是不同的参数,这其实完全能够利用并行算法来实现更快地response生成。这个例子其实OpenAI在其官网已经给出了详细的代码,这里给出链接,就不再赘述。

https://platform.openai.com/docs/guides/function-calling​platform.openai.com/docs/guides/function-calling

2.5. JSON mode

在本次的API更新中还有一个比较有意思的点就是GPT的JSON mode,这其实也在给GPT的格式化输出提供了一个一个新方法,使用方法也很简单,代码如下:

response = client.chat.completions.create(
        model = MODEL, 
        messages = [
            {"role": "system", "content": "You are a helpful assistant and give answer in json format."},
            {"role": "user", "content": "Introduce yourself."}
        ],
        response_format= { "type": "json_object" },
        max_tokens=1024,
        timeout=10
    )

得到的结果如下:

需要注意的是JSON mode目前只有gpt-4-1106-previeworgpt-3.5-turbo-1106两个聊天模型可以使用。

3. 结语

关于更多细节的更新可以参考官方的网页:

https://platform.openai.com/docs/guides/text-generation/completions-api​platform.openai.com/docs/guides/text-generation/completions-api

其中还包括有生成可重复性,tokens管理和参数调节等较为细节的更新,而本文则着重于function calling或者说tools calling这一功能的介绍。

OpenAI GPT的function calling很强大,但其闭源的特性或许是很多开发者或者是企业不太喜欢的,大家都喜欢自己可以掌握和完全可控的工具。所以基于开源模型的开源function calling其实已经逐步发展,我认为一个很好的例子就是chatGLM3,其API的调用就包含了function calling。而且也有很多研究者和开发者在不断开发新的开源function calling工具比如LLMCompiler。

https://github.com/SqueezeAILab/LLMCompiler​github.com/SqueezeAILab/LLMCompiler​编辑

function calling结合其他功能其实可以实现一些很有意思的想法,例如模仿计算机内存层级架构来拓展语言模型上下文窗口长度的MemGPT,这个项目当中就需要使用function calling。

MemGPT: Towards LLMs as Operating Systems​arxiv.org/abs/2310.08560​编辑

这其实也从侧面暗示了LLM结合function calling和memory等功能,很有可能演化为一种新型的操作系统的核心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1375935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

品牌出海新风尚:联名营销战略全面解析

随着全球化的推进,品牌出海已经成为许多企业拓展市场的重要战略之一。然而,要想在海外市场中获得成功,品牌需要面对一系列的挑战,包括文化差异、市场竞争、消费者需求等等。在这样的背景下,联名营销作为一种有效的品牌…

【ChatGPT-Share,国内可用】GPTS商店大更新:一探前沿科技的魅力!

使用地址:https://hello.zhangsan.cloud/list GPTS商店预览,王炸更新 精选应用: 系统内置应用: 绘画应用: 写作应用: 高效工具应用: 学术搜索和分析应用: 编程应用: 教育应…

6.3、SDN在云计算中的应用

目录 一、SDN概念 1.1、传统网络机制 1.2、SDN网络机制 1.3、二者区别 1.4、SDN架构 二、云数据中心 2.1、公有云环境特点 2.2、两大挑战 2.3、云数据中心引入SDN技术解决两大挑战 三、SDN云计算解决方案 3.1、SDN云计算解决方案之控制平面openflow协议 3.1.…

vue前端开发自学,父子组件传递数据,借助于Props实现子传父

vue前端开发自学,父子组件传递数据,借助于Props实现子传父! 之前我们说过,Props这个是用在父传子的情况下,今天为大家介绍的代码,就是在父组件里,自定义事件,绑定一个函数,让子组件可以接受到这…

架构01 - 知识体系详解

架构,又称为知识体系,是指在特定领域或系统中的组织结构和设计原则。它涵盖了该领域或系统的核心概念、基础理论、方法技术以及实践经验等。架构的主要作用是提供一个全面且系统化的视角,帮助人们理解和应用相关知识,并指导系统的…

PHP短链接url还原成长链接

在开发过程中,碰到了需要校验用户回填的短链接是不是系统所需要的,于是就需要还原找出短链接所对应的长链接。 长链接转短链接 在百度上搜索程序员,跳转页面后的url就是一个长链接。当然你可以从任何地方复制一个长链接过来。 长链接 http…

MySQL 按日期流水号 条码 分布式流水号

有这样一个场景,有多台终端,要获取唯一的流水号,流水号格式是 日期0001形式,使用MySQL的存储过程全局锁实现这个需求。 以下是代码示例。 注:所有的终端连接到MySQL服务器获取流水号,如果获取到的是 “-1”…

2022 年全国职业院校技能大赛高职组云计算赛项试卷

【赛程名称】云计算赛项第一场-私有云 某企业拟使用OpenStack 搭建一个企业云平台,以实现资源池化弹性管理、企业应用集中管理、统一安全认证和授权等管理。 系统架构如图 1 所示,IP 地址规划如表 1 所示。 图 1 系统架构图 表 1 IP 地址规划 设备…

【Oracle】数据库对象

一、视图 1、视图概述 视图是一种数据库对象 视图 > 封装sql语句 > 虚拟表 2、视图的优点 简化操作:视图可以简化用户处理数据的方式。着重于特定数据:不必要的数据或敏感数据可以不出现在视图中。视图提供了一个简单而有效的安全机制&#x…

使用 gitee+sphinx+readthedocs 搭建个人博客

给大家安利如何快速搭建个人博客网站! 前言 这是我本地运行的一个使用sphinx构建的博客服务,这些文章,都是用markdown写的。 一直有个想法,就是把自己写的这些文件,搞成一个博客网站,放到网上&#xff0c…

正面PK智驾,华为与博世「硬扛」

12月20日,随着奇瑞星纪元ES的亮相上市,华为与博世,分别作为新旧时代的供应商角色,首次在高阶智驾赛道进行正面PK。 11月28日,奇瑞和华为合作的首款车型智界S7上市,作为星纪元ES的兄弟车型,搭载华…

Jenkins基础篇--凭据(Credential)管理

什么是凭据 Jenkins的Credentials直译为证书、文凭,我们可以理解为它是钥匙,用来做某些事情的认证。 如Jenkins 和 GitLab交互时,需要添加GitLab的API令牌和登录凭证。 如Jenkins 添加从节点时,需要添加从节点的登录凭证或者Je…

Maven和MyBatis框架简单实现数据库交互

MyBatis是一种基于Java语言的持久层框架,它的主要目的是简化与数据库的交互过程。MyBatis通过XML或注解配置来映射Java对象和数据库表之间的关系,并提供了灵活的查询方式和结果集处理机制。MyBatis还提供了事务管理、缓存机制、插件扩展等特性。 使用My…

详细分析Java中的分布式任务调度框架 XXL-Job

目录 前言1. 基本知识2. Demo3. 实战 前言 可视化任务调度 可视化配置 1. 基本知识 在Java中,分布式任务调度框架 XXL-Job 是一个开源的分布式任务调度平台,用于实现分布式系统中的定时任务调度和分布式任务执行。 下面是关于XXL-Job的一些概念、功…

【Docker】概述与安装

🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一. Docker的概述 1.Docker为什么出现 2…

RabbitMQ入门到实战——高级篇

消息的可靠性 生产者的可靠性(确保消息一定到达MQ) 生产者重连 这⾥除了enabled是false外,其他 initial-interval 等默认都是⼀样的值。 生产者确认 生产者确认代码实现 application中增加配置:(publisher-returns…

【谭浩强C程序设计精讲 7】数据的输入输出

文章目录 3.5 数据的输入输出3.5.1 输入输出举例3.5.2 有关数据输入输出的概念3.5.3 用 printf 函数输出数据1. printf 的一般格式2. 格式字符 3.5.4 用 scanf 函数输入数据1. scanf 函数的一般形式2. scanf 函数中的格式声明3. 使用 scanf 函数时应注意的问题 3.5.5 字符输入输…

Find My游戏手柄|苹果Find My技术与手柄结合,智能防丢,全球定位

游戏手柄是一种常见电子游戏机的部件,通过操纵其按钮等,实现对游戏虚拟角色的控制。随着游戏设备硬件的升级换代,现代游戏手柄又增加了:类比摇杆(方向及视角),扳机键以及HOME菜单键等。现在的游…

货拉拉智能监控实践:如何解决多云架构下的故障应急问题?

一分钟精华速览 在月活超千万的大规模业务背景下,货拉拉遭遇了多云环境下的监控碎片化、规划无序等问题。为了应对这些挑战,货拉拉开发了一站式监控平台——Monitor。该平台的部署有效地实现了对核心应用的监控和报警全覆盖,显著提高了应急响…

Aigtek高压放大器的工作原理和指标应用介绍

高压放大器是一种用于放大高压信号的电子设备,具有高压输出,低噪声,高精度,高稳定性,高可靠性,低功耗,低成本等的优点,所以才被广泛应用在磁场探测、电磁脉冲放大、电磁波放大、电磁…