Skip to content

如何在 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])   # 绑定给模型

关键点(决定工具好不好用):

  1. docstring 是模型选工具的唯一依据:写清楚「什么时候用」而不仅是「是什么」,最好带触发场景;相邻工具的差异也要点明;
  2. 参数用类型注解 + Field description:模型按 schema 生成参数,描述含糊参数就幻觉;
  3. 返回值对模型友好:返回紧凑结构化文本/JSON,报错时返回可读的错误信息(模型能据此重试或向用户解释),别抛异常中断循环;
  4. 副作用工具要幂等:模型可能重复调用。

Java 侧(LangChain4j/Spring AI)等价实现:

java
@Tool("根据订单号查询订单状态")
public String queryOrder(@P("订单号") String orderId) { ... }

进阶:需要复杂参数校验时用 args_schema 传 Pydantic 模型;异步工具加 async 支持;工具结果太大时先摘要再返回。