在 LangChain 中,聊天模型提供了额外的功能:⼯具调⽤。它能使 LLM 与外部服务、API 和数据库进⾏交互。⼯具调⽤还可⽤于从⾮结构化数据中提取结构化信息并执⾏各种其他任务。
例如,当我们希望获取当前天⽓情况时,由于 LLM ⽆法获取实时信息,此时我们就可以借助⼯具,通过外部服务进⾏搜索完成查询:

创建工具

使用@tool装饰器创建工具

在langchain中,实现了一个@tool装饰器来创建工具,@tool装饰器是自定义工具的最简单方法。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_core.tools import tool

@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers.
Args:
a: First integer
b: Second integer
"""
return a * b

print(multiply.invoke({"a": 2, "b": 3})) # 输出:6
print(multiply.name) # 输出:multiply
print(multiply.description) # 输出:Multiply two ...省略...b:
Second integer

print(multiply.args) # 输出:{'a': {'title': 'A', 'type':
'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

工具通过@tool加python函数实现,其中:

  • 改装饰器默认使用函数名称作为工具名称
  • 该装饰器将使用函数的文档字符串作为工具的描述*

工具schema

schema就是描述其他数据结构的声明格式,用于自动验证数据而存在
示例:

1
2
3
4
5
{
"name": "张⼩红",
"birthday": "1732年2⽉22⽇",
"address": "陕西省西安市雁塔区"
}

对于⼯具schema,它将从函数名、类型提⽰和⽂档字符串中获取相关属性,以此来声明⼀个⼯具,包括其名称、描述、输⼊参数、输出类型等等。

依赖Pydantic

若使⽤ @tool 定义⼯具时,没有提供⽂档字符串,则会报错:
在 LangChain 中,可以使⽤ Pydantic 类,提供运⾏时数据验证和类型检查。通过Field(description=“…”) 添加字段描述,LangChain 会⾃动提取。

1
2
3
4
5
6
7
from pydantic import BaseModel, Field
class AddInput(BaseModel):

"""两数相加"""
a: int = Field(..., description="第一个数")
b: int = Field(..., description="第二个数")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pydantic import BaseModel, Field
class AddInput(BaseModel):

"""两数相加"""
a: int = Field(..., description="第一个数")
b: int = Field(..., description="第二个数")


from langchain_core.tools import tool

@tool(args_schema=AddInput)
def add(a: int, b: int) -> int:
# 未提供描述
return a + b

注意是代码中 @tool 的 args_schema 参数,它表⽰⼯具函数在未提供描述、⽂档字符串等需要传递给⼯具 Schema 的内容时,依赖 Pydantic 类使⽤ args_schema 参数,定义并提供⼯具输⼊参数
的schema。默认为 None。
点击运⾏,不会报错,且将来运⾏时会进⾏数据验证。因此,我们再次印证了函数名、类型提⽰和⽂档字符串都是传递给⼯具 Schema 的⼀部分,不可缺失。

依赖Annotated

在 LangChain 中,可以依赖 Annotated 和⽂档字符串传递给⼯具 Schema 。如下所⽰:

1
2
3
4
5
6
7
8
9
10
11
12
from langchain_core.tools import tool

from typing_extensions import Annotated
@tool
def add(
a:Annotated[int,...,"第一个整数"],
b:Annotated[int,...,"第二个整数"],
)->int:
"""两数相加
Args: a:第一个整数
b:第二个整数
""" return a+b

StructuredTool 提供的函数创建工具

class langchain_core.tools.structured.StructuredTool 类⽤来初始化⼯具,其中from_function 类⽅法通过给定的函数来创建并返回⼀个⼯具。 from_function 类⽅法定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
classmethod from_function(
func: Callable | None = None,
coroutine: Callable[[...], Awaitable[Any]] | None = None,
name: str | None = None,
description: str | None = None,
return_direct: bool = False,
args_schema: type[BaseModel] | dict[str, Any] | None = None,
infer_schema: bool = True,
*,
response_format: Literal['content', 'content_and_artifact'] = 'content',
parse_docstring: bool = False,
error_on_invalid_docstring: bool = False,
**kwargs: Any,

) → StructuredTool

•coroutine:协程函数,要设置的异步⼯具函数
•name:⼯具名称。默认为函数名称。
•description:⼯具描述。默认为函数⽂档字符串。
•args_schema:⼯具输⼊参数的schema。默认为 None。
•response_format:⼯具响应格式。默认为“content”。
◦如果配置为 “content” ,则⼯具的输出为 ToolMessage 的 content 属性。
▪对于 HumanMessage 、 AIMessage 已经⻅过,分别表⽰ ⽤⼾消息 和 AI消息响应 ,对于 ToolMessage ,它表⽰对应⼯具⻆⾊所发出的消息。
◦如果配置为 “content_and_artifact” ,则输出应是与 ToolMessage 的 content 属性与 artifact 属性相对应的⼆元组。

常规用法

1
2
3
4
5
6
7
8

from langchain_core.tools import StructuredTool
def multiply(a: int, b: int) -> int:
"""两数相乘"""
return a * b

calculator_tool = StructuredTool.from_function(func=multiply)
print(calculator_tool.invoke({"a": 2, "b": 3})) # 输出:6

依赖Pydantic类和加入response_format配置

举例说明加入response_format配置:
例如我们定义一个搜索天气的tool,如果使用到搜索引擎工具访问"今天天气如何?"时:

  • content可能是“根据最新搜索结果,结果北京晴天,⽓温在25°C到32°C之间。建议穿短袖⾐物。”
  • artifact 可能是某搜索引擎 API 返回的完整 JSON 响应,其中包含多个搜索结果条⽬、每个条⽬的标题、链接、摘要、排名等元数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from langchain_core.tools import StructuredTool  
from pydantic import BaseModel,Field


class Addinput(BaseModel):
"""两数相加"""
a :int = Field(...,description="第一个整数")
b:int=Field(...,description="第二个整数")

def add(a:int,b:int)->tuple[str,list[int]]:
nums=[a,b]
content=f"{nums}相加的结果是{a+b}"
return content,nums


add_tool=StructuredTool.from_function(
func=add,
name="add", # 工具名
description="两数相加",# 工具描述
args_schema=Addinput,# 工具参数
response_format="content_and_artifact"
)

print(add_tool.invoke({"a":2,"b":3}))
#print(add_tool.name)

# print(add_tool.description)
print(add_tool.args)

输出结果

1
2
[2, 3]相加的结果是5
{'a': {'description': '第一个整数', 'title': 'A', 'type': 'integer'}, 'b': {'description': '第二个整数', 'title': 'B', 'type': 'integer'}}

若想要看到⼯具返回的元组,我们需要模拟⼤模型调⽤⼯具的姿势,如下所⽰。这将返回⼀个ToolMessage:

1
2
3
4
5
6
7
8
print(add_tool.invoke(  
{
"name": "Calculator",
"args": {"a": 2, "b": 3},
"id": "123", # 必须,与⼯具调⽤关联的标识符,将⼯具调⽤请求与⼯具调⽤结果相关联。
"type": "tool_call", # 必须
}
))

输出结果

1
content='[2, 3]相加的结果是5' name='add' tool_call_id='123' artifact=[2, 3]

由于 LLM ⼤多理解⽂本,所以⼯具的主要输出 content 必须是结构良好、简洁的⽂本,以便模型能够轻松理解和基于它进⾏推理、⽣成下⼀步的指令。
在链(Chain)中,⼯具调⽤之后的其他组件或函数,可能需要⼯具的原始且结构化数据(即artifact )来执⾏特定操作。这些数据可能是庞⼤的、且⾮⽂本的。这些数据不适合直接塞给模型。因此, artifact 其实是为了给链中后续的组件或函数使⽤的,不被⼤模型所直接使⽤