018-tool-decorator-basics

LangChain @tool 装饰器:让函数秒变 Agent 工具

💡 摘要 :本文深入解析 LangChain 的 @tool 装饰器,探讨如何通过最少的样板代码将普通 Python 函数转换为 Agent 可调用的智能工具,以及如何设计高质量的工具描述和错误处理机制。

引言

想象一下,你正在开发一个智能助手,用户说"帮我查一下北京的天气",助手需要能够真正去查询天气数据,而不是空洞地说"我无法访问外部数据"。这背后需要什么?

答案就是 工具(Tools) ------Agent 与外部世界交互的桥梁。而 LangChain 提供的 @tool 装饰器,就是构建这座桥梁的最简洁方式。

为什么需要 @tool 装饰器?

传统方式的困境

在 LangChain 早期版本中,创建一个工具需要继承 BaseTool 类:

python 复制代码
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Type


class CalculatorInput(BaseModel):
    """计算器输入参数"""
    expression: str = Field(description="要计算的算术表达式,例如 '1 + 2 * 3'")


class CalculatorTool(BaseTool):
    name: str = "calculator"
    description: str = "Perform arithmetic calculations"
    args_schema: Type[BaseModel] = CalculatorInput

    def _run(self, expression: str) -> str:
        return str(eval(expression))

这段代码有几个问题:

  • 需要定义多个属性(name、description、args_schema)
  • 必须实现 _run 方法
  • 代码分散在类的多个位置
  • 修改工具需要重构类结构

@tool 装饰器的优雅解决方案

使用 @tool 装饰器,同样的功能只需要:

python 复制代码
from langchain_core.tools import tool

@tool
def calculator(expression: str) -> str:
    """Perform arithmetic calculations."""
    return str(eval(expression))

一行装饰器,代码内聚,阅读体验如同普通 Python 函数,却自动获得了:

  • 自动生成工具名称和 JSON Schema
  • 内置的参数验证
  • 同步/异步执行支持
  • 与 Agent 的无缝集成

@tool 装饰器核心用法

1. 基础工具定义

python 复制代码
from langchain_core.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """Search the customer database for records matching the query.

    Args:
        query: Search terms to look for (e.g., customer name, product)
        limit: Maximum number of results to return (default: 10)

    Returns:
        Formatted string with search results
    """
    # 模拟数据库查询
    return f"Found {limit} results for '{query}' in database."

# 调用方式
result = search_database.invoke({"query": "Python programming", "limit": 5})
print(result)

2. 自定义工具名称和描述

默认情况下,工具名称取自函数名,描述来自函数的 docstring。但你也可以自定义:

python 复制代码
# 自定义工具名称
from langchain_core.tools import tool


@tool("web_search")
def search(query: str) -> str:
    """Search the web for information."""
    return f"Web search results for: {query}"


print(search.name)  # 输出:web_search


# 自定义工具描述(覆盖自动生成)
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
    """Evaluate mathematical expressions safely."""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except Exception as e:
        return f"Error calculating: {str(e)}"

print(calc.description)

3. 使用 Pydantic 定义复杂输入

当工具参数较复杂时,使用 Pydantic 模型可以提供更清晰的 Schema:

python 复制代码
from pydantic import BaseModel, Field
from typing import Literal
from langchain_core.tools import tool


class WeatherInput(BaseModel):
    """Input for weather queries."""
    location: str = Field(description="City name or coordinates (e.g., 'Beijing' or '39.9,116.4')")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit for the result"
    )
    include_forecast: bool = Field(
        default=False,
        description="Whether to include 3-day forecast"
    )


@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast for a location.

    Args:
        location: City name or GPS coordinates
        units: Temperature unit (celsius or fahrenheit)
        include_forecast: Whether to include 3-day forecast

    Returns:
        Weather report as formatted string
    """
    temp = 25 if units == "celsius" else 77
    forecast = "\nForecast: Sunny tomorrow, Rain on day 3" if include_forecast else ""
    return f"Weather in {location}: {temp}°{units[0].upper()}{forecast}"

print(get_weather.description)

4. 异步工具开发

生产环境中的工具经常需要调用外部 API,这时异步工具能显著提升性能:

python 复制代码
import asyncio
from langchain_core.tools import tool
import logging

logger = logging.getLogger(__name__)

@tool
async def fetch_news_articles(topic: str, max_results: int = 5) -> list:
    """Fetch latest news articles about a topic.

    Use this to stay updated on:
    - Industry trends and news
    - Company announcements
    - Market developments

    Args:
        topic: News topic or keyword
        max_results: Maximum number of articles to return (default: 5, max: 20)

    Returns:
        List of article dictionaries with title, source, published_date
    """
    try:
        logger.info(f"Fetching news for topic: {topic}")
        await asyncio.sleep(0.5)  # 模拟网络延迟

        max_results = min(max_results, 20)

        articles = [
            {
                "title": f"Article {i} about {topic}",
                "source": "News Source",
                "published_date": "2026-04-18"
            }
            for i in range(max_results)
        ]
        return articles

    except asyncio.TimeoutError:
        logger.error(f"Timeout fetching news for: {topic}")
        return [{"error": "Request timeout, please try again"}]
    except Exception as e:
        logger.error(f"Error fetching news: {str(e)}")
        return [{"error": f"Failed to fetch news: {str(e)}"}]

工具描述

工具描述是 Agent 理解何时使用工具的关键。好的描述遵循 T.R.I.G.G.E.R. 原则:

字母 含义 示例
Task 明确任务目标 "将摄氏温度转换为华氏温度"
Reason 何时触发(正例) "当用户询问天气温度转换、食谱温度调整时"
Input 参数规范 celsius: float,范围 -273.15~1e6
Guidance 输出格式 "成功返回字符串,失败返回错误信息"
Gate 不适用场景(反例) "不要用于开尔文转换"
Example 典型调用示例 "输入 32 → 返回 89.6°F"
Return 副作用说明 "此工具不修改任何状态"

差描述 vs 好描述

复制代码
❌ 差的描述:
@tool
def process_data(data: str) -> str:
    """Do something with data."""
    return f"Processed: {data}"

✅ 好的描述:
@tool
def analyze_sentiment(text: str) -> str:
    """Analyze the emotional tone of text (positive/negative/neutral).

    Use this when you need to understand:
    - Customer feedback sentiment
    - Social media post emotions
    - Product review attitudes

    Args:
        text: The text content to analyze (max 1000 characters)

    Returns:
        Sentiment classification with confidence score
    """
    return "Sentiment: Positive (92%)"

工具与 Agent 的集成

工具定义好后,集成到 Agent 中非常简单:

python 复制代码
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
import os

# 初始化模型
model = init_chat_model(
    model="qwen-flash-character",
    model_provider="openai",
    api_key=os.getenv("QWEN_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 创建 Agent,传入工具列表
agent = create_agent(
    model=model,
    tools=[calculator, search_database, get_weather],
    system_prompt="You are a helpful assistant with access to various tools."
)

# 运行 Agent
result = agent.invoke({
    "messages": [
        ("human", "Calculate 15 * 8")
    ]
})

print(result)
print(result["messages"][-1].content)

错误处理机制

工具执行可能因网络问题、输入无效等原因失败。良好的错误处理让 Agent 能够优雅应对:

基础错误处理

python 复制代码
@tool
def divide(a: float, b: float) -> str:
    """Divide two numbers."""
    try:
        return str(a / b)
    except ZeroDivisionError:
        return "错误:除数不能为零,请提供一个非零的除数。"
    except Exception as e:
        return f"计算失败:{str(e)}"

使用 handle_tool_error 统一处理

python 复制代码
def _handle_error(error: Exception) -> str:
    return f"工具执行出错:{error}。请检查输入或稍后重试。"

@tool(handle_tool_error=_handle_error)
def risky_api_call(query: str) -> str:
    import requests
    response = requests.get(f"https://api.example.com/search?q={query}")
    response.raise_for_status()
    return response.text

最佳实践总结

  1. 优先使用 @tool 装饰器
    • 代码简洁,内聚性好
    • 自动处理类型注解和 Schema 生成
    • 除非需要复杂状态管理,否则不必继承 BaseTool
  2. 工具描述要详尽
    • 包含使用场景和触发条件
    • 说明参数的类型、范围、单位
    • 描述成功和失败时的输出格式
    • 添加负面示例,避免误用
  3. 异步优先
    • 对于 I/O 密集型操作,使用 async def
    • 配合 asyncioaiohttp 实现高效并发
  4. 错误返回而非抛出
    • 工具内部捕获所有异常
    • 返回人类可读的错误字符串
    • 让 Agent 能够理解并尝试恢复
  5. 考虑抽象层级
    • 单一职责工具更灵活,可组合
    • 复杂流程可封装为粗粒度工具
    • 提供"快捷方式"而非强制使用

相关推荐
tang&2 小时前
【LangGraph】LangGraph 协调者-工作者模式完全解析:从零构建一个智能报告生成系统
langchain
渣渣苏2 小时前
LangChain 的 Deep Agents:生产级智能体引擎的架构
架构·langchain·deep agents·harness
索西引擎18 小时前
【LangChain 1.0】 接入 Ollama:在本地跑通 DeepSeek-R1 的完整指南
langchain
染指111021 小时前
12.LangChain框架4-输出解释器
人工智能·langchain·rag
索西引擎1 天前
【LangChain 1.0】环境搭建指南:从 conda 到 uv 的现代化 Python 工程实践
python·langchain·conda
云姜.1 天前
Langchain快速上手编程-Runnable 与 LCEL
java·开发语言·langchain
Coder小相1 天前
LangChain1.0第四篇 - 统一接口多厂商模型适配
人工智能·langchain·agent
PeterLi1 天前
LangChain v1.x 最新官方完整教程(六大核心组件全解析+生产级代码示例)
langchain·agent