LangChain Tools 完全解析:从基础到生产级实践(附完整代码)
这是 LangChain Agent 进阶系列的第二篇。上一篇我们深入探讨了
create_agent的核心参数和流式模式,本文我们将聚焦于 LangChain 工具(Tools)——智能体的“双手”。掌握工具的定义、调用机制和高级用法,是构建生产级 Agent 的核心。
本文目标:学完之后,你将能够:
- 理解
BaseTool、Callable、Runnable三者的关系和设计思想 - 使用三种方式(
@tool、StructuredTool、继承BaseTool)定义工具 - 实现参数校验、重试、降级、限流、权限校验等生产级特性
- 在多工具调用中管理状态和中间结果
阅读建议:本文代码量较大,建议边看边运行。所有代码均配有详细注释,请耐心阅读。
1. 工具(Tools)是什么?为什么需要它?
"""
1.1
LangChain 工具(Tools)完整解析:
工具是智能体的“双手”,让 LLM 从“只会说”变成“能够做”。掌握工具的定义、调用机制和高级用法,是构建生产级 Agent 的核心。
一句话解释:LLM 本身无法执行外部操作(查数据库、发邮件、调用 API),工具为 LLM 提供了与外部世界交互的能力。
在生产场景中,你需要:
- 查询实时数据(天气、股票、订单)
- 执行写操作(发送邮件、创建工单)
- 调用内部 API(获取用户信息、更新数据库)
实际过程演示:
用户输入: "帮我查北京的天气"
↓
LLM 推理: 需要调用 get_weather 工具
↓
Agent 生成工具调用请求: {"name": "get_weather", "args": {"location": "北京"}}
↓
Agent 执行工具: get_weather("北京") → "北京天气:晴天 25°C"
↓
工具结果封装为 ToolMessage 返回给 LLM
↓
LLM 基于结果生成最终回答: "北京的天气是晴天,25°C"
"""
2. 【极其重要的扫盲知识点】LangChain 核心类型:BaseTool、Callable、Runnable 详解
在深入工具定义之前,必须先理解这三个核心类型。它们共同构成了 LangChain 的“乐高积木”体系。
一句话总结:LangChain 通过统一的抽象接口,让不同类型的组件(模型、工具、检索器、链)可以像“乐高积木”一样互相组合,而无需关心内部实现差异。
BaseTool:定义了“工具”的规范,让 Agent 能够统一调用任意外部能力。Callable:Python 内置的可调用对象类型,在 LangChain 中常作为函数签名,表示“任何可被调用的东西”。Runnable:LangChain 最核心的抽象,所有可运行组件(模型、链、工具、检索器)都实现它,提供invoke、stream、batch等统一方法。
2.1 BaseTool:工具的抽象基类
BaseTool 是 LangChain 中所有工具的基类。它定义了工具必须具备的接口,确保 Agent 能够以统一的方式调用任何工具。
为什么需要?
- 类型安全:让 Agent 知道哪些对象是工具。
- 自动生成 Schema:为 LLM 提供工具的描述和参数信息。
- 统一调用:通过
_run/_arun方法执行逻辑。
LangChain 的 Agent 在调用工具时,只认 BaseTool 的接口。也就是说,Agent 期望每个工具都有:
- 一个固定的名字 (
name) - 一段描述 (
description) - 一个执行方法 (
_run或_arun) - 一个参数结构 (
args_schema)
如果你自己写一个普通函数 def get_weather(city): ...,Agent 不知道如何调用它,也不知道它的参数是什么。继承 BaseTool 就是按照标准格式包装你的逻辑,让 Agent 能识别和使用它。
手写工具类示例:
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
# 1. 定义参数格式
class WeatherInput(BaseModel):
city: str = Field(description="城市名称")
# 2. 继承 BaseTool
class WeatherTool(BaseTool):
name = "get_weather"
description = "获取指定城市的天气"
args_schema = WeatherInput
def _run(self, city: str) -> str:
# 这里写真实的天气查询逻辑
return f"{city} 天气晴朗"
当你把这个 WeatherTool 实例传给 create_agent(tools=[WeatherTool()]) 时,Agent 就能:
- 看到工具名字和描述
- 知道需要一个参数
city - 当需要时,调用
_run(city="北京")并拿到结果
@tool 装饰器让你不用手动写一个继承 BaseTool 的类,也能得到一个工具。它把你写的普通函数自动包装成 BaseTool 的子类实例。
2.2 Callable:Python 内置的可调用类型
Callable 是 Python 标准库 typing 中的类型,表示任何可以像函数一样调用的对象(函数、方法、类、实现了 __call__ 的对象)。在 LangChain 代码里,它用来表示“你给我一个函数,我就能把它做成工具”。
LangChain 大量使用 Callable 作为参数类型,表示“你可以传入任何可调用的东西”。例如在创建工具时:
from langchain.tools import tool
from typing import Callable
def my_func(x: int) -> str:
return str(x)
# 传入一个 Callable
@tool
def my_tool(x: int) -> str:
return str(x)
# 等价于:
def create_tool_from_callable(func: Callable) -> BaseTool:
return tool(func)
# 这里 func: Callable 表示“你可以传任何可调用的东西进来”。这是文档和类型提示,不是运行时的逻辑。
2.3 Runnable:LangChain 的统一接口(非常重要)
Runnable 是 langchain_core.runnables 中的核心抽象。所有能够被“运行”的组件都实现了这个接口:
- 聊天模型(
ChatModel) - 检索器(
Retriever) - 工具(
BaseTool) - 链(
Chain) - 提示模板(
PromptTemplate)
为什么需要?
- 统一调用方式:无论什么组件,都用
invoke、stream、batch等方法。 - 可组合性:通过
|操作符将多个Runnable串联成管道。 - 异步/批量支持:自动适配同步和异步调用。
| 方法 | 作用 | 说明 | |
|---|---|---|---|
invoke(input) | 单次调用 | 最常用,返回结果 | |
stream(input) | 流式输出 | 适用于生成式模型 | |
batch(inputs) | 批量调用 | 一次处理多个输入 | |
ainvoke/astream/abatch | 异步版本 | 用于异步编程 | |
pipe(other) | 组合 | 等价于 ` | ` 操作符 |
实际使用示例:
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableLambda
# 所有这些都是 Runnable
model = ChatOpenAI(model="gpt-4o")
prompt = "写一首关于 {topic} 的诗"
# 定义转换函数(也是 Runnable)
def make_upper(x: str) -> str:
return x.upper()
# 组合成链
chain = prompt | model | RunnableLambda(make_upper)
# 统一调用
result = chain.invoke({"topic": "春天"})
print(result) # 输出全大写的诗
从 LangChain 1.0 开始,BaseTool 也实现了 Runnable 接口。这意味着你可以直接:
tool = WeatherTool()
result = tool.invoke({"location": "北京"}) # 直接调用,无需 Agent
2.4 其他常用基类
除了上述三个,LangChain 中还有一系列抽象基类,它们共同构成了框架的“类型系统”:
| 基类 | 作用 | 实现者 |
|---|---|---|
BaseMessage | 消息基类 | HumanMessage, AIMessage, SystemMessage, ToolMessage |
BaseChatModel | 聊天模型基类 | ChatOpenAI, ChatAnthropic |
BaseRetriever | 检索器基类 | VectorStoreRetriever, MultiQueryRetriever |
BaseVectorStore | 向量存储基类 | FAISS, Chroma, Pinecone |
BaseMemory | 记忆基类 | ConversationBufferMemory, ConversationSummaryMemory |
BaseOutputParser | 输出解析器 | PydanticOutputParser, StrOutputParser |
这些基类的设计初衷与 BaseTool 相同:定义统一接口,让组件可互换、可组合。
2.5 它们之间的关系
┌─────────────────────────────────────────────────────────────┐
│ Runnable │
│ (所有可运行组件的统一接口:invoke, stream, batch) │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌─────────┴─────────┐ ┌────┴─────┐ ┌────────┴────────┐
│ BaseChatModel │ │ BaseTool │ │ BaseRetriever │
│ (模型) │ │ (工具) │ │ (检索器) │
└───────────────────┘ └──────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
具体实现类 具体实现类 具体实现类
(ChatOpenAI) (WeatherTool) (VectorStoreRetriever)
Runnable是最高抽象,定义了“如何执行”。BaseTool是工具领域的Runnable,专注于“执行外部操作”。Callable是 Python 层面的类型,在 LangChain 中常被用作Runnable的构造块(如RunnableLambda将函数包装为Runnable)。
2.6 生产实践:组合使用
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# 1. 定义工具(继承 BaseTool,也是 Runnable)
@tool
def search(query: str) -> str:
return f"搜索结果:{query}"
# 2. 定义模型(BaseChatModel,也是 Runnable)
model = ChatOpenAI(model="gpt-4o")
# 3. 定义转换函数(包装为 RunnableLambda)
def format_response(x: str) -> str:
return f"最终答案:{x}"
# 4. 组合成链
chain = search | model | RunnableLambda(format_response)
# 5. 统一调用(invoke 自动传递)
result = chain.invoke("LangChain 教程")
print(result)
# 输出:最终答案:搜索结果:LangChain 教程
search是BaseTool,也是Runnable。model是BaseChatModel,也是Runnable。RunnableLambda将普通函数包装为Runnable。|操作符将它们串联,形成更大的Runnable。
3. 三种定义工具的方式
在实际开发中,根据场景选择合适的定义方式。下表是快速参考:
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
@tool 装饰器 | 简单函数 | 最简洁,自动生成 schema | 复杂参数需额外处理 |
StructuredTool | 需要精细控制 | 可自定义名称、描述、参数验证 | 代码稍多 |
继承 BaseTool | 复杂工具(含状态) | 完全控制 | 代码量大 |
3.1 方式一:@tool 装饰器(最常用)
@tool 是 LangChain 提供的一个快捷方式,让你不用手动写一个继承 BaseTool 的类,也能得到一个工具。它把你写的普通函数自动包装成 BaseTool 的子类实例。
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_weather(location: str) -> str:
"""获取天气信息"""
return "晴天 25°C"
@tool
def search(query: str) -> str:
"""搜索信息。"""
return f"结果:{query}"
agent = create_agent(
model="deepseek-chat",
tools=[search, get_weather]
)
参数类型支持:使用 Pydantic 模型定义复杂参数
from pydantic import BaseModel, Field
from langchain.tools import tool
class WeatherInput(BaseModel):
location: str = Field(description="城市名称,如'北京'")
unit: str = Field(default="celsius", description="温度单位,可选 'celsius' 或 'fahrenheit'")
@tool(args_schema=WeatherInput)
def get_weather(location: str, unit: str = "celsius") -> str:
"""获取指定城市的天气"""
return f"{location} 天气:25°{unit.upper()}"
args_schema 是 @tool 装饰器的一个参数,用于显式指定一个 Pydantic 模型,精确控制工具参数的名称、类型、描述、默认值、校验规则等,从而让 LLM 能够准确理解并生成符合预期的调用参数。
当你使用上述工具时,LangChain 会生成一个 JSON schema 发送给 LLM:
{
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如'北京'"
},
"unit": {
"type": "string",
"description": "温度单位,可选 'celsius' 或 'fahrenheit'",
"default": "celsius"
}
},
"required": ["location"]
}
}
LLM 根据这些描述,就能正确提取用户输入中的信息并填入参数。
补充:Pydantic 是什么?
Pydantic 是一个 Python 第三方库,它让你用类型注解来定义数据模型,然后它会自动帮你:
- 验证数据是否符合类型(比如字符串不能当整数)
- 把传入的字典自动转换成模型对象
- 自动生成 JSON Schema(让 LLM 知道这个数据结构长什么样)
一个直观的例子:假设你要让 LLM 从一段文本中提取联系人信息,你希望它返回:
{ "name": "张三", "phone": "13812345678" }你可以用 Pydantic 定义这个结构:
from pydantic import BaseModel, Field class Contact(BaseModel): name: str = Field(description="联系人姓名") phone: str = Field(description="手机号码")然后把这个模型传给 LangChain,它就会:
- 告诉 LLM 输出应该符合这个结构
- 自动把 LLM 返回的文本解析成
Contact对象- 如果 LLM 返回的格式不对,还能尝试修复或报错
BaseModel是 Pydantic 的基础类,用于创建数据模型。
3.2 方式二:StructuredTool(精确控制)
普通 Tool 的局限:传统的
Tool或Tool.from_function()在输入处理上存在明显限制。它通常只接受单一字符串作为输入,模型需自行拼接参数,这会导致多参数函数调用困难、参数类型不明晰、缺乏输入校验,以及模型调用错误率高等问题。当工具涉及多个参数或复杂结构输入时,这种方式显得笨拙且易出错。为此,LangChain 引入了结构化版本的工具:StructuredTool。参考原文链接:blog.csdn.net/cooldream20…
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
# BaseModel 是 Pydantic 库中的一个强大工具,用于数据验证和序列化
class WeatherInput(BaseModel):
location: str = Field(description="城市名称,如'北京'")
unit: str = Field(default="celsius", description="温度单位,可选 'celsius' 或 'fahrenheit'")
def get_weather_impl(location: str, unit: str = "celsius") -> str:
"""获取指定城市的天气"""
if unit == "celsius":
return f"{location} 天气:25°{unit.upper()}"
else:
return f"{location} 天气:77°{unit.upper()}"
weather_tool = StructuredTool.from_function(
func=get_weather_impl, # 你实现这个工具的逻辑来自于哪个函数?
name="get_weather", # 你要为这个工具取什么名字,默认为函数名字
description="获取指定城市的天气", # 这个工具描述是干什么的?
args_schema=WeatherInput, # 这个工具的 pydantic 模式是什么?
return_direct=True, # 若为 True,则工具返回结果后直接结束,不再经过 LLM
response_format="content" # 返回的格式是什么?
)
# 使用工具
result = weather_tool.invoke({"location": "北京", "unit": "celsius"})
print(result) # 输出:北京 天气:25°C
StructuredTool 的本质:它是 LangChain 工具定义的“显式模式”,为你提供对工具行为的全面控制,是 @tool 装饰器的底层实现。当你需要:
- 精确控制参数模式(使用 Pydantic 模型)
- 同时支持同步和异步(提供
func和coroutine) - 自定义错误处理(
handle_tool_error) - 返回结果后直接结束(
return_direct) - 注入外部依赖(通过闭包或工厂函数)
时,选择 StructuredTool 能让代码更清晰、更健壮。对于绝大多数简单工具,@tool 装饰器已经足够,但当你迈向生产级复杂度时,StructuredTool 是你的得力助手。
核心原则:保持代码简单,但在需要灵活性时,毫不犹豫地使用 StructuredTool。
3.3 方式三:继承 BaseTool(复杂状态)
from langchain.tools import BaseTool
from typing import Type, Any
from torchgen.api.cpp import return_type # 注意:这是用户原始导入,实际使用时应删除
class DatabaseQueryTool(BaseTool):
"""A tool that queries a database."""
name: str = "database_query"
description: str = "执行 sql 查询语句,返回结果"
args_schema: Type[BaseModel] = SQLInput # SQLInput 需提前定义
def _run(self, sql: str) -> str:
"""查询逻辑,省略"""
# result = execute_sql(sql)
return str(result)
async def _arun(self, sql: str) -> str:
"""异步版本"""
pass
# 上述函数+字段都是重写(Override)
4. 多工具调用:串行与并行
4.1 串行调用(默认行为)
当 LLM 决定依次调用多个工具时,Agent 会逐个执行:
# 用户: "帮我查北京天气,然后推荐适合的活动"
# LLM 可能生成:
tool_calls = [
{"name": "get_weather", "args": {"location": "北京"}, "id": "call_1"},
{"name": "recommend_activity", "args": {"weather": "晴天"}, "id": "call_2"}
]
执行顺序:
- 执行
get_weather→ToolMessage(id=call_1) - 将结果传回 LLM,LLM 根据结果决定是否调用
recommend_activity - 执行
recommend_activity→ToolMessage(id=call_2)
4.2 并行调用(自动支持)
如果 LLM 在同一次响应中发出多个工具调用,Agent 会并行执行它们:
# LLM 在一次响应中返回两个工具调用
tool_calls2 = [
{"name": "get_weather", "args": {"location": "北京"}, "id": "call_1"},
{"name": "get_weather", "args": {"location": "上海"}, "id": "call_2"}
]
# Agent 会同时执行这两个工具(使用 asyncio.gather)
# 大幅降低总耗时
生产建议:确保你的工具是线程安全的,且支持并发调用。
4.3 动态工具选择(由 LLM 推理决定)
LLM 根据前一步的工具结果,决定下一步调用哪个工具。这是 ReAct 模式的核心。
示例:多步推理
- 用户: "帮我查北京天气,如果下雨就推荐室内活动,否则推荐户外"
- 步骤1: LLM 调用
get_weather("北京")→ 返回 "晴天" - 步骤2: LLM 看到结果,决定调用
recommend_outdoor_activity() - 步骤3: 返回最终答案
5. 工具错误处理中间件
5.1 基础错误处理中间件
from langchain.agents.middleware import wrap_tool_call
from langchain_core.messages import ToolMessage
@wrap_tool_call
def handle_tool_error(request, handler):
"""
捕获工具执行异常,返回友好的错误消息。
这样 LLM 可以自行决定重试或告知用户。
"""
try:
return handler(request)
except Exception as e:
return ToolMessage(
id=request.id,
name="error",
args={"message": str(e)}
)
5.2 高级错误处理:带重试和降级
import time
from typing import Any
@wrap_tool_call
def resilient_tool_middleware(request, handler):
"""
组合功能:
1. 参数校验(预检查)
2. 指数退避重试
3. 降级策略(主工具失败后尝试备用工具)
"""
tool_name = request.tool_call.name
args = request.tool_call["args"]
# ========== 1. 参数预校验 ==========
if tool_name == "database_query":
try:
SQLInput(**args)
except Exception as e:
return ToolMessage(
id=request.id,
name="error",
args={"message": f"参数校验失败:{str(e)}"}
)
# ========== 2. 重试逻辑 ==========
last_exception = None
for attempt in range(3):
try:
return handler(request) # 调用工具
except Exception as e:
last_exception = e
if attempt < 2:
time.sleep(2 ** attempt)
else:
break
# ========== 3. 降级策略 ==========
if tool_name == "primary_search":
try:
# 修改请求,改用备用工具
request.tool_call["name"] = "fallback_search"
return handler(request)
except Exception as e:
last_exception = e
# 所有尝试失败!返回错误消息
return ToolMessage(
id=request.id,
name="error",
args={"message": f"工具调用失败:{str(last_exception)}"}
)
6. 工具调用之间的状态持久化(进阶)
工具执行过程中产生的中间数据,可以通过 Agent 的 state 在多次工具调用之间共享。
6.1 使用自定义状态
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from typing import TypedDict, Any
# 定义扩展状态
class MyState(AgentState):
intermediate_results: dict # 存储中间结果
tool_sequence: list # 记录工具调用顺序
# 中间件:在工具执行前注入状态
class StateAwareMiddleware(AgentMiddleware):
state_schema = MyState
def before_tool_call(self, state: MyState, runtime, tool_call) -> dict | None:
# 记录工具调用开始
tool_seq = state.get("tool_sequence", [])
tool_seq.append({
"tool": tool_call["name"],
"args": tool_call["args"],
"timestamp": time.time()
})
return {"tool_sequence": tool_seq}
def after_tool_call(self, state: MyState, runtime, tool_call, result):
# 保存中间结果
intermediate = state.get("intermediate_results", {})
intermediate[tool_call["id"]] = result
return {"intermediate_results": intermediate}
# 工具中可以直接访问 state
@tool
def smart_tool(query: str, state: MyState) -> str:
# 可以读取之前工具的结果
previous_results = state.get("intermediate_results", {})
# 基于历史结果做决策
return f"基于前序结果 {previous_results},查询 {query} 返回..."
6.2 使用 InjectedState 自动注入
LangChain 提供了 InjectedState 注解,让工具自动接收 state:
from langchain.tools import tool, InjectedState
from langchain.agents import AgentState
from typing import Annotated
@tool
def smart_search(query: str, state: Annotated[AgentState, InjectedState]) -> str:
"""智能搜索,会结合历史结果"""
# state 会自动注入
previous = state.get("intermediate_results", {})
return f"基于历史 {previous},搜索 {query}"
7. 生产级工具实践
7.1 工具日志与监控
from langchain.tools import tool
import time
import logging
logger = logging.getLogger(__name__)
@tool
def monitored_api_call(endpoint: str, payload: dict) -> str:
"""
带监控的 API 调用工具
"""
start = time.time()
try:
# 实际调用
result = call_external_api(endpoint, payload)
duration = time.time() - start
# 记录指标
logger.info(f"API调用成功: {endpoint}, 耗时: {duration:.2f}s")
# 生产环境可发送到监控系统(如 Prometheus)
# metrics.record("api_call_duration", duration, labels={"endpoint": endpoint})
return result
except Exception as e:
duration = time.time() - start
logger.error(f"API调用失败: {endpoint}, 耗时: {duration:.2f}s, 错误: {e}")
# 可选:发送告警
raise
7.2 工具限流与配额
from langchain.tools import tool
from collections import defaultdict
import time
# 简单的速率限制器
class RateLimiter:
def __init__(self, max_calls, period):
self.max_calls = max_calls
self.period = period
self.calls = defaultdict(list)
def is_allowed(self, user_id: str):
now = time.time()
window_start = now - self.period
self.calls[user_id] = [t for t in self.calls[user_id] if t > window_start]
if len(self.calls[user_id]) >= self.max_calls:
return False
self.calls[user_id].append(now)
return True
limiter = RateLimiter(max_calls=10, period=60) # 每分钟最多10次
@tool
def limited_tool(query: str, user_id: str = None) -> str:
"""
限制调用频率的工具
"""
if user_id and not limiter.is_allowed(user_id):
raise Exception("调用频率过高,请稍后再试")
return f"处理查询: {query}"
7.3 工具权限校验
from langchain.tools import tool
from typing import Annotated
from langchain.agents import InjectedState
@tool
def delete_user(user_id: str, state: Annotated[dict, InjectedState]) -> str:
"""
删除用户(仅管理员可用)
"""
user_role = state.get("user_role", "guest")
if user_role != "admin":
raise PermissionError("只有管理员可以删除用户")
# 执行删除
return f"用户 {user_id} 已删除"
8. 完整生产示例:带状态、重试、监控的工具集
下面是一个完整的生产级 Agent 示例,整合了状态管理、工具重试中间件、状态注入中间件。
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call, AgentMiddleware
from langchain.tools import tool, InjectedState
from langchain_openai import ChatOpenAI
from typing import Annotated, TypedDict
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ========== 1. 定义状态 ==========
class AppState(TypedDict):
messages: list
user_id: str
call_count: int
# ========== 2. 定义工具 ==========
@tool
def search_knowledge(query: str) -> str:
"""搜索知识库。参数 query: 搜索关键词"""
logger.info(f"搜索知识库: {query}")
# 模拟耗时操作
time.sleep(0.5)
return f"知识库结果: 关于 '{query}' 有 10 条记录"
@tool
def query_database(sql: str, state: Annotated[dict, InjectedState]) -> str:
"""执行 SQL 查询(只读)。参数 sql: SQL 语句"""
user_id = state.get("user_id", "unknown")
logger.info(f"用户 {user_id} 执行 SQL: {sql}")
# 安全检查
if "DROP" in sql.upper() or "DELETE" in sql.upper():
raise ValueError("不允许执行写操作")
# 模拟数据库查询
return f"查询结果: 共 {len(sql)} 条记录"
# ========== 3. 中间件:工具重试 + 错误处理 ==========
@wrap_tool_call
def resilient_tools(request, handler):
"""带重试和日志的工具中间件"""
tool_name = request.tool_call["name"]
logger.info(f"调用工具: {tool_name}, 参数: {request.tool_call['args']}")
for attempt in range(3):
try:
result = handler(request)
logger.info(f"工具 {tool_name} 成功")
return result
except Exception as e:
logger.warning(f"工具 {tool_name} 第 {attempt + 1} 次失败: {e}")
if attempt == 2:
# 最后一次失败,返回友好错误
from langchain_core.messages import ToolMessage
return ToolMessage(
content=f"工具执行失败: {str(e)}。请稍后重试或联系管理员。",
tool_call_id=request.tool_call["id"]
)
time.sleep(2 ** attempt)
# ========== 4. 中间件:状态注入 ==========
class StateInjector(AgentMiddleware):
def before_model(self, state: AppState, runtime):
# 确保状态中有 user_id
if "user_id" not in state:
state["user_id"] = "anonymous"
if "call_count" not in state:
state["call_count"] = 0
return state
# ========== 5. 创建 Agent ==========
agent = create_agent(
model=ChatOpenAI(model="gpt-4o", temperature=0),
tools=[search_knowledge, query_database],
middleware=[resilient_tools, StateInjector()],
state_schema=AppState
)
# ========== 6. 调用示例 ==========
result = agent.invoke({
"messages": [{"role": "user", "content": "帮我搜索 LangChain 文档,然后查询数据库中的相关记录"}],
"user_id": "user_12345"
})
print(result["messages"][-1].content)
写在最后
本文完整保留了原始 .py 笔记中 95% 以上的注释和讲解,并按照 “扫盲知识点 → 三种定义方式 → 调用模式 → 错误处理 → 状态持久化 → 生产实践 → 完整示例” 的顺序组织。希望通过这份详实的教学代码,你能彻底掌握 LangChain 工具的定义、调用和高级用法。
关键 Takeaways:
- 理解三个核心类型:
BaseTool(工具规范)、Callable(Python 可调用)、Runnable(LangChain 统一接口) - 选择合适的工具定义方式:简单函数用
@tool,需要精细控制用StructuredTool,复杂状态用继承BaseTool - 多工具调用支持串行和并行:LLM 一次返回多个工具调用时会自动并行执行
- 错误处理中间件:实现参数校验、重试、降级,避免 Agent 因单个工具失败而崩溃
- 状态持久化:通过
AgentState和InjectedState在多次工具调用间共享中间结果 - 生产实践:日志监控、限流、权限校验缺一不可
本文为个人学习笔记与实践总结,如有错误或疏漏,欢迎指正交流。
如果觉得本文对你有帮助,欢迎点赞、收藏、转发!
个人的源代码笔记:
"""
1.1
LangChain 工具(Tools)完整解析:
工具是智能体的“双手”,让 LLM 从“只会说”变成“能够做”。掌握工具的定义、调用机制和高级用法,是构建生产级 Agent 的核心。
一句话解释:LLM 本身无法执行外部操作(查数据库、发邮件、调用 API),工具为 LLM 提供了与外部世界交互的能力。
在生产场景中,你需要:
查询实时数据(天气、股票、订单)
执行写操作(发送邮件、创建工单)
调用内部 API(获取用户信息、更新数据库)
实际过程演示:
用户输入: "帮我查北京的天气"
↓
LLM 推理: 需要调用 get_weather 工具
↓
Agent 生成工具调用请求: {"name": "get_weather", "args": {"location": "北京"}}
↓
Agent 执行工具: get_weather("北京") → "北京天气:晴天 25°C"
↓
工具结果封装为 ToolMessage 返回给 LLM
↓
LLM 基于结果生成最终回答: "北京的天气是晴天,25°C"
"""
from langchain_classic.chains.sql_database.query import SQLInput
from torchgen.api.cpp import return_type
"""
补充内容:【极其重要的扫盲知识点!】
LangChain 核心类型 BaseTool、Callable、Runnable 详解
一句话总结:LangChain 通过统一的抽象接口,让不同类型的组件(模型、工具、检索器、链)可以像“乐高积木”一样互相组合,而无需关心内部实现差异。
BaseTool:定义了“工具”的规范,让 Agent 能够统一调用任意外部能力。
Callable:Python 内置的可调用对象类型,在 LangChain 中常作为函数签名,表示“任何可被调用的东西”。
Runnable:LangChain 最核心的抽象,所有可运行组件(模型、链、工具、检索器)都实现它,提供 invoke、stream、batch 等统一方法。
1.BaseTool:工具的抽象基类
BaseTool 是 LangChain 中所有工具的基类。它定义了工具必须具备的接口,确保 Agent 能够以统一的方式调用任何工具。
为什么需要?
类型安全:让 Agent 知道哪些对象是工具。
自动生成 Schema:为 LLM 提供工具的描述和参数信息。
统一调用:通过 _run / _arun 方法执行逻辑。
LangChain 的 Agent(智能体)在调用工具时,只认 BaseTool 的接口。也就是说,Agent 期望每个工具都有:一个固定的名字 (name)
一段描述 (description)一个执行方法 (_run 或 _arun)一个参数结构 (args_schema)
如果你自己写一个普通函数 def get_weather(city): ...,Agent 不知道如何调用它,也不知道它的参数是什么。继承 BaseTool 就是按照标准格式包装你的逻辑,让 Agent 能识别和使用它。
手写工具类:
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
# 1. 定义参数格式
class WeatherInput(BaseModel):
city: str = Field(description="城市名称")
# 2. 继承 BaseTool
class WeatherTool(BaseTool):
name = "get_weather"
description = "获取指定城市的天气"
args_schema = WeatherInput
def _run(self, city: str) -> str:
# 这里写真实的天气查询逻辑
return f"{city} 天气晴朗"
当你把这个 WeatherTool 实例传给 create_agent(tools=[WeatherTool()]) 时,Agent 就能:看到工具名字和描述
知道需要一个参数 city,当需要时,调用 _run(city="北京") 并拿到结果
@tool让你不用手动写一个继承 BaseTool 的类,也能得到一个工具。它把你写的普通函数自动包装成 BaseTool 的子类实例。
2.Callable:Python 内置的可调用类型:
在 LangChain 代码里,它用来表示“你给我一个函数,我就能把它做成工具”。
Callable(able+Call:可以调用的) 是 Python 标准库 typing 中的类型,表示任何可以像函数一样调用的对象(函数、方法、类、实现了 __call__ 的对象)。
LangChain 大量使用 Callable 作为参数类型,表示“你可以传入任何可调用的东西”。例如在创建工具时:
from langchain.tools import tool
from typing import Callable
def my_func(x: int) -> str:
return str(x)
# 传入一个 Callable
@tool
def my_tool(x: int) -> str:
return str(x)
# 等价于:
def create_tool_from_callable(func: Callable) -> BaseTool:
return tool(func)
# 这里 func: Callable 表示“你可以传任何可调用的东西进来”。这是文档和类型提示,不是运行时的逻辑。
3.Runnable:LangChain 的统一接口:非常重要
Runnable 是 langchain_core.runnables 中的核心抽象。所有能够被“运行”的组件都实现了这个接口:
聊天模型(ChatModel)检索器(Retriever)工具(BaseTool)链(Chain)提示模板(PromptTemplate)
为什么需要?
统一调用方式:无论什么组件,都用 invoke、stream、batch 等方法。
可组合性:通过 | 操作符将多个 Runnable 串联成管道。
异步/批量支持:自动适配同步和异步调用。
方法 作用 说明
invoke(input) 单次调用 最常用,返回结果
stream(input) 流式输出 适用于生成式模型
batch(inputs) 批量调用 一次处理多个输入
ainvoke/astream/abatch 异步版本 用于异步编程
pipe(other) 组合 等价于 | 操作符
总结三者:
类型 本质 在 LangChain 中的角色
BaseTool 工具的抽象基类 定义外部能力的统一接口,让 Agent 可调用
Callable Python 内置类型 表示任何可调用的东西,常用于参数注入
Runnable LangChain核心抽象 统一所有可运行组件的接口,实现无限组合
核心设计思想:
开闭原则:新增组件只需实现对应基类,无需修改框架。
组合优于继承:通过 Runnable 的 | 操作符自由组装。
统一体验:无论模型、工具、检索器,都用 invoke/stream 调用。
实际使用示例:
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableLambda
# 所有这些都是 Runnable
model = ChatOpenAI(model="gpt-4o")
prompt = "写一首关于 {topic} 的诗"
# 定义转换函数(也是 Runnable)
def make_upper(x: str) -> str:
return x.upper()
# 组合成链
chain = prompt | model | RunnableLambda(make_upper)
# 统一调用
result = chain.invoke({"topic": "春天"})
print(result) # 输出全大写的诗
从 LangChain 1.0 开始,BaseTool 也实现了 Runnable 接口。这意味着你可以直接:
tool = WeatherTool()
result = tool.invoke({"location": "北京"}) # 直接调用,无需 Agent
4.其他常用基类
除了上述三个,LangChain 中还有一系列抽象基类,它们共同构成了框架的“类型系统”:
基类 作用 实现者
BaseMessage 消息基类 HumanMessage, AIMessage, SystemMessage, ToolMessage
BaseChatModel 聊天模型基类 ChatOpenAI, ChatAnthropic
BaseRetriever 检索器基类 VectorStoreRetriever, MultiQueryRetriever
BaseVectorStore 向量存储基类 FAISS, Chroma, Pinecone
BaseMemory 记忆基类 ConversationBufferMemory, ConversationSummaryMemory
BaseOutputParser 输出解析器 PydanticOutputParser, StrOutputParser
这些基类的设计初衷与 BaseTool 相同:定义统一接口,让组件可互换、可组合。
5.它们之间的关系
┌─────────────────────────────────────────────────────────────┐
│ Runnable │
│ (所有可运行组件的统一接口:invoke, stream, batch) │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌─────────┴─────────┐ ┌────┴─────┐ ┌────────┴────────┐
│ BaseChatModel │ │ BaseTool │ │ BaseRetriever │
│ (模型) │ │ (工具) │ │ (检索器) │
└───────────────────┘ └──────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
具体实现类 具体实现类 具体实现类
(ChatOpenAI) (WeatherTool) (VectorStoreRetriever)
Runnable 是最高抽象,定义了“如何执行”。
BaseTool 是工具领域的 Runnable,专注于“执行外部操作”。
Callable 是 Python 层面的类型,
!!!:在 LangChain 中常被用作 Runnable 的构造块(如 RunnableLambda 将函数包装为 Runnable)。
生产实践:组合使用
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# 1. 定义工具(继承 BaseTool,也是 Runnable)
@tool
def search(query: str) -> str:
return f"搜索结果:{query}"
# 2. 定义模型(BaseChatModel,也是 Runnable)
model = ChatOpenAI(model="gpt-4o")
# 3. 定义转换函数(包装为 RunnableLambda)
def format_response(x: str) -> str:
return f"最终答案:{x}"
# 4. 组合成链
chain = search | model | RunnableLambda(format_response)
# 5. 统一调用(invoke 自动传递)
result = chain.invoke("LangChain 教程")
print(result)
# 输出:最终答案:搜索结果:LangChain 教程
search 是 BaseTool,也是 Runnable。
model 是 BaseChatModel,也是 Runnable。
RunnableLambda 将普通函数包装为 Runnable。
| 操作符将它们串联,形成更大的 Runnable。
"""
#1.1 正确的定义工具
# 方式 适用场景 优点 缺点
# @tool装饰器 简单函数 最简洁,自动生成 schema 复杂参数需额外处理
# StructuredTool 需要精细控制 可自定义名称、描述、参数验证 代码稍多
# 继承 BaseTool 复杂工具(含状态) 完全控制 代码量大
# 1 方式一:@tool 装饰器(最常用)、
"""
@tool 装饰器做了什么?
@tool 是 LangChain 提供的一个快捷方式,
让你不用手动写一个继承 BaseTool 的类,也能得到一个工具。它把你写的普通函数自动包装成 BaseTool 的子类实例。
"""
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_weather(location: str) -> str:
"""获取天气信息"""
return "晴天 25°C"
@tool
def search(query: str) -> str:
"""搜索信息。"""
return f"结果:{query}"
agent = create_agent(
model="deepseek-chat",
tools=[search, get_weather]
)
# 参数类型支持:可以使用 Pydantic 模型定义复杂参数。
from pydantic import BaseModel,Field
from langchain.tools import tool
class WeatherInput(BaseModel):
location: str = Field(description="城市名称,如'北京'")
unit: str = Field(default="celsius", description="温度单位,可选 'celsius' 或 'fahrenheit'")
@tool(args_schema=WeatherInput)
def get_weather(location: str, unit: str = "celsius") -> str:
"""获取指定城市的天气"""
return f"{location} 天气:25°{unit.upper()}"
# args_schema 是 @tool 装饰器的一个参数,用于显式指定一个 Pydantic 模型,
# 精确控制工具参数的名称、类型、描述、默认值、校验规则等,从而让 LLM 能够准确理解并生成符合预期的调用参数。
# 当你使用上述工具时,LangChain 会生成一个 JSON schema(框架;模式) 发送给 LLM:
"""{
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters":
{
"type": "object",
"properties":
{
"location":
{
"type": "string",
"description": "城市名称,如'北京'"
},
"unit":
{
"type": "string",
"description": "温度单位,可选 'celsius' 或 'fahrenheit'",
"default": "celsius"
}
},
"required": ["location"]
}
}"""
#LLM 根据这些描述,就能正确提取用户输入中的信息并填入参数。
"""
补充:Pydantic
是一个 Python 第三方库,它让你用类型注解来定义数据模型。然后它会自动帮你:
Pydantic 是一个强大的 Python 数据验证和管理库,通过类型注解定义数据模型,确保数据的一致性和有效性
人话:就是帮你“定义数据格式并自动验证”的工具
作用:
验证数据是否符合类型(比如字符串不能当整数)
把传入的字典自动转换成模型对象
自动生成 JSON Schema(让 LLM 知道这个数据结构长什么样)
一个直观的例子
假设你要让 LLM 从一段文本中提取联系人信息,你希望它返回:
{
"name": "张三",
"phone": "13812345678"
}
你可以用 Pydantic 定义这个结构:
from pydantic import BaseModel, Field
class Contact(BaseModel):
name: str = Field(description="联系人姓名")
phone: str = Field(description="手机号码")
然后把这个模型传给 LangChain,它就会:
告诉 LLM 输出应该符合这个结构
自动把 LLM 返回的文本解析成 Contact 对象
如果 LLM 返回的格式不对,还能尝试修复或报错
BaseModel 是:A base class for creating Pydantic models.
"""
"""
普通 Tool 的局限
传统的 Tool 或 Tool.from_function() 在输入处理上存在明显限制。它通常只接受单一字符串作为输入,模型需自行拼接参数,
这会导致多参数函数调用困难、参数类型不明晰、缺乏输入校验,以及模型调用错误率高等问题。
当工具涉及多个参数或复杂结构输入时,这种方式显得笨拙且易出错。为此,LangChain 引入了结构化版本的工具:StructuredTool。
参考原文链接:https://blog.csdn.net/cooldream2009/article/details/153915289
"""
#方式二:StructuredTool(精确控制)
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
#BaseModel 是 Pydantic 库中的一个强大工具,用于数据验证和序列化
class WeatherInput(BaseModel):
location: str = Field(description="城市名称,如'北京'")
unit: str = Field(default="celsius", description="温度单位,可选 'celsius' 或 'fahrenheit'")
def get_weather_impl(location: str, unit: str = "celsius") -> str:
"""获取指定城市的天气"""
if unit=="celsius":
return f"{location} 天气:25°{unit.upper()}"
else:
return f"{location} 天气:77°{unit.upper()}"
weather_tool = StructuredTool.from_function(
func=get_weather_impl, #你实现这个工具的逻辑来自于哪个函数?
name="get_weather", #你要为这个工具取什么名字,默认为函数名字
description="获取指定城市的天气", #这个工具描述是干什么的?
args_schema=WeatherInput, #这个工具的pydantic模式是什么?
return_direct=True, #若为 True,则工具返回结果后直接结束,不再经过 LLM
response_format="content" #范围的格式是什么?
)
# 使用工具
result = weather_tool.invoke({"location": "北京", "unit": "celsius"})
print(result) # 输出:北京 天气:25°C
"""
StructuredTool 的本质:它是 LangChain 工具定义的“显式模式”,为你提供对工具行为的全面控制,是 @tool 装饰器的底层实现。当你需要:
精确控制参数模式(使用 Pydantic 模型)
同时支持同步和异步(提供 func 和 coroutine)
自定义错误处理(handle_tool_error)
返回结果后直接结束(return_direct)
注入外部依赖(通过闭包或工厂函数)
时,选择 StructuredTool 能让代码更清晰、更健壮。对于绝大多数简单工具,@tool 装饰器已经足够,但当你迈向生产级复杂度时,StructuredTool 是你的得力助手。
核心原则:保持代码简单,但在需要灵活性时,毫不犹豫地使用 StructuredTool
"""
#方式三:继承 BaseTool(复杂状态)
from langchain.tools import BaseTool
from typing import Type, Any
class DatabaseQueryTool(BaseTool):
"""A tool that queries a database."""
name:str="database_query"
description:str="执行sql查询语句,返回结果"
args_schema:Type[BaseModel] =SQLInput
def _run(self,sql:str) -> str:
"""查询逻辑,省略"""
return str(result)
async def _arun(self, sql: str) -> str:
"""异步版本"""
pass
#上述函数+字段都是重写,都是Override
# 1.2
# 多工具调用:串行与并行
# 1 串行调用(默认行为)
# 当 LLM 决定依次调用多个工具时,Agent 会逐个执行:
# 用户: "帮我查北京天气,然后推荐适合的活动"
# LLM 可能生成:
tool_calls = [
{"name": "get_weather", "args": {"location": "北京"}, "id": "call_1"},
{"name": "recommend_activity", "args": {"weather": "晴天"}, "id": "call_2"}
]
"""
执行顺序:
1. 执行 get_weather → ToolMessage(id=call_1)
2. 将结果传回 LLM,LLM 根据结果决定是否调用 recommend_activity
3. 执行 recommend_activity → ToolMessage(id=call_2)
"""
# 2 并行调用(自动支持)如果 LLM 在同一次响应中发出多个工具调用,Agent 会并行执行它们:
# LLM 在一次响应中返回两个工具调用
tool_calls2 = [
{"name": "get_weather", "args": {"location": "北京"}, "id": "call_1"},
{"name": "get_weather", "args": {"location": "上海"}, "id": "call_2"}
]
# Agent 会同时执行这两个工具(使用 asyncio.gather)
# 大幅降低总耗时;生产建议:确保你的工具是线程安全的,且支持并发调用。
# 3 动态工具选择(由 LLM 推理决定)
# LLM 根据前一步的工具结果,决定下一步调用哪个工具。这是 ReAct 模式的核心。
# 示例:多步推理
# 用户: "帮我查北京天气,如果下雨就推荐室内活动,否则推荐户外"
#
# 步骤1: LLM 调用 get_weather("北京") → 返回 "晴天"
# 步骤2: LLM 看到结果,决定调用 recommend_outdoor_activity()
# 步骤3: 返回最终答案
#2.1基础错误处理中间件
from langchain.agents.middleware import wrap_tool_call
from langchain_core.messages import ToolMessage
@wrap_tool_call
def handle_tool_error(request,handler):
"""
捕获工具执行异常,返回友好的错误消息。
这样 LLM 可以自行决定重试或告知用户。
"""
try:
return handler(request)
except Exception as e:
return ToolMessage(
id=request.id,
name="error",
args={"message": str(e)}
)
#2.2 高级错误处理:带重试和降级
import time
from typing import Any
@wrap_tool_call
def resilient_tool_middleware(request, handler):
"""
组合功能:
1. 参数校验(预检查)
2. 指数退避重试
3. 降级策略(主工具失败后尝试备用工具)
"""
tool_name = request.tool_call.name
args = request.tool_call["args"]
# ========== 1. 参数预校验 ==========
if tool_name == "database_query":
try:
SQLInput(**args)
except Exception as e:
return ToolMessage(
id=request.id,
name="error",
args={"message": f"参数校验失败:{str(e)}"}
)
# ========== 2. 重试逻辑 ==========
last_exception = None
for attempt in range(3):
try:
return handler(request) # 调用工具
except Exception as e:
last_exception = e
if attempt < 2:
time.sleep(2 ** attempt)
else:
break
# ========== 3. 降级策略 ==========
if tool_name == "primary_search":
try:
#修改请求,改用备用该工具
request.tool_call["name"] = "fallback_search"
return handler(request)
except Exception as e:
last_exception = e
# 所有尝试失败!返回错误消息
return ToolMessage(
id=request.id,
name="error",
args={"message": f"工具调用失败:{str(last_exception)}"}
)
# =================(拓展)进阶,不做要求=================
# 六、工具调用之间的状态持久化
# 工具执行过程中产生的中间数据,可以通过Agent的state在多次工具调用之间共享。
# 6.1
# 使用自定义状态
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from typing import TypedDict, Any
# 定义扩展状态
class MyState(AgentState):
intermediate_results: dict # 存储中间结果
tool_sequence: list # 记录工具调用顺序
# 中间件:在工具执行前注入状态
class StateAwareMiddleware(AgentMiddleware):
state_schema = MyState
def before_tool_call(self, state: MyState, runtime, tool_call) -> dict | None:
# 记录工具调用开始
tool_seq = state.get("tool_sequence", [])
tool_seq.append({
"tool": tool_call["name"],
"args": tool_call["args"],
"timestamp": time.time()
})
return {"tool_sequence": tool_seq}
def after_tool_call(self, state: MyState, runtime, tool_call, result):
# 保存中间结果
intermediate = state.get("intermediate_results", {})
intermediate[tool_call["id"]] = result
return {"intermediate_results": intermediate}
# 工具中可以直接访问 state
@tool
def smart_tool(query: str, state: MyState) -> str:
# 可以读取之前工具的结果
previous_results = state.get("intermediate_results", {})
# 基于历史结果做决策
return f"基于前序结果 {previous_results},查询 {query} 返回..."
# 6.2
# 在工具中使用InjectedStateLangChain 的InjectedState注解,让工具自动接收state:
from langchain.tools import tool, InjectedState
from langchain.agents import AgentState
from typing import Annotated
@tool
def smart_search(query: str, state: Annotated[AgentState, InjectedState]) -> str:
"""智能搜索,会结合历史结果"""
# state 会自动注入
previous = state.get("intermediate_results", {})
return f"基于历史 {previous},搜索 {query}"
# 七、生产级工具实践7.1工具日志与监控
from langchain.tools import tool
import time
import logging
logger = logging.getLogger(__name__)
@tool
def monitored_api_call(endpoint: str, payload: dict) -> str:
"""
带监控的 API 调用工具
"""
start = time.time()
try:
# 实际调用
result = call_external_api(endpoint, payload)
duration = time.time() - start
# 记录指标
logger.info(f"API调用成功: {endpoint}, 耗时: {duration:.2f}s")
# 生产环境可发送到监控系统(如 Prometheus)
# metrics.record("api_call_duration", duration, labels={"endpoint": endpoint})
return result
except Exception as e:
duration = time.time() - start
logger.error(f"API调用失败: {endpoint}, 耗时: {duration:.2f}s, 错误: {e}")
# 可选:发送告警
raise
# 7.2工具限流与配额
from langchain.tools import tool
from collections import defaultdict
import time
# 简单的速率限制器
class RateLimiter:
def __init__(self, max_calls, period):
self.max_calls = max_calls
self.period = period
self.calls = defaultdict(list)
def is_allowed(self, user_id: str):
now = time.time()
window_start = now - self.period
self.calls[user_id] = [t for t in self.calls[user_id] if t > window_start]
if len(self.calls[user_id]) >= self.max_calls:
return False
self.calls[user_id].append(now)
return True
limiter = RateLimiter(max_calls=10, period=60) # 每分钟最多10次
@tool
def limited_tool(query: str, user_id: str = None) -> str:
"""
限制调用频率的工具
"""
if user_id and not limiter.is_allowed(user_id):
raise Exception("调用频率过高,请稍后再试")
return f"处理查询: {query}"
# 7.3工具权限校验
from langchain.tools import tool
from typing import Annotated
from langchain.agents import InjectedState
@tool
def delete_user(user_id: str, state: Annotated[dict, InjectedState]) -> str:
"""
删除用户(仅管理员可用)
"""
user_role = state.get("user_role", "guest")
if user_role != "admin":
raise PermissionError("只有管理员可以删除用户")
# 执行删除
return f"用户 {user_id} 已删除"
# 八、完整生产示例:带状态、重试、监控的工具集
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call, AgentMiddleware
from langchain.tools import tool, InjectedState
from langchain_openai import ChatOpenAI
from typing import Annotated, TypedDict
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ========== 1. 定义状态 ==========
class AppState(TypedDict):
messages: list
user_id: str
call_count: int
# ========== 2. 定义工具 ==========
@tool
def search_knowledge(query: str) -> str:
"""搜索知识库。参数 query: 搜索关键词"""
logger.info(f"搜索知识库: {query}")
# 模拟耗时操作
time.sleep(0.5)
return f"知识库结果: 关于 '{query}' 有 10 条记录"
@tool
def query_database(sql: str, state: Annotated[dict, InjectedState]) -> str:
"""执行 SQL 查询(只读)。参数 sql: SQL 语句"""
user_id = state.get("user_id", "unknown")
logger.info(f"用户 {user_id} 执行 SQL: {sql}")
# 安全检查
if "DROP" in sql.upper() or "DELETE" in sql.upper():
raise ValueError("不允许执行写操作")
# 模拟数据库查询
return f"查询结果: 共 {len(sql)} 条记录"
# ========== 3. 中间件:工具重试 + 错误处理 ==========
@wrap_tool_call
def resilient_tools(request, handler):
"""带重试和日志的工具中间件"""
tool_name = request.tool_call["name"]
logger.info(f"调用工具: {tool_name}, 参数: {request.tool_call['args']}")
for attempt in range(3):
try:
result = handler(request)
logger.info(f"工具 {tool_name} 成功")
return result
except Exception as e:
logger.warning(f"工具 {tool_name} 第 {attempt + 1} 次失败: {e}")
if attempt == 2:
# 最后一次失败,返回友好错误
from langchain_core.messages import ToolMessage
return ToolMessage(
content=f"工具执行失败: {str(e)}。请稍后重试或联系管理员。",
tool_call_id=request.tool_call["id"]
)
time.sleep(2 ** attempt)
# ========== 4. 中间件:状态注入 ==========
class StateInjector(AgentMiddleware):
def before_model(self, state: AppState, runtime):
# 确保状态中有 user_id
if "user_id" not in state:
state["user_id"] = "anonymous"
if "call_count" not in state:
state["call_count"] = 0
return state
# ========== 5. 创建 Agent ==========
agent = create_agent(
model=ChatOpenAI(model="gpt-4o", temperature=0),
tools=[search_knowledge, query_database],
middleware=[resilient_tools, StateInjector()],
state_schema=AppState
)
# ========== 6. 调用示例 ==========
result = agent.invoke({
"messages": [{"role": "user", "content": "帮我搜索 LangChain 文档,然后查询数据库中的相关记录"}],
"user_id": "user_12345"
})
print(result["messages"][-1].content)