LangChain:Tool(工具)的概念、创建和绑定

LangChain的 Tool(工具)就像给大语言模型这类“大脑”装上了“手和脚”。大模型本身只能输出文本,但通过工具系统,它能够与外部世界互动,执行诸如搜索、调用API、读写文件等具体操作,从而突破自身能力的局限

核心概念

Tool(工具)扩展了 智能体(agents) 的能力,允许它们获取实时数据、执行代码、查询外部数据库以及在真实世界中采取行动。

在底层,工具是可调用的函数,具有明确定义的输入和输出,这些会被传递给聊天模型。模型会根据对话上下文决定何时调用工具,以及提供哪些输入参数。

Tool(工具)核心概念:

  • **工具 (Tool)**:一个封装了特定功能的可调用对象,包含名称、描述、参数和执行逻辑。
  • 工具调用 (Tool Calling):指大模型根据你的请求,自主决定并生成调用哪个工具的指令的过程。模型本身不执行工具,只生成指令。
  • **函数调用 (Function Calling)**:由模型提供商(如OpenAI)提供的原生底层接口,而工具调用是LangChain在此基础上的高级封装,提供了更易用的标准化接口。在LangChain里,可以把“函数调用”理解为“工具调用”的底层技术基础。

简单来说,Tool 是一个封装了特定功能(如查询天气、调用API、搜索数据库)的通用接口。它的核心价值在于:

  • 拓展能力边界:让只擅长文本处理的 LLM,获得与现实世界交互的能力。
  • 实现智能决策:为 Agent(智能体)提供“手脚”,使其能够根据任务目标,自主选择合适的工具并执行,完成复杂任务。

Tool属性方法

Tool(工具)继承自 Runnable接口,关键属性和方法:

属性/方法 说明 作用说明
name 名称 工具的唯一标识符,方便代理(Agent)快速识别和调用。
description 描述 用自然语言解释工具的功能。
模型会据此判断是否应该调用该工具。
args 参数 工具需要哪些输入参数的JSON结构,帮助模型生成正确的参数。
invoke() 同步调用 同步调用工具
ainvoke() 异步调用 异步调用工具

一个标准的LangChain Tool(工具),主要由以下几部分构成:

组成部分 作用说明
名称 (Name) 工具的唯一标识符,方便代理(Agent)快速识别和调用。
描述 (Description) 用自然语言解释工具的功能。这是最关键的部分,模型会据此判断是否应该调用该工具。
参数模式 (Args Schema) 定义工具需要哪些输入参数的JSON结构,帮助模型生成正确的参数。
执行逻辑 (Execution Logic) 工具被调用时实际运行的底层代码或逻辑。
返回控制 (Return Direct) 控制工具执行后的结果是直接返回给用户,还是继续交给模型处理。

Tool调用流程

典型的工具调用流程通常包含以下几个步骤:

  1. 定义工具:首先定义好一个或多个工具。
  2. 绑定工具:将定义好的工具通过 bind_tools 方法与你的模型绑定。
  3. 模型决策:当用户提问时,模型会根据指令和工具的描述,决定是否调用工具,并生成调用所需的结构化参数。
  4. 解析与执行:你可以解析模型的返回结果,提取出工具调用指令和参数,然后手动调用相应的工具。
  5. 返回结果:将工具执行的结果返回给模型,模型会基于这个结果继续生成最终的应答。

以下是一个Python示例,演示如何通过绑定工具实现模型的自主决策和执行:

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
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

# 1. 定义工具
@tool
def get_weather(city: str) -> str:
"""根据城市名,获取该城市的实时天气。"""
# 这里用模拟数据代替真实API调用
return f"{city}的天气是晴天,温度25度。"

# 2. 创建模型并绑定工具
model = ChatOpenAI(model="gpt-3.5-turbo")
model_with_tools = model.bind_tools([get_weather])

# 3. 用户提问,模型调用工具
user_input = "北京今天天气如何?"
response = model_with_tools.invoke(user_input)

# 4. 解析模型返回的工具调用指令并执行
tool_calls = response.tool_calls
if tool_calls:
for tc in tool_calls:
# 从调用指令中获取函数名和参数
selected_tool = {"get_weather": get_weather}[tc["name"]]
tool_result = selected_tool.invoke(tc["args"])
print(tool_result) # 输出: 北京的天气是晴天,温度25度。

Tool的创建

在 LangChain 1.0+ 中主要有三种创建方式,以满足从简到繁的各种需求。

默认情况下,函数的文档字符串会成为工具的描述,帮助模型理解何时使用它:需要类型提示,因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁,以帮助模型理解工具的目的。

@tool 装饰器

@tool 装饰器 (推荐用于绝大多数场景),这是最简单、最推荐的方式,尤其适合将现有的函数快速转换为工具。

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

# 最基础的用法:函数名就是工具名,函数文档字符串就是工具描述
@tool
def search_database(query: str, limit: int = 10) -> str:
"""在客户数据库中搜索与查询条件匹配的记录。

Args:
query: 要查找的搜索词。
limit: 返回结果的最大数量。
"""
return f"找到了 {limit} 条关于 '{query}' 的记录。"
  • 必须添加类型注解 —— 它们定义工具的输入 JSON Schema
  • 必须编写清晰的 docstring —— 这是模型理解工具用途的唯一依据

自定义tool名称

默认情况下,工具名称来自函数名称。当你需要更具描述性的名称时,可以覆盖它:

1
2
3
4
5
6
@tool("web_search")  # Custom name
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"

print(search.name) # web_search

自定义tool描述

覆盖自动生成的工具描述,以便为模型提供更清晰的指导:

1
2
3
4
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
"""Evaluate mathematical expressions."""
return str(eval(expression))

自定义参数描述

可以使用 Annotated 工具更简洁地为函数参数添加描述

1
2
3
4
5
6
7
from typing_extensions import Annotated

@tool
def add(a: Annotated[int, ..., "第一个整数"],
b: Annotated[int, ..., "第二个整数"]) -> int:
"""Add two integers."""
return a + b

高级模式定义

使用 Pydantic 模型或 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 导入 Pydantic 的基础模型类和字段定义
from pydantic import BaseModel, Field
# 导入 typing 模块中的 Literal 类型,用于限定参数值只能是指定的字面量
from typing import Literal

# ------------------------------------------------------------------
# 1. 定义 Pydantic 模型,用于描述工具的输入参数结构
# ------------------------------------------------------------------
class WeatherInput(BaseModel):
"""
Input for weather queries. # 模型的文档字符串,说明该模型用于天气查询的输入
"""
# 定义一个必填字段 location,类型为 str,描述为 "City name or coordinates"
location: str = Field(description="City name or coordinates")

# 定义一个可选字段 units,类型限定为 "celsius" 或 "fahrenheit"
# 默认值为 "celsius",描述温度单位偏好
units: Literal["celsius", "fahrenheit"] = Field(
default="celsius", description="Temperature unit preference"
)

# 定义一个可选字段 include_forecast,类型为 bool,默认值为 False
# 描述为 "Include 5-day forecast"
include_forecast: bool = Field(
default=False, description="Include 5-day forecast"
)

# ------------------------------------------------------------------
# 2. 使用 @tool 装饰器,并传入 args_schema=WeatherInput
# 这意味着该工具函数接受的结构化输入将由 WeatherInput 模型校验和生成
# 注:@tool 通常由 LangChain 或类似框架提供,用于将普通函数注册为可被大模型调用的工具
# ------------------------------------------------------------------
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""
Get current weather and optional forecast. # 工具的文档字符串,描述其功能
"""
# 模拟天气数据:根据单位返回温度值(celsius 返回 22,fahrenheit 返回 72)
temp = 22 if units == "celsius" else 72

# 构造基本的天气信息字符串:地点、温度、温度单位首字母(C 或 F)
# 例如 "Current weather in Beijing: 22 degrees C"
result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"

# 如果 include_forecast 为 True,则附加一个简单的 5 日预报字符串
if include_forecast:
result += "\nNext 5 days: Sunny"

# 返回最终的天气信息字符串
return result

# ------------------------------------------------------------------
# 用法说明:
# ------------------------------------------------------------------
# 1. 定义 Pydantic 模型 WeatherInput,严格描述了工具调用时需要哪些参数、参数的类型、默认值和描述。
# 2. 在 @tool 装饰器中通过 args_schema=WeatherInput 将模型与函数绑定。
# 框架(如 LangChain)会根据这个模型自动生成该工具的 JSON 模式(JSON Schema),
# 大模型在调用工具时,会按照该模式提供参数,框架会校验参数并转换为函数所需的 Python 类型。
# 3. 函数 get_weather 的参数名、类型、默认值必须与 WeatherInput 模型中的字段保持一致(顺序可不同)。
# 4. 当模型调用工具时,例如:
# - get_weather(location="London") → 使用默认 units="celsius", include_forecast=False
# - get_weather(location="New York", units="fahrenheit", include_forecast=True)
# 5. 该模式的好处:
# - 参数校验自动化(类型、必填/可选、字面量限制)
# - 自动生成清晰的 API 描述(供大模型理解)
# - 支持复杂的嵌套模型和字段验证

代码解释:代码定义了一个使用 Pydantic 模型(WeatherInput)来描述函数输入参数的工具(tool),该工具可用于天气查询场景。

  • **WeatherInput**:一个 Pydantic 模型,定义了工具输入的结构,包括字段类型、默认值和描述。
  • **@tool(args_schema=WeatherInput)**:装饰器,将函数 get_weather 包装成一个“工具”,并关联输入校验模式。该装饰器通常来自 LLM 工具调用框架(如 LangChain),使得大模型可以依据 Pydantic 模型自动生成正确的函数调用参数。
  • **get_weather**:实际的业务逻辑函数,接收与模型对应的参数,返回字符串结果。
  • 执行效果:当大模型需要获取天气时,框架会提供符合 WeatherInput 结构的 JSON 对象,经过校验后转为 Python 参数传递给 get_weather,函数的返回值再返回给大模型。

保留的参数名称

以下参数名称是保留的,不能用作工具参数。使用这些名称会导致运行时错误。要访问运行时信息,请使用 ToolRuntime 参数,而不是将自己的参数命名为 configruntime

StructuredTool

StructuredTool (用于需要更多控制的场景):当需要异步执行、对工具生命周期进行更精细控制时,可以使用StructuredTool.from_function 方法。

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

def multiply(a: int, b: int) -> int:
"""将两个整数相乘。"""
return a * b

async def amultiply(a: int, b: int) -> int:
return a * b

# 创建可以同时支持同步和异步调用的工具
structured_tool = StructuredTool.from_function(
func=multiply,
coroutine=amultiply,
name="multiply",
description="将两个整数相乘。"
)

继承BaseTool

继承 BaseTool (用于高级自定义):这是最复杂的方式,但提供了最大的灵活性,允许你完全控制工具的行为,例如集成复杂的验证逻辑。

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

class MyCustomTool(BaseTool):
name: str = "custom_tool"
description: str = "一个自定义工具。"

def _run(self, query: str) -> str:
# 在 _run 中实现同步逻辑
return f"你查询的内容是:{query}"

async def _arun(self, query: str) -> str:
# 在 _arun 中实现异步逻辑
return await some_async_operation(query)

Tool的绑定

创建 Tool:这是第一步,你需要使用上面介绍的方法,通过函数定义一个 Tool。

bind_tools绑定 Tool

bind_tools 绑定 Tool:为了让 LLM “知道” 有哪些工具可用,需要将工具列表绑定到 LLM 上,这就是工具绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 假设 tools_list 是一个包含了你定义的工具的列表
llm_with_tools = llm.bind_tools([search_database, calculate])
response = llm_with_tools.invoke(messages)
# 检查是否生成工具调用请求
if response.tool_calls:
for tool_call in response.tool_calls:
print(f"调用工具: {tool_call['name']}, 参数: {tool_call['args']}")
# 执行工具函数并获取结果
result = weather_tool.invoke(tool_call["args"])
# 将结果构造成 ToolMessage 传回模型
messages.append({"role": "tool", "content": result, "tool_call_id": tool_call["id"]})

# 模型基于工具结果生成最终回答
final_response = model.invoke(messages)

绑定成功后,当给 LLM 发送用户消息时,它可能会在响应中附带一个请求调用工具的指令,这就是工具调用

create_agent(可选)

在 LangChain 1.0+ 中,官方推荐通过 create_agent 来构建智能体,它会内置一个标准的 Agent 循环,自动处理工具调用的解析、执行和结果返回。

1
2
3
4
5
6
7
from langchain.agents import create_agent

agent = create_agent(
model="your_preferred_model", # 模型
tools=[search_database, web_search, multiply], # 工具列表
systemPrompt="你是一个有帮助的助手。", # 系统提示词
)

Tool高级特性

tool_choice强制执行工具

**强制执行工具 (tool_choice)**:可以强制 LLM 必须调用某个特定的工具。例如,强制模型调用之前定义的 multiply 工具。这在需要确保特定工作流执行时非常有用(例如先查数据库再回答问题)。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent

from model.deepseek.config import DeepSeekConfig

# 定义两个简单的工具
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气"""
return f"{city} 的天气是晴天,25°C"

@tool
def get_time(city: str) -> str:
"""获取指定城市的当前时间"""
return f"{city} 的当前时间是下午 3 点"

config = DeepSeekConfig()
# 初始化大模型(可使用国内模型)
llm = ChatOpenAI(
model=config.model,
api_key=config.api_key,
base_url=config.base_url,
temperature=0
)

# 定义系统提示
system_prompt = """你是一个非常有用的生活助手,可以当地的提供天气和时间信息。"""

# 绑定工具,并强制要求模型调用 get_weather
# llm = ChatOpenAI(model="gpt-4o-mini")
# tool_choice 可以指定为工具名称,或使用统一格式 {"type": "function", "function": {"name": "get_weather"}}
llm_with_forced_tool = llm.bind_tools(
tools=[get_weather, get_time],
tool_choice="get_weather" # 强制模型必须调用 get_weather
)

messages = [
SystemMessage(content=system_prompt),
HumanMessage(content="深圳现在几点了?天气怎么样?")
]

# 用户的问题 (即使问时间,模型也会被迫调用天气工具)
response = llm_with_forced_tool.invoke(messages)
# 检查响应中的 tool_calls
print(response.tool_calls)
# 输出: [{'name': 'get_weather', 'args': {'city': '深圳'}, 'id': 'call_00_clXA9QrXlGYcQlbBrM0U4440', 'type': 'tool_call'}]

print(response.content) # 没有结果

# 解析模型返回的工具调用指令并执行
tool_calls = response.tool_calls
if tool_calls:
for tc in tool_calls:
# 从调用指令中获取函数名和参数
selected_tool = {"get_weather": get_weather}[tc["name"]]
tool_result = selected_tool.invoke(tc["args"])
print(tool_result) # 输出: 北京的天气是晴天,温度25度。

# LangChain 1.0的另一个重大更新是官方推荐使用 create_agent API 来构建代理。
# 它提供了一种比手动循环更简单、更标准、可维护性更强的代理构建方式。
# 创建代理时,只需将工具列表传入即可
# 代理会根据用户输入自动循环调用合适的工具
agent = create_agent(llm, [get_weather, get_time])
result = agent.invoke({"messages": messages})
print(result["messages"][-1].content)

代码解释:

当模型被强制调用工具时(tool_choice=”get_weather”),模型的响应主要是工具调用,而 response.content 可能是空的或者很少的内容,这是因为:

  • tool_choice 也可以设为 "any"(强制调用任意一个工具)或 "auto"(默认,模型自主决定)。在 OpenAI 风格的 API 中,还可以使用 {"type": "function", "function": {"name": "tool_name"}}
  • 模型的主要输出是 tool_calls,而不是文本内容。
  • 当使用 tool_choice 强制调用工具时,模型会专注于生成工具调用参数,可能不会生成额外的文本内容。

return_direct直接返回结果

**直接返回 (return_direct)**:若将某个工具的 return_direct 属性设为 True,当该工具被调用后,Agent 会将结果直接返回给用户,不再根据结果进行后续的思考和处理。适合那些本身就是最终信息(比如“搜索结束”、“计算完毕”)的工具。

中间件与工具编排:对于复杂场景,还支持通过中间件实现复杂的工具编排,如强制工具的顺序执行、让 LLM 根据上下文自主规划多步工具调用等。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from langchain.agents import create_agent
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

from model.deepseek.config import DeepSeekConfig


@tool(return_direct=True) # 关键参数
def final_answer(answer: str) -> str:
"""当已经得到最终答案时,使用此工具直接回复用户。"""
return answer

@tool
def search(query: str) -> str:
"""模拟搜索,返回一个中间结果"""
return f"关于 '{query}' 的搜索结果:……(此处省略内容)"

# llm = ChatOpenAI(model="gpt-4o-mini")

config = DeepSeekConfig()
# 初始化大模型(可使用国内模型)
llm = ChatOpenAI(
model=config.model,
api_key=config.api_key,
base_url=config.base_url,
temperature=0
)

agent = create_agent(
model=llm,
tools=[search, final_answer]
)

system_prompt = "你是一个助手。如果你已经收集到足够信息,就用 final_answer 工具直接给出最终答案。"
messages = [
SystemMessage(content=system_prompt),
HumanMessage(content="深圳今天天气怎么样?")
]

# 用户问一个需要搜索的问题
result = agent.invoke({"messages": messages})
print(result["messages"][-1].content)
# 假设模型先调用 search,然后根据搜索结果决定调用 final_answer,
# 而 final_answer 被调用后直接返回其参数内容,Agent 循环终止。

代码解释

  • return_direct=True 不会影响模型是否选择该工具,只是在该工具执行完毕后,跳过 Agent 的“再思考”步骤。
  • 必须配合 Agent(如 create_agent)才能体现其作用;如果只是手动调用工具,该属性无效。

最佳实践

  • 工具命名规范:工具的名称应使用字母、数字、下划线、连字符(即 snake_case 格式,如 web_search)来保证最佳兼容性,避免使用空格或其他特殊字符。
  • 工具描述清晰:工具的 description 至关重要,模型主要依靠它来判断使用场景。请务必提供清晰、具体的描述,最好在docstring中通过 Args:Returns: 清晰说明参数的用途和返回值的含义。
  • 合理设计参数:利用类型提示(Type Hints)和字段描述(Field description)来精确地定义参数,这能帮助模型更准确地生成参数。
  • 错误处理:工具内部应包含完善的错误处理逻辑,防止因 API 调用失败等异常导致整个 Agent 运行崩溃。
  • 从简到繁:刚上手时,专注于1-2个简单的工具来验证整体工作流,同时建议在 LangSmith 这类可观测性平台上密切监控工具调用的轨迹,以方便调试和优化。

通过以上步骤,就可以在 LangChain 1.0+ 中创建出强大且可靠的 Tool,让 LLM 应用的能力得到极大的扩展。

LangChain:Tool(工具)的概念、创建和绑定

http://blog.gxitsky.com/2026/05/05/AI-LangChain-046-Tool-Create-Bind/

作者

光星

发布于

2026-05-05

更新于

2026-05-05

许可协议

评论