函数调用
前言
使用大型语言模型进行函数调用 (Function Calling) 是一个庞大且不断发展的主题。这对AI应用尤为重要:
无论是为了绕过当前AI技术的局限性,而设计的原生AI应用,
还是为了提升性能、用户体验或效率,寻求整合AI技术的现有应用。
先不深入讨论LLM在应用中应扮演的角色及相关的最佳实践。这些观点反映在AI应用框架的设计上:从LangChain到LlamaIndex再到XXXAgent。
相反,我们将讨论如何使用XXX 来支持函数调用,以及如何利用它实现你的目标,从开发应用时的推理用途,到硬核定制的内部运作。在这个指南中,
我们首先将展示如何使用XXX 进行函数调用。
接着,我们将介绍使用XXX 进行函数调用的技术细节,主要涉及模板的使用。
在开始之前,还有一件事我们尚未介绍,那就是…
什么是函数调用?
这一概念也可能被称为“工具使用” (“tool use”)。虽然有人认为“工具”是“函数”的泛化形式,但在当前,它们的区别仅在技术层面上,表现为编程接口的不同输入输出类型。
大型语言模型(LLMs)确实强大。然而,有时候单靠大型语言模型的能力还是不够的。
-
一方面,大型语言模型存在建模局限性。首先,对于训练数据中没有的信息,包括训练结束后发生的事情,它们并不了解。此外,它们通过概率方式学习,这意味着对于有固定规则集的任务,如数学计算,可能不够精确。
-
另一方面,将大型语言模型作为即插即用服务与其它系统进行编程式协作,并非易事。大型语言模型的表达多含主观解释成分,因而产生歧义;而其他软件、应用或系统则通过预定义、固定和结构化的代码及编程接口进行沟通。
为此,函数调用确立了一个通用协议,规定了大型语言模型应与其他实体互动的流程。主要流程如下:
-
应用程序向大型语言模型提供一组函数及其使用说明。
-
大型语言模型根据用户查询,选择使用或不使用,或被迫使用一个或多个函数。
-
如果大型语言模型选择使用这些函数,它会根据函数说明如何使用。
-
应用程序按照选择使用这些函数,并获取结果。如果需要进一步互动,结果将提供给大型语言模型。
大型语言模型理解并遵循此协议有多种方式。关键在于提示工程 (Prompt Engineering) 或模型内化的模板。XXX2预先训练了多种支持函数调用的模板,以便用户可以直接利用这一过程。
使用函数调用进行推理
注意,随着框架和XXX模型的不断演进,推理的使用方式可能会发生变化。
由于函数调用本质上是通过提示工程实现的,您可以手动构建XXX2模型的输入。但是,支持函数调用的框架可以帮助您完成所有繁重的工作。
接下来,我们将介绍(通过专用的函数调用模板)使用
-
XXX-Agent,
-
Hugging Face transformers,
-
Ollama
-
vLLM
如果您熟悉OpenAI API的使用,您也可以直接使用适用于XXX 的OpenAI兼容API服务。然而,并非所有服务都支持XXX 的函数调用。目前,支持的解决方案包括由Ollama或vLLM提供的自托管服务和阿里云百炼的云服务。
如果您熟悉应用框架,例如LangChain,您也可以通过ReAct Prompting在XXX 中使用函数调用功能。
案例
我们同样通过一个示例来展示推理的使用方法。假设我们使用的编程语言是Python 3.11。
场景:假设我们要询问模型某个地点的温度。通常,模型会回答无法提供实时信息。但我们有两个工具,可以分别获取城市的当前温度和指定日期的温度,我们希望模型能够利用这些工具。
为了这个示例案例,您可以使用以下代码:
工具应使用JSON Schema进行描述,消息应包含尽可能多的有效信息。您可以在下面找到工具和消息的解释:
XXX-Agent
XXX-Agent 实际上是一个用于开发AI应用的Python智能体框架。尽管其设计用例比高效推理更高级,但它确实包含了XXX 函数调用的规范实现。基于OpenAI兼容API,它可以通过模板为XXX 提供了对用户透明的的函数调用能力。
值得注意的是,由于应用框架可以在幕后完成大量工作,目前XXX 官方的函数调用实现非常灵活且超出了简单的模板化,这使得它难以适应那些使用能力较弱的模板引擎的其他框架。
在开始之前,让我们确保已安装了最新的库:
pip install -U XXX-agent
For this guide, we are at version v0.0.10.
准备工作
XXX-Agent可以封装一个不支持函数调用的OpenAI兼容API。您可以使用大多数推理框架来提供此类API,或者从DashScope或Together等云提供商处获取一个。
假设在http://localhost:8000/v1处有一个OpenAI兼容API,XXX-Agent提供了一个快捷函数get_chat_model,用于获取具有函数调用支持的模型推理类:
from XXX_agent.llm import get_chat_model
llm = get_chat_model({
"model": "XXX/XXX -7B-Instruct",
"model_server": "http://localhost:8000/v1",
"api_key": "EMPTY",
})
在上述代码中,model_server是其他OpenAI兼容API客户端常用的api_base。建议您提供api_key(但不要以明文形式出现在代码中),即使API服务器不检查它,在这种情况下,您可以将其设置为任何值。
对于模型输入,应使用系统、用户和助手历史记录的通用消息结构:
messages = MESSAGES[:]
# [
# {"role": "system", "content": "You are XXX, created by Alibaba Cloud. You are a helpful assistant.\n\nCurrent Date: 2024-09-30"},
# {"role": "user", "content": "What's the temperature in San Francisco now? How about tomorrow?"}
# ]
我们在系统消息中添加当前日期,以便使用户消息中的”明天”有明确的参照点。如果需要,也可以将其添加到用户消息中。
目前,XXX-Agent使用“函数”而非“工具”。这需要对我们工具描述进行一些小的更改,即提取函数字段:
functions = [tool[“function”] for tool in TOOLS]
工具调用和工具结果
为了与模型交互,应使用chat方法:
for responses in llm.chat(
messages=messages,
functions=functions,
extra_generate_cfg=dict(parallel_function_calls=True),
):
pass
messages.extend(responses)
在上述代码中,chat方法接收messages、functions以及一个extra_generate_cfg参数。你可以在extra_generate_cfg中放入诸如temperature和top_p等采样参数。这里,我们添加了XXX-Agent提供的特殊控制parallel_function_calls。顾名思义,它将启用并行函数调用,这意味着模型可能为单次请求生成多个函数调用,按照其判断进行。
chat方法返回一个列表的生成器,每个列表可能包含多条消息。因为我们启用了parallel_function_calls,我们应该在响应中得到两条消息:
[
{
'role': 'assistant', 'content': '', 'function_call': {
'name': 'get_current_temperature', 'arguments': '{"location": "San Francisco, CA, USA", "unit": "celsius"}'}},
{
'role': 'assistant', 'content': '', 'function_call': {
'name': 'get_temperature_date', 'arguments': '{"location": "San Francisco, CA, USA", "date": "2024-10-01", "unit": "celsius"}'}},
]
我们可以看到,XXX-Agent试图以更易于使用的结构化格式解析模型生成。与函数调用相关的详细信息被放置在消息的function_call字段中:
name:代表要调用的函数的字符串
arguments:表示函数应带有的参数的JSON格式字符串
请注意,XXX -7B-Instruct相当强大:
它遵循函数指令,在位置中添加了州和国家。
它正确地推断出明天的日期,并以函数要求的格式给出。
接下来是关键部分——检查和应用函数调用:
for message in responses:
if fn_call := message.get("function_call", None):
fn_name: str = fn_call['name']
fn_args: dict = json.loads(fn_call["arguments"])
fn_res: str = json.dumps(get_function_by_name(fn_name)(**fn_args))
messages.append({
"role": "function",
"name": fn_name,
"content": fn_res,
})
获取工具结果:
第1行:我们应该按模型生成它们的顺序迭代函数调用。
第2行:通过检查生成消息的function_call字段,我们可以查看是否需要按模型判断进行函数调用。
第3-4行:相关详情,包括函数名称和参数,也可以在那里找到,分别是name和arguments。
第6行:有了这些细节,应该调用函数并获取结果。这里,我们假设有一个名为get_function_by_name的函数来帮助我们根据名称获取相关函数。
第8-12行:获得结果后,将函数结果作为content添加到消息中,并将role设置为"function"。
现在消息是
[
{
'role': 'system', 'content': 'You are XXX, created by Alibaba Cloud. You are a helpful assistant.\n\nCurrent Date: 2024-09-30'},
{
'role': 'user', 'content': "What's the temperature in San Francisco now? How about tomorrow?"},
{
'role': 'assistant', 'content': '', 'function_call': {
'name': 'get_current_temperature', 'arguments': '{"location": "San Francisco, CA, USA", "unit": "celsius"}'}},
{
'role': 'assistant', 'content': '', 'function_call': {
'name': 'get_temperature_date', 'arguments': '{"location": "San Francisco, CA, USA", "date":