对 LLM 工具使用进行统一

news2024/9/28 1:15:20

我们为 LLM 确立了一个跨模型的 统一工具调用 API。有了它,你就可以在不同的模型上使用相同的代码,在 MistralCohereNousResearch 或 Llama 等模型间自由切换,而无需或很少需要根据模型更改工具调用相关的代码。此外,我们还在 transformers 中新增了一些实用接口以使工具调用更丝滑,

引言

LLM 工具使用这个功能很有意思 —— 每个人都认为它很棒,但大多数人从未亲测过。它的概念很简单: 你给 LLM 提供一些工具 (即: 可调用的函数),LLM 在响应用户的查询的过程中可自主判断、自行调用它们。比方说,你给它一个计算器,这样它就不必依赖其自身不靠谱的算术能力; 你还可以让它上网搜索或查看你的日历,或者授予它访问公司数据库的权限 (只读!),以便它可以提取相应信息或搜索技术文档。

工具调用使得 LLM 可以突破许多自身的核心限制。很多 LLM 口齿伶俐、健谈,但涉及到计算和事实时往往不够精确,并且对小众话题的具体细节不甚了解。它们还不知道训练数据截止日期之后发生的任何事情。它们是通才,但除了你在系统消息中提供的信息之外,它们在开始聊天时对你或聊天背景一无所知。工具使它们能够获取结构化的、专门的、相关的、最新的信息,这些信息可以帮助其成为真正有帮助的合作伙伴,而不仅仅是令人着迷的新奇玩意儿。

然而,当你开始真正尝试工具使用时,问题出现了!文档很少且互相之间不一致,甚至矛盾 —— 对于闭源 API 和开放模型无不如此!尽管工具使用在理论上很简单,但在实践中却常常成为一场噩梦: 如何将工具传递给模型?如何确保工具提示与其训练时使用的格式相匹配?当模型调用工具时,如何将其合并到聊天提示中?如果你曾尝试过动手实现工具使用,你可能会发现这些问题出奇棘手,而且很多时候文档并不完善,有时甚至会帮倒忙。

更糟糕的是,不同模型的工具使用的实现可能迥异。即使在定义可用工具集这件最基本的事情上,一些模型厂商用的是 JSON 模式,而另一些模型厂商则希望用 Python 函数头。即使那些希望使用 JSON 模式的人,细节上也常常会有所不同,因此造成了巨大的 API 不兼容性。看!用户被摁在地板上疯狂摩擦,同时内心困惑不已。

为此,我们能做些什么呢?

聊天模板

Hugging Face Cinematic Universe 的忠​​粉会记得,开源社区过去在 聊天模型 方面也面临过类似的挑战。聊天模型使用 <|start_of_user_turn|> 或 <|end_of_message|> 等控制词元来让模型知道聊天中发生了什么,但不同的模型训练时使用的控制词元完全不同,这意味着用户需要为他们用的模型分别编写特定的格式化代码。这在当时是一个非常头疼的问题。

最终的解决方案是 聊天模板 - 即,模型会自带一个小小的 Jinja 模板,它能用正确的格式来规范每个模型的聊天格式和控制词元。聊天模板意味着用户能用通用的、与模型无关的方式编写聊天,并信任 Jinja 模板来处理模型格式相关的事宜。

基于此,支持工具使用的一个显而易见的方法就是扩展聊天模板的功能以支持工具。这正是我们所做的,但工具给模板方案带来了许多新的挑战。我们来看看这些挑战以及我们是如何解决它们的吧。希望在此过程中,你能够更深入地了解该方案的工作原理以及如何更好利用它。

将工具传给聊天模板

在设计工具使用 API 时,首要需求是定义工具并将其传递给聊天模板的方式应该直观。我们发现大多数用户的流程是: 首先编写工具函数,然后弄清楚如何据其生成工具定义并将其传递给模型。一个自然而然的想法是: 如果用户可以简单地将函数直接传给聊天模板并让它为他们生成工具定义那就好了。

但问题来了,“传函数”的方式与使用的编程语言极度相关,很多人是通过 JavaScript 或 Rust 而不是 Python 与聊天模型交互的。因此,我们找到了一个折衷方案,我们认为它可以两全其美: 聊天模板将工具定义为 JSON 格式,但如果你传 Python 函数给模板,我们会将其自动转换为 JSON 格式 这就产生了一个漂亮、干净的 API:

def get_current_temperature(location: str):
    """
    Gets the temperature at a given location.
 
    Args:
        location: The location to get the temperature for
    """
    return 22.0 # bug: Sometimes the temperature is not 22. low priority
 
tools = [get_current_temperature]
 
chat = [
    {"role": "user", "content": "Hey, what's the weather like in Paris right now?"}
]
 
tool_prompt = tokenizer.apply_chat_template(
    chat,
    tools=tools,
    add_generation_prompt=True,
    return_tensors="pt"
)

在 apply_chat_template 内部, get_current_temperature 函数会被转换成完整的 JSON 格式。想查看生成的格式,可以调用 get_json_schema 接口:

>>> from transformers.utils import get_json_schema
 
>>> get_json_schema(get_current_weather)
{
    "type": "function",
    "function": {
        "name": "get_current_temperature",
        "description": "Gets the temperature at a given location.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The location to get the temperature for"
                }
            },
            "required": [
                "location"
            ]
        }
    }
}

如果你更喜欢手动控制或者使用 Python 以外的语言进行编码,则可以将工具组织成 JSON 格式直接传给模板。但是,当你使用 Python 时,你可以无需直接处理 JSON 格式。你仅需使用清晰的 函数名、 准确的 类型提示 以及完整的含 参数文档字符串 的 文档字符串 来定义你的工具函数,所有这些都将用于生成模板所需的 JSON 格式。其实,这些要求本来就已是 Python 最佳实践,你本应遵守,如果你之前已经遵守了,那么无需更多额外的工作,你的函数已经可以用作工具了!

请记住: 无论是从文档字符串和类型提示生成还是手动生成,JSON 格式的准确性,对于模型了解如何使用工具都至关重要。模型永远不会看到该函数的实现代码,只会看到 JSON 格式,因此它们越清晰、越准确越好!

在聊天中调用工具

用户 (以及模型文档😬) 经常忽略的一个细节是,当模型调用工具时,实际上需要将 两条 消息添加到聊天历史记录中。第一条消息是模型 调用 工具的信息,第二条消息是 工具的响应,即被调用函数的输出。

工具调用和工具响应都是必要的 - 请记住,模型只知道聊天历史记录中的内容,如果它看不到它所作的调用以及传递的参数,它将无法理解工具的响应。 22 本身并没有提供太多信息,但如果模型知道它前面的消息是 get_current_temperature("Paris, France") ,则会非常有帮助。

不同模型厂商对此的处理方式迥异,而我们将工具调用标准化为 聊天消息中的一个域,如下所示:

message = {
    "role": "assistant",
    "tool_calls": [
        {
            "type": "function",
             "function": {
                 "name": "get_current_temperature",
                 "arguments": {
                     "location": "Paris, France"
                }
            }
        }
    ]
}
chat.append(message)

在聊天中添加工具响应

工具响应要简单得多,尤其是当工具仅返回单个字符串或数字时。

message = {
    "role": "tool",
    "name": "get_current_temperature",
    "content": "22.0"
}
chat.append(message)

实操

我们把上述代码串联起来搭建一个完整的工具使用示例。如果你想在自己的项目中使用工具,我们建议你尝试一下我们的代码 - 尝试自己运行它,添加或删除工具,换个模型并调整细节以感受整个系统。当需要在软件中实现工具使用时,这种熟悉会让事情变得更加容易!为了让它更容易,我们还提供了这个示例的 notebook

首先是设置模型,我们使用 Hermes-2-Pro-Llama-3-8B ,因为它尺寸小、功能强大、自由使用,且支持工具调用。但也别忘了,更大的模型,可能会在复杂任务上获得更好的结果!

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
 
checkpoint = "NousResearch/Hermes-2-Pro-Llama-3-8B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, torch_dtype=torch.bfloat16, device_map="auto")

接下来,我们设置要使用的工具及聊天消息。我们继续使用上文的 get_current_Temperature :

def get_current_temperature(location: str):
    """
    Gets the temperature at a given location.
 
    Args:
        location: The location to get the temperature for, in the format "city, country"
    """
    return 22.0 # bug: Sometimes the temperature is not 22. low priority to fix tho
 
tools = [get_current_temperature]
 
chat = [
    {"role": "user", "content": "Hey, what's the weather like in Paris right now?"}
]
 
tool_prompt = tokenizer.apply_chat_template(
    chat,
    tools=tools,
    return_tensors="pt",
    return_dict=True,
    add_generation_prompt=True,
)
tool_prompt = tool_prompt.to(model.device)

模型可用工具设定完后,就需要模型生成对用户查询的响应:

out = model.generate(**tool_prompt, max_new_tokens=128)
generated_text = out[0, tool_prompt['input_ids'].shape[1]:]
 
print(tokenizer.decode(generated_text))

我们得到:

<tool_call>
{"arguments": {"location": "Paris, France"}, "name": "get_current_temperature"}
</tool_call><|im_end|>

模型请求使用一个工具!请注意它正确推断出应该传递参数 “Paris, France” 而不仅仅是 “Paris”,这是因为它遵循了函数文档字符串推荐的格式。

但模型并没有真正以编程方式调用这些工具,就像所有语言模型一样,它只是生成文本。作为程序员,你需要接受模型的请求并调用该函数。首先,我们将模型的工具请求添加到聊天中。

请注意,此步骤可能需要一些手动处理 - 尽管你应始终按照以下格式将请求添加到聊天中,但模型调用工具的请求文本 (如 <tool_call> 标签) 在不同模型之间可能有所不同。通常,它非常直观,但请记住,在你自己的代码中尝试此操作时,你可能需要一些特定于模型的 json.loads() 或 re.search() !

message = {
    "role": "assistant",
    "tool_calls": [
        {
            "type": "function",
            "function": {
                "name": "get_current_temperature",
                "arguments": {"location": "Paris, France"}
            }
        }
    ]
}
chat.append(message)

现在,我们真正在 Python 代码中调用该工具,并将其响应添加到聊天中:

message = {
    "role": "tool",
    "name": "get_current_temperature",
    "content": "22.0"
}
chat.append(message)

然后,就像之前所做的那样,我们按格式更新聊天信息并将其传给模型,以便它可以在对话中使用工具响应:

tool_prompt = tokenizer.apply_chat_template(
    chat,
    tools=tools,
    return_tensors="pt",
    return_dict=True,
    add_generation_prompt=True,
)
tool_prompt = tool_prompt.to(model.device)
 
out = model.generate(**tool_prompt, max_new_tokens=128)
generated_text = out[0, tool_prompt['input_ids'].shape[1]:]
 
print(tokenizer.decode(generated_text))

最后,我们得到对用户的最终响应,该响应是基于中间工具调用步骤中获得的信息构建的:

The current temperature in Paris is 22.0 degrees Celsius. Enjoy your day!<|im_end|>

令人遗憾的响应格式不统一

在上面的例子中,你可能已经发现,尽管聊天模板可以帮助隐藏模型之间在聊天格式以及工具定义格式上的差异,但它仍有未尽之处。当模型发出工具调用请求时,其用的还是自己的格式,因此需要你手动解析它,然后才能以通用格式将其添加到聊天中。值得庆幸的是,大多数格式都非常直观,因此应该仅需几行 json.loads() ,最坏情况下估计也就是一个简单的 re.search() 就可以创建你需要的工具调用字典。

尽管如此,这是最后遗留下来的“不统一”尾巴。我们对如何解决这个问题有一些想法,但尚未成熟,“撸起袖子加油干”吧!

总结

尽管还留了一点小尾巴,但我们认为相比以前,情况已经有了很大的改进,之前的工具调用方式分散、混乱且记录不足。我们希望我们为统一作的努力可以让开源开发人员更轻松地在他们的项目中使用工具,以通过一系列令人惊叹的新工具来增强强大的 LLM。从 Hermes-2-Pro-8B 等较小模型到 Mistral-LargeCommand-R-Plus 或 Llama-3.1-405B 等最先进的巨型庞然大物,越来越多的前沿 LLM 已经支持工具使用。我们认为工具将成为下一波 LLM 产品不可或缺的一部分,祝你好运!

文章转载自:HuggingFace

原文链接:https://www.cnblogs.com/huggingface/p/18432899

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

领夹式无线麦克风哪个品牌好,口碑最好的领夹麦克风品牌推荐

对于追求专业音频录制的创作者而言&#xff0c;无线领夹麦克风不仅是录制设备&#xff0c;更是表达自我的艺术工具。从市场反馈中&#xff0c;我们发现西圣、大疆、罗德等品牌深受消费者喜爱。不过我看事情不能只看一面&#xff0c;在市场繁荣的表象下&#xff0c;一些劣质产品…

单片机的两种看门狗原理解析——IWDG和WWDG

一、IWDG独立开门狗的主要性能 计时机制&#xff1a; 递减计数器 独立开门狗的初始频率&#xff1a; LSI低速内部时钟&#xff1a;RC震荡器&#xff0c;40kHz 独立开门狗是以LSI为初始频率的&#xff0c;所以独立开门狗的初始时钟频率取决与单片机本身&#xff0c;因此在使…

Charles(青花瓷)抓取https请求

文章目录 前言Charles&#xff08;青花瓷&#xff09;抓取https请求 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&…

【工具分享】Chimera勒索病毒解密工具

前言 Chimera勒索软件首次出现在2015年&#xff0c;最初是在德国被发现。该勒索软件不仅加密受害者的文件&#xff0c;还威胁如果不支付赎金&#xff0c;就会将被盗的数据公开发布。这种“双重勒索”策略使得Chimera在众多勒索软件中脱颖而出。Chimera通常通过钓鱼邮件传播&am…

第五部分:6---信号的递达

目录 信号的递达流程&#xff1a; 信号在什么时候递达&#xff1f; 用户态和内核态&#xff1a; 内核态、用户态在页表的映射关系&#xff1a; 操作系统如何得知当前执行状态是用户态还是内核态&#xff1f; 操作系统如何处理被捕捉的信号&#xff1f; 信号的递达流程&am…

Python PyQt5 在frame中生成多个QLabel控件和彻底销毁QLabel控件

文章目录 步骤 1: 创建主窗口和布局步骤 2: 添加QLabel到QFrame步骤 3: 销毁QLabel示例代码 在PyQt5中&#xff0c;在QFrame或任何其他容器控件中生成多个QLabel控件并通过一个标志位或方法来彻底销毁这些QLabel控件是相对直接的操作。以下是一个简单的示例&#xff0c;展示了如…

爬虫小案例:爬取豆瓣网TOP250的电影信息(内含面相对象源码、及详细教学)

爬虫案例二———爬取豆瓣网TOP250的电影信息&#xff0c;并存入MySQL数据库 前提准备 安装pymysql库 pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple Python连接MySQL数据库&#xff0c;并进行增删改查基本操作 连接数据库 """连接MyS…

坚果N1 Air高亮版对比当贝D6X高亮版:谁是2000元预算的投影仪王者?

当贝D6X高亮版新品升级&#xff0c;对于那些计划在这个时间点购买投影仪的用户来说&#xff0c;现在是个绝佳的时机&#xff01;特别是那些预算在两千元左右的&#xff0c;目前两千元左右的投影仪&#xff0c;无外乎两款产品&#xff0c;当贝D6X高亮版和坚果N1 Air高亮版&#…

David律所代理Beau Parsons的小狗插画图案版权维权,速排查下架

案件基本情况&#xff1a;起诉时间&#xff1a;2024-9-16案件号&#xff1a;2024-cv-08505原告&#xff1a;Beau Parsons原告律所&#xff1a;David起诉地&#xff1a;伊利诺伊州北部法院涉案商标/版权&#xff1a;原告品牌简介&#xff1a;Beau Parsons是一位来自澳大利亚的专…

AI情感陪伴新纪元:WT2605C语音芯片在成人用品中的创新应用

在探索成人用品领域的无限可能时&#xff0c;科技的每一次进步都为我们带来了前所未有的体验。而今&#xff0c;WT2605C AI语音芯片的引入&#xff0c;正悄然改变着这一传统行业的面貌&#xff0c;为成人用品赋予了全新的情感陪伴功能&#xff0c;开启了智能化、个性化的新时代…

鸿蒙开发(NEXT/API 12)【硬件(Pen Kit)】手写笔服务

Pen Kit&#xff08;手写笔服务&#xff09;是华为提供的一套手写套件&#xff0c;提供笔刷效果、笔迹编辑、报点预测、一笔成形和全局取色的功能。手写笔服务可以为产品带来优质手写体验&#xff0c;为您创造更多的手写应用场景。 目前Pen Kit提供了四种能力&#xff1a;手写…

Qt_网络编程

目录 1、Qt的UDP Socket 1.1 用Udp实现服务器 1.2 用Udp实现客户端 2、Qt的TCP Socket 2.1 用Tcp实现服务器 2.2 用Tcp实现客户端 3、Qt的HTTP 3.1使用Qt的HTTP 结语 前言&#xff1a; 网络协议是每个平台都必须遵守的&#xff0c;只是不同的平台所提供的网络API不…

[element-ui]记录对el-table表头样式的一些处理

1、表头换行 & 列表项换行 可用element-table组件自带的方法实现列标题换行的效果 2、小圆点样式

Codeforces Round 973 (Div. 2) A-C 题解

C 提交 MLE 了一次&#xff0c;原因是找到答案没加感叹号 A. Zhan’s Blender 题意 原题描述还是不太清楚 你有 n n n 个水果&#xff0c;每秒可以放入搅拌机 y y y 个水果&#xff0c;搅拌机每秒可以搅拌 x x x 个水果&#xff0c;问最终至少需要多少秒能搅完&#xff1…

Python的包管理工具pip安装

Python的包管理工具pip安装 一、安装步骤1.检查 pip是否已安装2.安装 pip方法一&#xff1a;通过 ​ensurepip​ 模块安装(推荐)方法二&#xff1a;通过 ​get-pip.py​ 脚本安装&#xff08;经常应为网络域名问题连接不上&#xff09; 3.验证pip安装4.创建别名5.更新pip 二、常…

Java网络通信—UDP

1.总揽 2.客户端 使用udp通信&#xff0c;需要三个东西&#xff1a;数据本体、通道、数据包装工具 Scanner scanner new Scanner(System.in);String string scanner.next();byte[] bytes string.getBytes();// 数据本体DatagramSocket datagramSocket new DatagramSocket(…

代码随想录冲冲冲 Day58 图论Part9

47. 参加科学大会&#xff08;第六期模拟笔试&#xff09; 根据昨天的dijkstra进行堆优化 使用的原因是点多但边少 所以直接对于边进行操作 1.对于priority_queue来说 这是最小堆, 小于的话就是最大堆 之后由于是根据边来说的 所以新建一个Edge并且初始化一下 之后由于使用…

动态规划入门题目->使用最小费用爬楼梯

1.题目&#xff1a; 2.解析&#xff1a; 做题模式&#xff1a; 步骤一&#xff1a;找状态转移方程 步骤二&#xff1a;初始化 步三&#xff1a;填表 步骤四&#xff1a;返回-> dp[n] dp[i]表示到达 i 位置最小花费 逻辑&#xff1a;要爬到楼顶先找到 i 位置 &#xff0c; 要…

深度学习:(八)深层神经网络参数与流程

深层神经网络 符号规定 L L L &#xff1a;表示神经网络的层数&#xff1b; l l l &#xff1a;表示第几层&#xff1b; n [ l ] n^{[~l~]} n[ l ] &#xff1a;表示第 l l l 层的节点数&#xff1b; a [ l ] a^{[~l~]} a[ l ] &#xff1a;表示第 l l l 层中的激活函数&…

【web安全】——sql注入

1.MySQL基础 1.1information_schema数据库详解 简介&#xff1a; 在mysql5版本以后&#xff0c;为了方便管理&#xff0c;默认定义了information_schema数据库&#xff0c;用来存储数据库元数据信息。schemata(数据库名)、tables(表名tableschema)、columns(列名或字段名)。…