如何在 LangChain 中自定义 Tool 工具?
- 标签:LangChain、Tool 工具
🏛 业务架构师
在 LangChain 里自定义 Tool,我讲三种方式(由轻到重)与业务考量:
方式一·@tool 装饰器(首选):
python
from langchain_core.tools import tool
@tool
def query_order(order_id: str) -> str:
"""根据订单号查询订单状态与物流信息。order_id 为 10 位字母数字。"""
return order_service.get(order_id)函数名、类型注解、docstring 自动转为工具的名称/参数 Schema/描述——docstring 就是给模型看的使用说明书,要写清楚"干什么用、什么时候用、参数含义",这直接决定模型选对工具的准确率。
方式二·StructuredTool.from_function:需要更精细的参数 Schema(描述、必填、枚举)时用,显式声明 args_schema。
方式三·BaseTool 子类化:需要复杂初始化(注入服务依赖、连接池)、内部状态或自定义 _arun 异步实现时用——生产级工具的主流形态,便于挂鉴权、限流、审计切面。
生产化的四个要求(业务视角重点):参数校验兜底(模型传错类型要能优雅报错并给模型可行动的信息)、返回体量控制(大结果截断/摘要)、幂等设计(写操作防重)、权限与审计(谁在什么场景调了什么工具)。
建议:工具用 MCP 协议注册成 Server 更利于跨应用复用——LangChain 工具定义与 MCP 工具可以打通,一次定义处处可用。
🏗 系统架构师
LangChain 自定义 Tool 我给三种姿势(由简到繁):
1. @tool 装饰器(默认推荐):
python
from langchain_core.tools import tool
@tool
def query_order(order_id: str, include_items: bool = False) -> dict:
"""根据订单号查询订单状态。
Args:
order_id: 订单编号,如 A123456
include_items: 是否包含商品明细
"""
return {"status": "shipped", "eta": "2026-10-26"}docstring 就是给模型看的工具描述(描述质量 = 调用准确率),类型注解自动生成 inputSchema。
2. Pydantic 参数模型(复杂参数结构):
python
class SearchInput(BaseModel):
keyword: str = Field(description="搜索关键词")
date_range: str = Field(default="30d", description="时间范围,如 7d/30d")
@tool(args_schema=SearchInput)
def search_docs(**kwargs) -> str: ...3. StructuredTool.from_function / BaseTool 继承(需要运行时状态、异步、流式场景)。
生产要点(架构师视角):
- 描述即 API 文档:写清"何时用、何时不用、返回什么",模型选错工具八成是描述问题;
- 工具要幂等 + 结构化错误:异常别裸抛,返回
{"error": "...", "hint": "请先调用 X 获取 order_id"}——带建议的错误让模型能自救; - 注册进 Agent:
create_agent(model, tools=[...])或 LangGraph 的 ToolNode; - 治理:工具按权限分组注入(不同用户可见不同工具集),调用打点审计走 Callback。
💻 全栈开发专家
LangChain 自定义 Tool 现在的标准做法是 @tool 装饰器(新版),工具 = 函数 + 类型注解 + docstring:
python
from langchain_core.tools import tool
from pydantic import Field
@tool
def query_order(
order_id: str = Field(description="订单号,格式如 A123456")
) -> str:
"""根据订单号查询订单状态与物流信息。用户询问订单进度、发货、物流时使用。"""
order = order_client.get(order_id)
if not order:
return f"未找到订单 {order_id},请确认订单号"
return f"状态:{order.status} 承运商:{order.carrier} 预计送达:{order.eta}"
llm.bind_tools([query_order]) # 绑定给模型关键点(决定工具好不好用):
- docstring 是模型选工具的唯一依据:写清楚「什么时候用」而不仅是「是什么」,最好带触发场景;相邻工具的差异也要点明;
- 参数用类型注解 + Field description:模型按 schema 生成参数,描述含糊参数就幻觉;
- 返回值对模型友好:返回紧凑结构化文本/JSON,报错时返回可读的错误信息(模型能据此重试或向用户解释),别抛异常中断循环;
- 副作用工具要幂等:模型可能重复调用。
Java 侧(LangChain4j/Spring AI)等价实现:
java
@Tool("根据订单号查询订单状态")
public String queryOrder(@P("订单号") String orderId) { ... }进阶:需要复杂参数校验时用 args_schema 传 Pydantic 模型;异步工具加 async 支持;工具结果太大时先摘要再返回。