先决条件
本指南假定您熟悉以下概念:
工具调用 vs 函数调用
我们将术语工具调用与函数调用交替使用。尽管有时函数调用指的是调用单个函数,但我们将所有模型都视为可以在每条消息中返回多个工具或函数调用。
支持的模型
您可以找到支持工具调用的所有模型的列表。
工具调用允许聊天模型通过“调用工具”来响应给定的提示。虽然名称暗示模型正在执行某些操作,但实际情况并非如此!模型生成工具的参数,实际上是否运行工具由用户决定。例如,如果您想要从非结构化文本中提取与某个模式匹配的输出,您可以给模型提供一个“提取”工具,该工具接受与所需模式匹配的参数,然后将生成的输出视为最终结果。
然而,工具调用不仅限于结构化输出,因为您可以将来自被调用工具的响应传递回模型,以创建更长的交互。例如,给定一个搜索引擎工具,LLM 可能通过首先向搜索引擎发出带有参数的调用来处理查询。调用 LLM 的系统可以接收工具调用,执行它,并将输出返回给 LLM 以通知其响应。LangChain 包括一套内置工具并支持几种方法来定义您自己的自定义工具。
工具调用并非普遍存在,但许多流行的 LLM 提供商,包括 Anthropic、Cohere、Google、Mistral、OpenAI 等,都支持工具调用功能的变体。
LangChain 实现了用于定义工具、将它们传递给 LLM 以及表示工具调用的标准接口。本指南将向您展示如何使用它们。
向聊天模型传递工具
支持工具调用功能的聊天模型实现了一个 .bind_tools
方法,该方法接收一个 LangChain 工具对象 列表,并以其预期的格式将它们绑定到聊天模型。对聊天模型的后续调用将包含工具模式。
例如,我们可以使用 @tool
装饰器在 Python 函数上定义自定义工具的模式:
from langchain_core.tools import tool@tooldef add(a: int, b: int) -> int: """将 a 和 b 相加。""" return a + b@tooldef multiply(a: int, b: int) -> int: """将 a 和 b 相乘。""" return a * btools = [add, multiply]
API 参考:tool
或者,我们可以使用 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]
我们可以按照以下步骤将它们绑定到聊天模型:
- OpenAI
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")
- Anthropic
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")
- Azure
pip install -qU langchain-openai
import getpass
import os
os.environ["AZURE_OPENAI_API_KEY"] = getpass.getpass()
from langchain_openai import AzureChatOpenAI
llm = AzureChatOpenAI(
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
azure_deployment=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
openai_api_version=os.environ["AZURE_OPENAI_API_VERSION"],
)
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")
- Cohere
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")
- FireworksAI
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)
- MistralAI
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")
- TogetherAI
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响应中包含工具调用,则它们将作为一个工具调用对象列表附加到相应的消息或消息块的.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_KquHA7mSbgtAkpkmRPaFnJKa'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_Fl0hQi4IBTzlpaJYlM5kPQhE'}]
.tool_calls
属性应包含有效的工具调用。请注意,偶尔模型提供者可能输出格式不正确的工具调用(例如,参数不是有效的JSON)。在这些情况下,解析失败时,将在 .invalid_tool_calls
属性中填充InvalidToolCall的实例。InvalidToolCall
可以有名称、字符串参数、标识符和错误消息。
如果需要,输出解析器可以进一步处理输出。例如,我们可以转换回原始的Pydantic类:
from langchain_core.output_parsers.openai_tools import PydanticToolsParserchain = llm_with_tools | PydanticToolsParser(tools=[Multiply, Add])chain.invoke(query)
API 参考:[PydanticToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain_core.output_parsers.openai_tools
[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_3aQwTP9CYlFxwOvQZPHDu6wL', '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_SQUoSsJz2p9Kx2x73GOgN1ja', '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_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]
[{'name': 'Multiply', 'args': '{"a"', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]
[{'name': 'Multiply', 'args': '{"a": 3, ', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 1', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{"a"', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{"a": 11,', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{"a": 11, "b": ', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{"a": 11, "b": 49}', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', 'index': 1}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_AkL3dVeCjjiqvjv8ckLxL3gP', 'index': 0}, {'name': 'Add', 'args': '{"a": 11, "b": 49}', 'id': 'call_b4iMiB3chGNGqbt5SjqqD2Wh', '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_4p0D4tHVXSiae9Mu0e8jlI1m'}]
[{'name': 'Multiply', 'args': {'a': 3}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 1}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_4p0D4tHVXSiae9Mu0e8jlI1m'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_54Hx3DGjZitFlEjgMe1DYonh'}]
print(type(gathered.tool_calls[0]["args"]))
<class 'dict'>
将工具输出传递给模型
如果我们使用模型生成的工具调用来实际调用工具,并希望将工具结果传递回模型,我们可以使用 ToolMessage
。
from langchain_core.messages import HumanMessage, ToolMessage
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 | ToolMessage
[HumanMessage(content='What is 3 * 12? Also, what is 11 + 49?'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_svc2GLSxNFALbaCAbSjMI9J8', 'function': {'arguments': '{"a": 3, "b": 12}', 'name': 'Multiply'}, 'type': 'function'}, {'id': 'call_r8jxte3zW6h3MEGV3zH2qzFh', 'function': {'arguments': '{"a": 11, "b": 49}', 'name': 'Add'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 105, 'total_tokens': 155}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_d9767fc5b9', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-a79ad1dd-95f1-4a46-b688-4c83f327a7b3-0', tool_calls=[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_svc2GLSxNFALbaCAbSjMI9J8'}, {'name': 'Add', 'args': {'a': 11, 'b': 49}, 'id': 'call_r8jxte3zW6h3MEGV3zH2qzFh'}]),
ToolMessage(content='36', tool_call_id='call_svc2GLSxNFALbaCAbSjMI9J8'),
ToolMessage(content='60', tool_call_id='call_r8jxte3zW6h3MEGV3zH2qzFh')]
llm_with_tools.invoke(messages)
AIMessage(content='3 * 12 is 36 and 11 + 49 is 60.', response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 171, 'total_tokens': 189}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_d9767fc5b9', 'finish_reason': 'stop', 'logprobs': None}, id='run-20b52149-e00d-48ea-97cf-f8de7a255f8c-0')
注意,我们在 ToolMessage
中传回与从模型接收的相同的 id
,以帮助模型匹配工具响应与工具调用。
Few-shot 提示
对于更复杂的工具使用,非常有用的是在提示中添加 few-shot 示例。我们可以通过添加带有 ToolCall
和相应 ToolMessage
的 AIMessage
来实现。
例如,即使有一些特殊指示,我们的模型也可能因运算顺序而出现错误:
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_T88XN6ECucTgbXXkyDeC2CQj'}, {'name': 'Add', 'args': {'a': 952, 'b': -20}, 'id': 'call_licdlmGsRqzup8rhqJSb1yZ4'}]
模型不应该尝试添加任何东西,因为技术上它还不能知道 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
API 参考: AIMessage | ChatPromptTemplate | RunnablePassthrough
[{'name': 'Multiply', 'args': {'a': 119, 'b': 8}, 'id': 'call_9MvuwQqg7dlJupJcoTWiEsDo'}]
这次我们得到了正确的输出。
以下是 LangSmith trace 的样子。
绑定模型特定格式(高级)
提供者采用不同的工具模式格式化约定。例如,OpenAI 使用如下格式:
type
: 工具的类型。在撰写本文时,这始终为"function"
。function
: 包含工具参数的对象。function.name
: 要输出的模式的名称。function.description
: 要输出的模式的高级描述。function.parameters
: 你想提取的模式的嵌套细节,格式为 JSON schema 字典。
如果需要,我们也可以将此模型特定格式直接绑定到模型。以下是一个示例:
from langchain_openai import ChatOpenAI
model = ChatOpenAI()
model_with_tools = model.bind(
tools=[
{
"type": "function",
"function": {
"name": "multiply",
"description": "Multiply two integers together.",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "First integer"},
"b": {"type": "number", "description": "Second integer"},
},
"required": ["a", "b"],
},
},
}
]
)
model_with_tools.invoke("Whats 119 times 8?")
API 参考: ChatOpenAI
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_mn4ELw1NbuE0DFYhIeK0GrPe', 'function': {'arguments': '{"a":119,"b":8}', 'name': 'multiply'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 62, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-353e8a9a-7125-4f94-8c68-4f3da4c21120-0', tool_calls=[{'name': 'multiply', 'args': {'a': 119, 'b': 8}, 'id': 'call_mn4ELw1NbuE0DFYhIeK0GrPe'}])
这在功能上等同于上面的 bind_tools()
调用。
下一步
现在你已经学会了如何将工具模式绑定到聊天模型并调用这些工具。接下来,请查看一些工具调用的更具体用途: