大语言模型(LLM)是基于训练数据进行预测并生成结果的,它无法掌握训练数据之外的知识。这一点在前面的文章中已经多次提到,通过 RAG
检索的方式,可以在一定程度上缓解这一问题。
但如果我们希望大模型获取最新的时事信息呢?或者让它查找"北京最好吃的五家餐馆"的地理位置信息?又或者要求它完成一套复杂的数学计算?这些需求仅依靠大语言模型本身的能力显然无法解决。
为此,LangChain 提供了 Tools
组件,可以通过创建自定义工具的方式,让 LLM 调用外部能力:例如,获取时事信息时调用搜索工具,查询地理位置时对接高德地图等 API,执行复杂计算时也能编写专门的工具。有了工具调用,AI 应用在解决实际问题上的能力就能得到进一步提升。
在构建 LLM
对象时,可以将工具列表绑定到模型上。这样,LLM 就能够获取到这些工具的名称、描述和参数定义,并以此作为上下文信息来判断是否需要调用工具。如果需要,它会请求调用一个或多个工具,并传入相应的参数,工具调用流程图如下:

当接收到调用指令和参数后,工具会执行相应逻辑并返回结果。随后,大语言模型会结合工具的输出,对用户的提问生成最终回答。这就是LLM工具调用的基本流程。
要实现LLM工具调用,第一步就是工具的创建。本文将会详细介绍在 LangChain 中如何创建工具,在后续文章中,我们会进一步展开,讲解工具调用的完整流程。
文中所有示例代码:github.com/wzycoding/l...
一、工具是什么
LLM 和工具之间的关系好比:大语言模型像是"大脑",工具就像人的"四肢"。大脑可以对四肢发出指令,而 LLM 则会根据用户的提问来决定调用哪些工具。
所谓工具,其实就是供 LLM 调用的程序,可以是实现特定功能的类或函数,也可以是对外部 API
的封装。
一个工具通常需要包含以下几个部分:
- 工具的名称
- 工具的功能描述
- 工具入参的
JSON Schema
- 执行工具逻辑的函数
在 LangChain
中,支持三种方式来创建工具:
- 函数方式
- 通过
Runnables
创建 - 继承
BaseTool
类
下面将分别介绍这三种工具创建方式。
二、通过函数创建工具
通过函数方式创建工具时,需要配合 @tool
注解,将函数转换为工具。其中,第一个参数默认为工具名称,args_schema
用于指定入参结构,return_direct
表示工具调用完成后是否直接将结果传递给大模型:当值为 True
时,结果会直接返回;当值为 False
时,结果会先经过大模型加工后再返回。示例如下:
python
from langchain_core.tools import tool
from pydantic.v1 import Field, BaseModel
class AddNumberInput(BaseModel):
num1: int = Field(description="第一个数")
num2: int = Field(description="第二个数")
@tool("add-tool", args_schema=AddNumberInput, return_direct=True)
def add(num1: int, num2: int):
"""两数相加"""
return num1 + num2
print(f"工具名称:{add.name}")
print(f"工具描述:{add.description}")
print(f"工具参数:{add.args}")
print(f"是否直接返回:{add.return_direct}")
print("1+1=" + str(add.invoke({"num1": 1, "num2": 1})))
执行结果如下:
python
工具名称:add-tool
工具描述:两数相加
工具参数:{'num1': {'title': 'Num1', 'description': '第一个数', 'type': 'integer'}, 'num2': {'title': 'Num2', 'description': '第二个数', 'type': 'integer'}}
是否直接返回:True
1+1=2
除了使用注解的方式,还可以通过 StructuredTool
来创建工具。StructuredTool.from_function
类方法相比 @tool
注解提供了更多配置项,并且不需要额外编写代码。其中,func
参数用于传入同步执行的函数,coroutine
参数则用于传入异步执行的函数,其余参数的作用与前面介绍的相同。示例如下:
python
import asyncio
from langchain_core.tools import StructuredTool
from pydantic.v1 import BaseModel, Field
class AddNumberInput(BaseModel):
"""加法工具入参"""
num1: int = Field(description="第一个数")
num2: int = Field(description="第二个数")
def add(num1: int, num2: int):
"""两数相加"""
return num1 + num2
async def async_add(num1: int, num2: int):
"""两数相加"""
return num1 + num2
add_tool = StructuredTool.from_function(
func=add,
coroutine=async_add,
name="add_tool",
description="两数相加",
args_schema=AddNumberInput,
return_direct=True,
)
print(f"工具名称:{add_tool.name}")
print(f"工具描述:{add_tool.description}")
print(f"工具参数:{add_tool.args}")
print(f"是否直接返回:{add_tool.return_direct}")
# 同步调用工具
print("1+1=" + str(add_tool.invoke({"num1": 1, "num2": 1})))
# 异步调用工具
async def async_main():
result = await add_tool.ainvoke({"num1": 2, "num2": 5})
print("2+5=" + str(result))
asyncio.run(async_main())
执行结果如下:
python
工具名称:add_tool
工具描述:两数相加
工具参数:{'num1': {'title': 'Num1', 'description': '第一个数', 'type': 'integer'}, 'num2': {'title': 'Num2', 'description': '第二个数', 'type': 'integer'}}
是否直接返回:True
1+1=2
2+5=7
三、通过Runnables创建工具
通过可运行组件(Runnable
)的 as_tool()
方法也可以创建工具。由于由多个可运行组件组成的链(Chain
)本身也是一个 Runnable
,因此可以直接调用 chain.as_tool()
方法,将整个链包装成一个工具。示例如下:
python
import dotenv
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic.v1 import BaseModel, Field
# 读取env配置
dotenv.load_dotenv()
class RandomInput(BaseModel):
"""生成随机数入参"""
count: int = Field(description="生成随机数个数")
# 1.创建提示词模板
prompt = ChatPromptTemplate.from_template("请帮我生成{count}个100以内随机数,只返回随机数本身就好")
# 2.构建GPT-3.5模型
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 3.创建输出解析器
parser = StrOutputParser()
# 4.执行链
chain = prompt | llm | parser
random_tool = chain.as_tool(name="random_tool", description="生成100以内随机数", args_schema=RandomInput)
print("生成随机数:" + str(random_tool.invoke({"count": 10})))
执行结果:
python
生成随机数:27, 63, 84, 12, 95, 41, 55, 73, 38, 9
四、通过继承BaseTool类创建工具
还可以通过继承 BaseTool
来创建自定义工具。这种方式的自由度最高,也更加灵活。示例如下:创建 AddNumberTool
类继承 BaseTool
,并指定工具相关参数,同时重写 _run()
方法,在方法中实现具体的工具逻辑。
python
from langchain_core.tools import BaseTool
from pydantic.v1 import BaseModel, Field
class AddNumberInput(BaseModel):
"""加法工具入参"""
num1: int = Field(description="第一个数")
num2: int = Field(description="第二个数")
class AddNumberTool(BaseTool):
"""加法工具"""
name = "add_number_tool"
description = "两数相加工具"
args_schema = AddNumberInput
def _run(self, num1: int, num2: int) -> int:
return num1 + num2
add_number_tool = AddNumberTool()
print(f"工具名称:{add_number_tool.name}")
print(f"工具描述:{add_number_tool.description}")
print(f"工具参数:{add_number_tool.args}")
print(f"是否直接返回:{add_number_tool.return_direct}")
print("1+1=" + str(add_number_tool.invoke({"num1": 1, "num2": 1})))
执行结果:
python
工具名称:add_number_tool
工具描述:两数相加工具
工具参数:{'num1': {'title': 'Num1', 'type': 'integer'}, 'num2': {'title': 'Num2', 'type': 'integer'}}
是否直接返回:False
1+1=2
五、总结
通过工具,LLM 这个"超级大脑"有了四肢,甚至可以配备各种各样的"武器"。这些"武器"就是提供给 LLM 的工具。有了工具,LLM 不再只是一个聊天助手,而是能够真正解决问题的助手。它可以根据用户的提问内容做出判断,决定需要调用哪些工具,并传递相应的参数。
在 LangChain
中,创建工具的方式有多种,不同方式适用于不同场景:通过函数创建工具,适合实现简单功能;将 Chain
包装成工具,方便已有逻辑的复用;如果需求比较复杂,则可以通过继承 BaseTool
类的方式,实现更灵活的工具。
通过本文的介绍,相信你已经理解了为什么需要工具,以及 LLM 如何进行工具调用。除此之外,我们还重点介绍了多种工具创建方式。在下一篇文章中,将会详细讲解如何让 LLM 实际完成工具调用,欢迎持续关注。