工具调用
最早是由 OpenAI 引入的功能,当时称为 Function Calling(函数调用),该功能允许模型通过函数与外部系统交互。随着时间的推移,功能调用演变为我们现在熟知的 Tool Calling(工具调用)。随着 OpenAI 的创新,其他语言模型提供商(如 Gemini、Mistral、Fireworks)也纷纷推出了类似的功能,但每个提供商都开发了不同的接口和实现方式。
标准化接口
在 LangChain 中,Tools 是大语言模型可以调用的各种外部函数、API、库或服务。大语言模型在执行任务时可以调用这些工具来执行特定的任务,比如查找信息、运行代码、调用搜索引擎、发送HTTP请求、与外部API交互等。可以把 Tools 看作是模型的"超级能力",通过它,模型可以处理非语言类的任务。
例如,一个典型的例子就是将搜索引擎作为一个 Tool,当模型遇到某个它不了解的问题时,它可以通过调用这个工具去实时查询答案,而不是依赖于静态训练数据。
由于不同模型提供商之间的工具调用接口不一致,开发者面临了如何在不同平台之间平滑切换的问题。为了应对这一挑战,LangChain 推出了 tool_calls
属性,这是一个标准化的接口,旨在统一不同模型提供商的工具调用机制。
LangChain中的核心组件
为了实现这种标准化的工具调用,LangChain 提供了几个核心组件:
ChatModel.bind_tools()
:这个方法允许开发者将工具定义绑定到模型上。开发者可以通过传递一个工具定义列表,指定可以调用的工具及其参数模式。这样,模型在生成响应时,可以依据这些定义来决定何时调用哪个工具。AIMessage.tool_calls
:这是tool_calls
属性的核心部分,用于访问模型决定调用的工具信息。无论具体使用的是哪个模型提供商,工具调用的结果都可以通过这个标准化的属性进行访问。这个属性提供了一种一致的方式来捕捉和处理模型发起的工具调用。create_tool_calling_agent()
:这是一个代理构造函数,允许开发者轻松创建能够调用工具的智能代理。它能够与任何实现了bind_tools
并返回tool_calls
的模型一起工作,使得工具调用的实现更加灵活和通用。
工具定义
1.工具的基本组成部分
自定义工具在LangChain中由以下几个关键组件组成:
- name(名称):工具的名称,必须唯一,这是代理识别工具时使用的唯一标识。
- description(描述):可选但推荐使用的描述,代理使用它来确定工具的用途和使用场景。
- return_direct:布尔值,默认为
False
,用于指示工具的输出是否应直接返回给用户。 - args_schema:可选的
Pydantic BaseModel
,用于定义工具的输入参数,可以验证参数并提供更多的上下文信息。
2.定义工具的两种主要方式
下面的使用装饰器定义工具本质 上是对Tool
数据类的一种简化和语法糖。它通过 @tool
装饰器,简化了函数转换为工具的过程,而无需手动调用 Tool.from_function()
方法。
方法1:使用Tool
数据类
Tool
数据类是最简单的方式,适用于接受单个字符串输入并返回字符串输出的工具。以下是一个简单示例,它定义了一个搜索工具 Search
,用于通过SerpAPI(一个网络搜索API)获取当前事件的答案。
from langchain import LLMMathChain, SerpAPIWrapper
from langchain.tools import Tool
# 定义一个 SerpAPI 搜索工具
search = SerpAPIWrapper()
# 定义一个工具列表
tools = [
Tool.from_function(
func=search.run,
name="Search",
description="useful for when you need to answer questions about current events"
)
]
在这个示例中,Tool.from_function()
方法包装了一个函数 search.run
,并将其转换为一个工具。这个工具接收一个字符串作为输入,并返回一个字符串作为输出。
方法2:子类化BaseTool
BaseTool
允许更高级的控制和自定义。这种方式适合需要对工具的实例变量进行更好控制,或者需要嵌套链或其他工具回调的场景。
from langchain.tools import BaseTool
# 创建自定义搜索工具,子类化 BaseTool
class CustomSearchTool(BaseTool):
name = "custom_search"
description = "useful for when you need to answer questions about current events"
def _run(self, query: str) -> str:
"""Use the tool."""
return search.run(query)
# 定义工具列表
tools = [CustomSearchTool()]
通过继承 BaseTool
,你可以在 _run()
方法中自定义工具的具体行为。如果工具需要异步支持,可以通过 _arun()
方法实现。
3.定义带有结构化输入的工具
文档还展示了如何使用 args_schema
来定义工具的输入结构,特别是当工具需要多个参数时,可以使用 Pydantic
模型进行参数的验证和定义。
from pydantic import BaseModel, Field
# 定义输入参数结构
class CalculatorInput(BaseModel):
question: str = Field()
tools.append(
Tool.from_function(
func=llm_math_chain.run,
name="Calculator",
description="useful for when you need to answer questions about math",
args_schema=CalculatorInput
)
)
这里 CalculatorInput
定义了工具所需的输入格式,question
字段是必填的。这种方式非常适合需要多个参数或复杂输入的工具,确保输入符合预期。
4.使用装饰器定义工具
为了简化工具的定义,LangChain 提供了一个 @tool
装饰器。这个装饰器可以将一个普通的Python函数快速转换为工具,并自动将函数名作为工具名,文档字符串作为工具描述。
from langchain.tools import tool
@tool
def search_api(query: str) -> str:
"""Searches the API for the query."""
return f"Results for query {query}"
通过 @tool
装饰器,开发者可以快速创建工具,而无需手动调用 Tool.from_function()
。装饰器还支持自定义工具名称和返回行为,如下所示:
@tool("search", return_direct=True)
def search_api(query: str) -> str:
"""Searches the API for the query."""
return "Results"
在这个示例中,工具名称被显式指定为 "search"
,并且 return_direct=True
表示工具的输出将直接返回给用户,而无需进一步处理。
“”“Searches the API for the query.”“”:就是这个tool的描述
@tool(“search”, return_direct=True, args_schema=SearchInput):注解上也可以添加上args_schema
5.创建代理并执行任务
文档还介绍了如何使用工具列表创建一个代理,并通过代理执行任务。initialize_agent()
函数可以接受工具和模型,并生成一个能够处理复杂任务的代理。
from langchain.agents import initialize_agent, AgentType
# 创建代理
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
# 执行代理任务
agent.run("Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?")
这里 AgentType.ZERO_SHOT_REACT_DESCRIPTION
代表代理类型,即零样本反应描述。这意味着代理可以根据描述自动选择合适的工具来执行任务。在示例中,代理先使用搜索工具找到 Leo DiCaprio 女友的名字和年龄,然后使用计算器工具计算其年龄的指数。
6.处理工具错误
如果工具在运行时遇到错误,LangChain提供了 ToolException
机制来处理这些错误。通过设置 handle_tool_error
,开发者可以自定义错误处理逻辑,并确保代理不会因为工具错误而停止工作。
from langchain.schema import ToolException
def _handle_error(error: ToolException) -> str:
return f"The following errors occurred during tool execution: {error.args[0]}"
# 自定义工具
def search_tool1(s: str):
raise ToolException("The search tool1 is not available.")
tools = [
Tool.from_function(
func=search_tool1,
name="Search_tool1",
description="useful for when you need to answer questions about current events",
handle_tool_error=_handle_error
)
]
通过自定义错误处理函数 _handle_error()
,代理可以继续执行任务,而不是在工具失败时终止执行。
Tool Calling
1.什么是Tool Calling
“Tool Calling”(工具调用)与“Function Calling”(函数调用)可以互换使用,都是指模型在响应时生成一个符合用户定义的工具或函数调用的输出。这种机制允许模型生成工具的调用参数,但实际执行工具或函数仍然是由用户(或外部系统)来完成的。工具调用的核心包括以下部分:
- 名称(
name
):工具或函数的名称。 - 参数字典(
arguments
):参数的键值对({参数名: 参数值})。 - 可选标识符(
id
):工具调用的唯一标识符。
这种工具调用非常适合从非结构化数据中提取结构化的输出,或构建复杂的工具链。
2.工具调用的支持者
许多 LLM 提供商,如 Anthropic、Cohere、Google、Mistral、OpenAI 等,都支持工具调用功能。这些功能通常允许请求包括可用工具及其模式,并返回工具调用结果。例如,模型可以根据查询先调用一个搜索引擎工具,获取信息后再根据这些信息生成最终的响应。
不同的 LLM 提供商在工具调用的格式上可能有所不同。例如:
-
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" } ] }
3.定义工具模式:LangChain Tool
LangChain 提供了标准接口来定义工具并将其传递给 LLM。例如,可以使用 @tool
装饰器在 Python 函数上定义工具:
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]
通过 @tool
装饰器,可以将普通的 Python 函数转化为可以被 LLM 调用的工具。
4.使用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]
Pydantic 类定义了工具的输入格式,并通过 Field
提供额外的说明和参数验证。
5.绑定工具到模型
接下来,我们可以将这些 Pydantic 模型绑定到聊天模型(Chat Model)中。LangChain 提供了 bind_tools()
方法,用于将工具绑定到语言模型上。绑定后的模型可以在对话过程中调用这些工具。
示例:
from langchain_openai import ChatOpenAI
# 初始化 OpenAI 的 LLM 模型
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
# 将定义好的工具绑定到模型
tools = [add, multiply]
llm_with_tools = llm.bind_tools(tools)
现在,工具已经绑定到模型中,可以通过模型生成器来进行工具调用。
示例请求:
query = "What is 3 * 12? Also, what is 11 + 49?"
tool_calls = llm_with_tools.invoke(query).tool_calls
在这个例子中,模型将调用绑定的工具,并生成如下的 tool_calls
输出:
[
{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_UL7E2232GfDHIQGOM4gJfEDD'},
{'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': 'call_VKw8t5tpAuzvbHgdAXe9mjUx'}
]