工具调用
我们交替使用“工具调用”和“函数调用”这两个术语。虽然函数调用有时是指单个函数的调用,但我们将所有模型都视为可以在每条消息中返回多个工具或函数调用。
查看此处以获取支持工具调用的所有模型列表。
工具调用允许模型通过生成与用户定义的模式匹配的输出对给定提示做出响应。虽然名称暗示模型正在执行某些操作,但实际上并非如此!模型正在想出工具的参数,而实际运行工具(或不运行)则取决于用户 - 例如,如果您想从非结构化文本中提取与某些模式匹配的输出,您可以为模型提供一个“提取”工具,该工具采用与所需模式匹配的参数,然后将生成的输出视为您的最终结果。
工具调用包括名称、参数字典和可选标识符。参数字典的结构为{argument_name: argument_value}
。
许多 LLM 提供商,包括Anthropic、Cohere、Google、Mistral、OpenAI等,都支持工具调用功能的变体。这些功能通常允许对 LLM 的请求包含可用的工具及其模式,以及对响应包含对这些工具的调用。例如,给定一个搜索引擎工具,LLM 可能会通过首先发出对搜索引擎的调用来处理查询。调用 LLM 的系统可以接收工具调用、执行它并将输出返回给 LLM 以告知其响应。LangChain 包括一套内置工具,并支持多种定义您自己的自定义工具的方法。工具调用对于构建使用工具的链和代理以及更普遍地从模型获取结构化输出非常有用。
提供商采用不同的约定来格式化工具模式和工具调用。例如,Anthropic 将工具调用作为解析后的结构返回在一个更大的内容块中
[
{
"text": "<thinking>\nI should use a tool.\n</thinking>",
"type": "text"
},
{
"id": "id_value",
"input": {"arg_name": "arg_value"},
"name": "tool_name",
"type": "tool_use"
}
]
而 OpenAI 将工具调用分离到一个单独的参数中,参数作为 JSON 字符串
{
"tool_calls": [
{
"id": "id_value",
"function": {
"arguments": '{"arg_name": "arg_value"}',
"name": "tool_name"
},
"type": "function"
}
]
}
LangChain 实现标准接口来定义工具、将它们传递给 LLM 和表示工具调用。
请求:将工具传递给模型
为了使模型能够调用工具,您需要在发出聊天请求时将工具模式传递给它。支持工具调用功能的 LangChain ChatModels 实现了一个.bind_tools
方法,该方法接收 LangChain 工具对象、Pydantic 类或 JSON 模式列表,并将它们绑定到以提供程序特定预期格式的聊天模型。绑定聊天模型的后续调用将在每次对模型 API 的调用中包含工具模式。
定义工具模式:LangChain 工具
例如,我们可以使用 Python 函数上的@tool
装饰器来定义自定义工具的模式
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""Multiplies a and b.
Args:
a: first int
b: second int
"""
return a * b
tools = [add, multiply]
API 参考
定义工具模式:Pydantic 类
我们也可以使用 Pydantic 来定义模式。当您的工具输入更复杂时,Pydantic 很有用
from langchain_core.pydantic_v1 import BaseModel, Field
# Note that the docstrings here are crucial, as they will be passed along
# to the model along with the class name.
class add(BaseModel):
"""Add two integers together."""
a: int = Field(..., description="First integer")
b: int = Field(..., description="Second integer")
class multiply(BaseModel):
"""Multiply two integers together."""
a: int = Field(..., description="First integer")
b: int = Field(..., description="Second integer")
tools = [add, multiply]
我们可以按如下方式将它们绑定到聊天模型
- OpenAI
- Anthropic
- Cohere
- FireworksAI
- MistralAI
- TogetherAI
安装依赖项
pip install -qU langchain-openai
设置环境变量
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
安装依赖项
pip install -qU langchain-anthropic
设置环境变量
import getpass
import os
os.environ["ANTHROPIC_API_KEY"] = getpass.getpass()
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-sonnet-20240229")
安装依赖项
pip install -qU langchain-google-vertexai
设置环境变量
import getpass
import os
os.environ["GOOGLE_API_KEY"] = getpass.getpass()
from langchain_google_vertexai import ChatVertexAI
llm = ChatVertexAI(model="gemini-pro")
安装依赖项
pip install -qU langchain-cohere
设置环境变量
import getpass
import os
os.environ["COHERE_API_KEY"] = getpass.getpass()
from langchain_cohere import ChatCohere
llm = ChatCohere(model="command-r")
安装依赖项
pip install -qU langchain-fireworks
设置环境变量
import getpass
import os
os.environ["FIREWORKS_API_KEY"] = getpass.getpass()
from langchain_fireworks import ChatFireworks
llm = ChatFireworks(model="accounts/fireworks/models/firefunction-v1", temperature=0)
安装依赖项
pip install -qU langchain-mistralai
设置环境变量
import getpass
import os
os.environ["MISTRAL_API_KEY"] = getpass.getpass()
from langchain_mistralai import ChatMistralAI
llm = ChatMistralAI(model="mistral-large-latest")
安装依赖项
pip install -qU langchain-openai
设置环境变量
import getpass
import os
os.environ["TOGETHER_API_KEY"] = getpass.getpass()
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="https://api.together.xyz/v1",
api_key=os.environ["TOGETHER_API_KEY"],
model="mistralai/Mixtral-8x7B-Instruct-v0.1",)
绑定工具模式
我们可以使用bind_tools()
方法来处理将Multiply
转换为“工具”并将其绑定到模型(即,在每次调用模型时都将其传递)。
llm_with_tools = llm.bind_tools(tools)
请求:强制执行工具调用
当您只使用bind_tools(tools)
时,模型可以选择是否返回一个工具调用、多个工具调用或根本不返回任何工具调用。某些模型支持tool_choice
参数,该参数使您能够强制模型调用工具。对于支持此功能的模型,您可以传入要让模型始终调用的工具的名称tool_choice="xyz_tool_name"
。或者,您可以传入tool_choice="any"
以强制模型至少调用一个工具,而无需指定具体哪个工具。
目前,OpenAI、MistralAI、FireworksAI 和 Groq 支持tool_choice="any"
功能。
目前,Anthropic 完全不支持tool_choice
。
如果我们希望我们的模型始终调用乘法工具,我们可以执行以下操作
always_multiply_llm = llm.bind_tools([multiply], tool_choice="multiply")
如果我们希望它始终调用加法或乘法中的至少一个,我们可以执行以下操作
always_call_tool_llm = llm.bind_tools([add, multiply], tool_choice="any")
响应:从模型输出中读取工具调用
如果工具调用包含在 LLM 响应中,则它们会作为AIMessage或AIMessageChunk(流式输出时)中ToolCall对象的列表附加到.tool_calls
属性中。ToolCall
是一个类型化字典,其中包含工具名称、参数值字典和(可选)标识符。没有工具调用的消息默认为此属性的空列表。
示例
query = "What is 3 * 12? Also, what is 11 + 49?"
llm_with_tools.invoke(query).tool_calls
[{'name': 'multiply',
'args': {'a': 3, 'b': 12},
'id': 'call_UL7E2232GfDHIQGOM4gJfEDD'},
{'name': 'add',
'args': {'a': 11, 'b': 49},
'id': 'call_VKw8t5tpAuzvbHgdAXe9mjUx'}]
.tool_calls
属性应包含有效的工具调用。请注意,在某些情况下,模型提供商可能会输出格式错误的工具调用(例如,无效的 JSON 参数)。在这些情况下解析失败时,.invalid_tool_calls
属性中将填充InvalidToolCall实例。InvalidToolCall
可以具有名称、字符串参数、标识符和错误消息。
如果需要,输出解析器可以进一步处理输出。例如,我们可以转换回原始的 Pydantic 类
from langchain_core.output_parsers.openai_tools import PydanticToolsParser
chain = llm_with_tools | PydanticToolsParser(tools=[multiply, add])
chain.invoke(query)
API 参考
[multiply(a=3, b=12), add(a=11, b=49)]
响应:流式输出
当在流式上下文中调用工具时,消息片段 将通过 .tool_call_chunks
属性以列表形式填充 工具调用片段 对象。ToolCallChunk
包含工具 name
、args
和 id
的可选字符串字段,以及一个可选的整数字段 index
,可用于将片段连接在一起。字段是可选的,因为工具调用的部分可能跨不同的片段进行流式传输(例如,包含参数子字符串的片段可能对工具名称和 ID 具有空值)。
由于消息片段继承自其父消息类,因此包含工具调用片段的 AIMessageChunk 也将包含 .tool_calls
和 .invalid_tool_calls
字段。这些字段是从消息的工具调用片段中尽力解析的。
请注意,并非所有提供商目前都支持工具调用的流式传输。
示例
async for chunk in llm_with_tools.astream(query):
print(chunk.tool_call_chunks)
[]
[{'name': 'multiply', 'args': '', 'id': 'call_5Gdgx3R2z97qIycWKixgD2OU', 'index': 0}]
[{'name': None, 'args': '{"a"', 'id': None, 'index': 0}]
[{'name': None, 'args': ': 3, ', 'id': None, 'index': 0}]
[{'name': None, 'args': '"b": 1', 'id': None, 'index': 0}]
[{'name': None, 'args': '2}', 'id': None, 'index': 0}]
[{'name': 'add', 'args': '', 'id': 'call_DpeKaF8pUCmLP0tkinhdmBgD', 'index': 1}]
[{'name': None, 'args': '{"a"', 'id': None, 'index': 1}]
[{'name': None, 'args': ': 11,', 'id': None, 'index': 1}]
[{'name': None, 'args': ' "b": ', 'id': None, 'index': 1}]
[{'name': None, 'args': '49}', 'id': None, 'index': 1}]
[]
请注意,添加消息片段将合并其对应的工具调用片段。这是 LangChain 的各种 工具输出解析器 支持流式传输的基本原理。
例如,下面我们累积工具调用片段
first = True
async for chunk in llm_with_tools.astream(query):
if first:
gathered = chunk
first = False
else:
gathered = gathered + chunk
print(gathered.tool_call_chunks)
[]
[{'name': 'multiply', 'args': '', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}]
[{'name': 'multiply', 'args': '{"a"', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}]
[{'name': 'multiply', 'args': '{"a": 3, ', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 1', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '{"a"', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '{"a": 11,', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '{"a": 11, "b": ', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '{"a": 11, "b": 49}', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
[{'name': 'multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_hXqj6HxzACkpiPG4hFFuIKuP', 'index': 0}, {'name': 'add', 'args': '{"a": 11, "b": 49}', 'id': 'call_GERgANDUbRqdtmXRbIAS9JTS', 'index': 1}]
print(type(gathered.tool_call_chunks[0]["args"]))
<class 'str'>
下面我们累积工具调用以演示部分解析
first = True
async for chunk in llm_with_tools.astream(query):
if first:
gathered = chunk
first = False
else:
gathered = gathered + chunk
print(gathered.tool_calls)
[]
[]
[{'name': 'multiply', 'args': {}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}]
[{'name': 'multiply', 'args': {'a': 3}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 1}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}, {'name': 'add', 'args': {}, 'id': 'call_P39VunIrq9MQOxHgF30VByuB'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}, {'name': 'add', 'args': {'a': 11}, 'id': 'call_P39VunIrq9MQOxHgF30VByuB'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}, {'name': 'add', 'args': {'a': 11}, 'id': 'call_P39VunIrq9MQOxHgF30VByuB'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': 'call_P39VunIrq9MQOxHgF30VByuB'}]
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_aXQdLhKJpEpUxTNPXIS4l7Mv'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': 'call_P39VunIrq9MQOxHgF30VByuB'}]
print(type(gathered.tool_calls[0]["args"]))
<class 'dict'>
请求:将工具输出传递给模型
如果我们使用模型生成的工具调用来实际调用工具,并希望将工具结果传递回模型,则可以使用 ToolMessage
。
from langchain_core.messages import HumanMessage, ToolMessage
@tool
def add(a: int, b: int) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""Multiplies a and b.
Args:
a: first int
b: second int
"""
return a * b
tools = [add, multiply]
llm_with_tools = llm.bind_tools(tools)
messages = [HumanMessage(query)]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
for tool_call in ai_msg.tool_calls:
selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()]
tool_output = selected_tool.invoke(tool_call["args"])
messages.append(ToolMessage(tool_output, tool_call_id=tool_call["id"]))
messages
API 参考
[HumanMessage(content='What is 3 * 12? Also, what is 11 + 49?'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Jja7J89XsjrOLA5rAjULqTSL', 'function': {'arguments': '{"a": 3, "b": 12}', 'name': 'multiply'}, 'type': 'function'}, {'id': 'call_K4ArVEUjhl36EcSuxGN1nwvZ', 'function': {'arguments': '{"a": 11, "b": 49}', 'name': 'add'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 144, 'total_tokens': 193}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_a450710239', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-9db7e8e1-86d5-4015-9f43-f1d33abea64d-0', tool_calls=[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_Jja7J89XsjrOLA5rAjULqTSL'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': 'call_K4ArVEUjhl36EcSuxGN1nwvZ'}]),
ToolMessage(content='36', tool_call_id='call_Jja7J89XsjrOLA5rAjULqTSL'),
ToolMessage(content='60', tool_call_id='call_K4ArVEUjhl36EcSuxGN1nwvZ')]
llm_with_tools.invoke(messages)
AIMessage(content='3 * 12 = 36\n11 + 49 = 60', response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 209, 'total_tokens': 225}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-a55f8cb5-6d6d-4835-9c6b-7de36b2590c7-0')
请求:少样本提示
对于更复杂的工具使用,在提示中添加少样本示例非常有用。我们可以通过在提示中添加包含 ToolCall
和对应 ToolMessage
的 AIMessage
来实现。
对于大多数模型来说,重要的是 ToolCall 和 ToolMessage 的 ID 要一致,以便每个包含 ToolCalls 的 AIMessage 后面都跟着具有对应 ID 的 ToolMessages。
例如,即使有一些特殊说明,我们的模型也可能被运算顺序所困扰
llm_with_tools.invoke(
"Whats 119 times 8 minus 20. Don't do any math yourself, only use tools for math. Respect order of operations"
).tool_calls
[{'name': 'multiply',
'args': {'a': 119, 'b': 8},
'id': 'call_RofMKNQ2qbWAFaMsef4cpTS9'},
{'name': 'add',
'args': {'a': 952, 'b': -20},
'id': 'call_HjOfoF8ceMCHmO3cpwG6oB3X'}]
模型不应该尝试添加任何内容,因为它在技术上还不知道 119 * 8 的结果。
通过添加包含一些示例的提示,我们可以纠正这种行为
from langchain_core.messages import AIMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
examples = [
HumanMessage(
"What's the product of 317253 and 128472 plus four", name="example_user"
),
AIMessage(
"",
name="example_assistant",
tool_calls=[
{"name": "multiply", "args": {"x": 317253, "y": 128472}, "id": "1"}
],
),
ToolMessage("16505054784", tool_call_id="1"),
AIMessage(
"",
name="example_assistant",
tool_calls=[{"name": "add", "args": {"x": 16505054784, "y": 4}, "id": "2"}],
),
ToolMessage("16505054788", tool_call_id="2"),
AIMessage(
"The product of 317253 and 128472 plus four is 16505054788",
name="example_assistant",
),
]
system = """You are bad at math but are an expert at using a calculator.
Use past tool usage as an example of how to correctly use the tools."""
few_shot_prompt = ChatPromptTemplate.from_messages(
[
("system", system),
*examples,
("human", "{query}"),
]
)
chain = {"query": RunnablePassthrough()} | few_shot_prompt | llm_with_tools
chain.invoke("Whats 119 times 8 minus 20").tool_calls
[{'name': 'multiply',
'args': {'a': 119, 'b': 8},
'id': 'call_tWwpzWqqc8dQtN13CyKZCVMe'}]
看起来这次我们得到了正确的输出。
以下是 LangSmith 追踪 的样子。
后续步骤
- 输出解析:请参阅 OpenAI 工具输出解析器 和 OpenAI 函数输出解析器,了解如何将函数调用 API 响应提取到各种格式中。
- 结构化输出链:某些模型具有构造函数,可以帮助您创建结构化输出链。
- 工具使用:请参阅 这些指南 中如何构建调用已调用工具的链和代理。