LLM中工具调用指南
引言
LLMs 现在正受到热捧,而工具调用的功能扩展了 大型语言模型 的范围。它不仅能够生成文本,还使 LLM 能够完成以前不可能实现的复杂自动化任务,例如动态 UI 生成、代理自动化等。
这些模型是在大量数据上训练的。因此,它们能够理解并生成结构化数据,使它们成为需要精确输出的工具调用应用程序的理想选择。这推动了大型语言模型在人工智能驱动的软件开发中的广泛应用,其中工具调用——从简单的函数到复杂的智能体——已成为一个重点。
在本文中,您将从学习 LLM 工具调用的基础知识开始,实施它以使用开源工具构建代理。
学习目标
- 了解什么是LLM工具。
- 理解工具调用的基本原理和使用案例。
- 探索 OpenAI 中工具调用是如何工作的(ChatCompletions API、Assistants API、并行工具调用和结构化输出)、Anthropic 模型和 LangChain。
- 学习使用开源工具构建强大的人工智能代理。
目录
什么是工具?
工具是允许大型语言模型(LLMs)与外部环境互动的对象。这些工具是提供给大型语言模型的功能,LLM可以在判断使用它们合适时单独执行。
通常,工具定义有三个要素。
- 名称: 函数/工具的一个有意义的名称。
- 描述: 工具的详细描述。
- 参数:函数/工具参数的JSON模式。
什么是工具调用?
工具调用使模型能够生成符合用户定义的函数架构的提示响应。换句话说,当LLM判断应该使用工具时,它会生成与工具参数架构匹配的结构化输出。
例如,如果您向LLM提供了get_weather函数的模式并询问某个城市的天气,它不会生成文本响应,而是返回一个格式化的函数参数模式,您可以使用该模式来执行函数以获取该城市的天气。
尽管名称为“工具调用”,但该模型实际上并不执行任何工具。相反,它生成一个根据定义的模式格式化的结构化输出。然后,您可以将此输出提供给相应的函数以在您的端运行它。
工具调用是如何工作的?
AI实验室,如OpenAI和Anthropic,已经训练出模型,使您可以为大型语言模型提供多种工具,并让其根据上下文选择合适的工具。
每个提供者处理工具调用和响应处理的方式不同。以下是当您将提示和工具传递给LLM时,工具调用的基本流程:
- 定义工具并提供用户提示
- 定义工具和函数,包括名称、描述以及参数的结构化模式。
- 还包括用户提供的文本,例如,“今天纽约的天气怎么样?”
- 大型语言模型决定使用工具
- 助理评估是否需要工具。
- 如果是,它将停止文本生成。
- 助手生成一个 JSON 格式的响应,包含工具的参数值。
- 提取工具输入,运行代码,并返回输出
- 提取函数调用中提供的参数。
- 通过传递参数来运行函数。
- 将输出结果返回给LLM。
- 从工具输出生成答案
- LLM使用工具输出以形成一般答案。
示例用例
- 使 LLM 能够采取行动:将 LLM 与外部应用程序(如 Gmail、GitHub 和 Discord)连接,以自动化操作,例如发送电子邮件、推送 PR 和发送消息。
- 为LLMs提供数据: 从知识库中获取数据,如网络、维基百科和天气API,为LLMs提供专业信息。
- 动态用户界面: 根据用户输入更新应用程序的用户界面。
不同的模型提供者在处理工具调用时采取不同的方法。本文将讨论OpenAI、Anthropic和LangChain的工具调用方法。您还可以使用开源模型,如Llama 3,以及推理提供者,如Groq,进行工具调用。
在OpenAI模型中调用工具
目前,OpenAI 有四种不同的模型(GPT-4o、GPT-4o-mini、GPT-4-turbo 和 GPT-3.5-turbo)。所有这些模型都支持工具调用。
让我们通过一个简单的计算器函数示例来理解它。
def calculator(operation, num1, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
return num1 / num2
创建一个用于计算器功能的工具调用模式。
import openai
openai.api_key = OPENAI_API_KEY
# 定义函数架构(这就是GPT-4将用来理解如何调用该函数的内容)
calculator_function = {
"name": "calculator",
"description": "执行基本的算术运算",
"parameters": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "要执行的操作"
},
"num1": {
"type": "number",
"description": "第一个数字"
},
"num2": {
"type": "number",
"description": "第二个数字"
}
},
"required": ["operation", "num1", "num2"]
}
}
一个典型的OpenAI函数/工具调用模式包括一个名称、描述和参数部分。在参数部分,您可以提供函数参数的详细信息。
- 每个属性都有一个数据类型和描述。
- 可选地,可以定义一个枚举,指定参数期望的具体值。在这种情况下,“operation”参数期望“add”、“subtract”、“multiply”和“divide”中的任何一个。
- 必要的部分表示模型必须生成的参数。
现在,使用定义好的函数模式从聊天补全端点获取响应。
# 调用 OpenAI API 的示例
response = openai.chat.completions.create(
model="gpt-4-0613", # 您可以使用任何支持函数调用的版本
messages=[
{
"role": "system", "content": "您是一个有帮助的助手。"},
{
"role": "user", "content": "3 加 4 等于多少?"},
],
functions=[calculator_function],
function_call={
"name": "calculator"}, # 指示模型调用计算器函数
)
# 从响应中提取函数调用及其参数
function_call = response.choices[0].message.function_call
name = function_call.name
arguments = function_call.arguments
您现在可以将参数传递给计算器函数以获得输出。
import json
args = json.loads(arguments)
result = calculator(args['operation'], args['num1'], args['num2'])
# 输出结果
print(f"结果: {
result}")
这是使用 OpenAI 模型调用工具的最简单方法。
使用助手 API
您还可以使用助手API进行工具调用。这为整个工作流程提供了更多的自由和控制,使您能够“构建代理以完成复杂的自动化任务。
这里是如何使用助手API进行工具调用。
我们将使用相同的计算器示例。
from openai import OpenAI
client = OpenAI(api_key=OPENAI_API_KEY)
assistant = client.beta.assistants.create(
instructions="你是一个天气机器人。使用提供的函数回答问题。",
model="gpt-4o",
tools=[{
"type":"function",
"function":{
"name": "calculator",
"description": "执行基本的算术运算",
"parameters": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "要执行的运算"
},
"num1": {
"type": "number",
"description": "第一个数字"
},
"num2": {
"type": "number",
"description": "第二个数字"
}
},
"required": ["operation", "num1", "num2"]
}
}
}
]
)
创建一个线程和一条消息
thread = client.beta.threads.create()
message = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="3加4等于什么?",
)
开始运行
run = client.beta.threads.runs.create_and_poll(
thread_id=thread.id,
assistant_id="assistant.id")
获取参数并运行计算器函数
arguments = run.required_action.submit_tool_outputs.tool_calls[0].function.arguments
import json
args = json.loads(arguments)
result = calculator(args['operation'], args['num1'], args['num2'])
遍历所需的操作并将其添加到列表中
#tool_outputs = []
# 遍历所需操作部分中的每个工具
for tool in run.required_action.submit_tool_outputs.tool_calls:
if tool.function.name == "calculator":
tool_outputs.append({
"tool_call_id": tool.id,
"output": str(result)
})
将工具输出提交给API并生成响应
# 将工具输出提交到 API
client.beta.threads.runs.submit_tool_outputs_and_poll(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs
)
messages = client.beta.threads.messages.list(
thread_id=thread.id
)
print(messages.data[0].content[0].text.value)
这将输出一个响应 `3加4等于7`。
并行函数调用
您还可以同时使用多个工具来处理更复杂的用例。例如,获取某个地点的当前天气和降水几率。为实现这一点,您可以使用并行函数调用功能。
定义两个虚拟函数及其工具调用的模式
from openai import OpenAI
client = OpenAI(api_key=OPENAI_API_KEY)
def get_current_temperature(location, unit="Fahrenheit"):
return {
"location": location, "temperature": "72", "unit": unit}
def get_rain_probability(location):
return {
"location": location, "probability": "40"}
assistant = client.beta.assistants.create(
instructions="你是一个天气机器人。使用提供的函数来回答问题。",
model="gpt-4o",
tools=[
{
"type": "function",
"function": {
"name": "get_current_temperature",
"description": "获取特定地点的当前温度",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加州"
},
"unit": {
"type": "string",
"enum": ["Celsius", "Fahrenheit"],
"description": "要使用的温度单位。根据用户的位置推断。"
}
},
"required": ["location", "unit"]
}
}
},
{
"type": "function",
"function": {
"name": "get_rain_probability",
"description": "获取特定地点的降雨概率",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加州"
}
},
"required": ["location"]
}
}
}
]
)
现在,创建一个线程并启动一个运行。根据提示,这将输出所需的函数参数的JSON架构。
thread = client.beta.threads.create()
message = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="今天旧金山的天气怎么样,下雨的可能性有多大?",
)
run = client.beta.threads.runs.create_and_poll(
thread_id=thread.id,
assistant_id=assistant.id,
)
解析工具参数并调用函数
import json
location = json.loads(run.required_action.submit_tool_outputs.tool_calls[0].function.arguments)
weather = json.loa"s(run.requir"d_action.submit_t"ol_out"uts.tool_calls[1].function.arguments)
temp = get_current_temperature(location['location'], location['unit'])
rain_p"ob = get_rain_pro"abilit"(weather['location'])
# 输出结果
print(f"结果: {
temp}")
print(f"结果: {
rain_prob}")
定义一个列表来存储工具输出
# 定义列表以存储工具输出
tool_outputs = []
# 遍历所需操作部分的每个工具
for tool in run.required_action.submit_tool_outputs.tool_calls:
if tool.function.name == "get_current_temperature":
tool_outputs.append({
"tool_call_id": tool.id,
"output": str(temp)
})
elif tool.function.name == "get_rain_probability":
tool_outputs.append({
"tool_call_id": tool.id,
"output": str(rain_prob)
})
提交工具输出并生成答案
# 一次性提交所有工具输出,收集在 tool_outputs 中:
try:
run = client.beta.threads.runs.submit_tool_outputs_and_poll(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs
)
print("工具输出成功提交.")
except Exception as e:
print("提交工具输出失败:", e)
else:
print("没有工具输出可提交.")
if run.status == 'completed':
messages = client.beta.threads.messages.list(
thread_id=thread.id
)
print(messages.data[0].content[0].text.value)
else:
print(run.status)
模型将根据工具的输出生成完整的答案。 `旧金山市当前的温度为72°F。今天有40%的降雨概率。`
请参阅官方文档获取更多信息。
结构化输出
最近,OpenAI 引入了 结构化输出,这确保了模型为函数调用生成的参数与您提供的 JSON 模式完全匹配。此功能防止模型生成不正确或意外的枚举值,使其响应与指定的模式保持一致。
要使用工具调用的结构化输出,请设置 strict: True。API 将预处理提供的模式,并限制模型严格遵循您的模式。
from openai import OpenAI
client = OpenAI()
assistant = client.beta.assistants.create(
instructions="你是一个天气机器人。使用提供的功能回答问题。",
model="gpt-4o-2024-08-06",
tools=[
{
"type": "function",
"function": {
"name": "get_current_temperature",
"description": "获取特定位置的当前温度",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加州"
},
"unit": {
"type": "string",
"enum": ["Celsius", "Fahrenheit"],
"description": "使用的温度单位。根据用户的位置推断。"
}
},
"required": ["location", "unit"],
"additionalProperties": False
},
"strict": True
}
},
{
"type": "function",
"function": {
"name": "get_rain_probability",
"description": "获取特定位置的降雨概率",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加州"
}
},
"required": ["location"],
"additionalProperties": False
},
// highlight-start
"strict": True
// highlight-end
}
}
]
)
初始请求将需要几秒钟。不过,随后将使用缓存的工件进行工具调用。
在Anthropic Claude中的工具调用
Anthropic的Claude系列模型在工具调用方面也很高效。
使用Claude调用工具的工作流程与OpenAI相似。然而,关键的区别在于工具响应的处理方式。在OpenAI的设置中,工具响应是在单独的角色下管理的,而在Claude的模型中,工具响应直接被纳入用户角色中。
在Claude中,典型的工具定义包括函数的名称、描述和JSON架构。
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "获取给定位置的当前天气",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加州"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,可以是'摄氏度'或'华氏度'"
}
},
"required": ["location"]
}
},
],
messages=[
{
"role": "user",
"content": "纽约的天气怎么样?"
}
]
)
print(response)
函数的架构定义类似于我们之前讨论的OpenAI聊天完成API中的架构定义。
然而,回应将Claude的模型与OpenAI的模型区分开来。
{
"id": "msg_01Aq9w938a90dw8q",
"model": "claude-3-5-sonnet-20240620",
"stop_reason": "tool_use",
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>我需要调用 get_weather 函数,而用户想要的是旧金山,这很可能是加利福尼亚州的旧金山。</thinking>"
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": {
"location": "旧金山, CA", "unit": "摄氏度"}
}
]
}
您可以提取参数,执行原始函数,并将输出传递给LLM,以获取带有函数调用附加信息的文本响应。
response = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "获取给定地点的当前天气",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如:旧金山,加利福尼亚州"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,可以是 'celsius' 或 'fahrenheit'"
}
},
"required": ["location"]
}
}
],
messages=[
{
"role": "user",
"content": "旧金山的天气怎么样?"
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>我需要使用 get_weather,用户想要的是旧金山,这就是加利福尼亚州的旧金山。</thinking>"
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": {
"location": "San Francisco, CA", "unit": "celsius"}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9", # 来自API的响应
"content": "65度" # 来自运行你的工具
}
]
}
]
)
print(response)
在这里,您可以观察到我们在用户角色下传递了工具调用输出。
有关Claude工具调用的更多信息,请参阅官方文档。
以下是不同LLM提供商工具调用功能的比较概述。
管理多个LLM提供者在构建复杂的AI应用程序时可能会迅速变得困难。因此,像LangChain这样的框架创建了一个统一的接口,用于处理来自多个LLM提供者的工具调用。
使用@tool装饰器在LangChain中创建自定义工具。
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""将 a 和 b 相加。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""将 a 和 b 相乘。
Args:
a: 第一个整数
b: 第二个整数
"""
return a * b
tools = [add, multiply]
初始化一个LLM,
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
使用绑定工具方法将定义的工具添加到 LLMs。
llm_with_tools = llm.bind_tools(tools)
有时候,你应该强迫LLM使用某些工具。许多LLM提供商允许这种行为。要在LangChain中实现这一点,请使用
always_multiply_llm = llm.bind_tools([multiply], tool_choice="乘法")
如果您想调用提供的任何工具
always_call_tool_llm = llm.bind_tools([add, multiply], tool_choice="任何")
使用 Pydantic 的模式定义
您还可以使用 Pydantic 定义工具架构。这在工具具有复杂架构时很有用。
from langchain_core.pydantic_v1 import BaseModel, Field
# 请注意,这里的文档字符串是至关重要的,因为它们将被传递给
# 模型和类名。
class add(BaseModel):
"""将两个整数相加。"""
a: int = Field(..., description="第一个整数")
b: int = Field(..., description="第二个整数")
class multiply(BaseModel):
"""将两个整数相乘。"""
a: int = Field(..., description="第一个整数")
b: int = Field(..., description="第二个整数")
tools = [add, multiply]
确保详细的文档字符串和清晰的参数描述以获得最佳结果。
在构建代理时调用工具
代理是由大型语言模型驱动的自动化程序,可以与外部环境进行互动。代理可以根据某些条件决定采取哪些行动,而不是按顺序一个接一个地执行动作。
从大型语言模型(LLMs)获取结构化响应以与人工智能代理合作曾经是繁琐的。然而,工具调用使从大型语言模型获取所需的结构化响应变得相对简单。这个主要特性正在引领人工智能代理革命。
所以,让我们看看如何使用 OpenAI SDK 和一个名为 Composio 的开源工具集构建一个真实世界的代理,例如 GitHub PR 审阅者。
Composio是什么?
Composio 是一个开源工具解决方案,用于构建人工智能代理。为了构建复杂的代理自动化,它提供了开箱即用的集成,适用于 GitHub、Notion、Slack 等应用程序。它让您可以轻松地与代理集成工具,而无需担心复杂的应用身份验证方法,如 OAuth。
这些工具可以与LLMs一起使用。它们经过优化以支持代理交互,这使得它们比简单的功能调用更为可靠。它们还处理用户身份验证和授权。
您可以使用这些工具与 OpenAI SDK、LangChain、LlamaIndex 等。
使用 GitHub 工具构建 AI 代理
让我们看一个例子,在这个例子中你将使用 OpenAI SDK 构建一个 GitHub PR 审查代理。
安装 OpenAI SDK 和 Composio。
pip install openai composio
登录到您的 Composio 用户账户。
composio 登录
完成集成流程以添加 GitHub 集成。
composio 添加 github composio 应用 更新
启用一个触发器以在创建时接收 PR。
composio triggers enable github_pull_request_event
创建一个新文件,导入库,并定义工具。
import os
from composio_openai import Action, ComposioToolSet
from openai import OpenAI
from composio.client.collections import TriggerEventData
composio_toolset = ComposioToolSet()
pr_agent_tools = composio_toolset.get_actions(
actions=[
Action.GITHUB_GET_CODE_CHANGES_IN_PR, # 获取给定 PR 的所有更改
Action.GITHUB_PULLS_CREATE_REVIEW_COMMENT, # 为给定 PR 创建一个评论
Action.GITHUB_ISSUES_CREATE, # 如果需要,允许您在 GitHub 上创建问题
]
)
初始化一个 OpenAI 实例并定义一个提示。
openai_client = OpenAI()
code_review_assistant_prompt = (
"""
你是一个经验丰富的代码审查员。
你的任务是审查提供的文件差异并给出建设性的反馈。
请遵循以下步骤:
1. 确定文件是否包含重大逻辑更改。
2. 在100个单词内用清晰简洁的英语总结差异中的更改。
3. 如果代码中存在任何问题,请提供可行的建议。
一旦你决定了任何待办事项的更改,请创建一个Github问题。
"""
)
创建一个带有提示和工具的OpenAI助手线程。
# 给予openai访问所有工具的权限
assistant = openai_client.beta.assistants.create(
name="PR审查助手",
description="一个帮助您审查PR的助手",
instructions=code_review_assistant_prompt,
model="gpt-4o",
tools=pr_agent_tools,
)
print("助手已准备就绪")
现在,设置一个 webhook 来接收触发器获取的 PR,并设置一个回调函数来处理它们。
## 创建触发器监听器
listener = composio_toolset.create_trigger_listener()
## 当一个新的 PR 被打开时触发
@listener.callback(filters={
"trigger_name": "github_pull_request_event"})
def review_new_pr(event: TriggerEventData) -> None:
# 使用触发器中的信息,执行代理
code_to_review = str(event.payload)
thread = openai_client.beta.threads.create()
openai_client.beta.threads.messages.create(
thread_id=thread.id, role="user", content=code_to_review
)
## 我们打印我们的线程
url = f"https://platform.openai.com/playground/assistants?assistant={
assistant.id}&thread={
thread.id}"
print("访问此 URL 查看线程: ", url)
# 使用集成执行代理
# 开始执行
run = openai_client.beta.threads.runs.create(
thread_id=thread.id, assistant_id=assistant.id
)
composio_toolset.wait_and_handle_assistant_tool_calls(
client=openai_client,
run=run,
thread=thread,
)
print("监听器已启动!")
print("创建一个 pr 来获取审查")
listener.listen()
这里是上述代码块中发生的事情
- 初始化监听器并定义回调:我们定义了一个带有触发名称的过滤器的事件监听器和一个回调函数。当事件监听器接收到来自指定触发器的事件时,回调函数会被调用,即 github_pull_request_event。
- 处理 PR 内容:从事件负载中提取代码差异。
- 运行助手代理:创建一个新的OpenAI线程并将代码发送到GPT模型。
- 管理工具调用并开始监听:处理执行期间的工具调用并激活监听器以进行持续的PR监控。
通过这个,你将拥有一个完全功能的AI代理来审查新的PR请求。每当新的拉取请求被提出时,webhook会触发回调函数,最后,代理会将代码差异的摘要作为评论发布到PR中。
结论
大语言模型的工具调用处于代理革命的前沿。它使以前不可能的使用案例成为可能,例如让机器根据需要与外部应用程序交互、动态 UI 生成等。开发人员可以利用 OpenAI SDK、LangChain 和 Composio 等工具和框架构建复杂的代理自动化过程。
关键要点
- 工具是让大语言模型与外部应用程序交互的对象。
- 工具调用是LLMs根据用户消息生成所需函数的结构化模式的方法。
- 然而,主要的LLM提供商如OpenAI和Anthropic提供了不同实现的函数调用。
- LangChain 提供了一个统一的 API,用于使用 LLM 调用工具。
- Composio 提供了像 GitHub、Slack 和 Gmail 这样的工具和集成,用于复杂的代理自动化。