LangChain @tool Decorator — Defining Tools

A Tool is the bridge for an Agent to interact with the external world.

Using the@tooldecorator, you can quickly convert any Python function into a tool callable by an Agent.


@tool Basic Syntax

@tool is a decorator provided by LangChain. Its usage is extremely simple: add the @tool decorator to a function, and the function becomes a tool.

Example

from langchain.tools import tool

# The simplest tool: an ordinary function + @tool decorator
@tool
def hello_tool(name: str) -> str:
    """Say hello to the specified person.

    Args:
name: the name of the person to greet
    """

    return f"Hello, {name}! Welcome to EXAMPLE Tutorial."

# A tool is also an ordinary Python function and can be called directly
result = hello_tool.invoke({"name": "Xiao Ming"})
print(result)

# The tool contains auto-generated description information
print(f"\nTool name: {hello_tool.name}")
print(f"Tool description: {hello_tool.description}")

Output:

你好,小明!欢迎来到Example。

Tool name: hello_tool
Tool description: 向指定的人打招呼。

    Args:
        name: 要打招呼的人的名字

The function's docstring automatically becomes the tool's description. The Agent relies on this description to determine "what this tool can do" and "when it should be called." The clearer the docstring, the more accurately the Agent uses the tool. In the description, explain parameter meanings, function behavior, and usage scenarios.


Using Different Parameter Types

@tool supports multiple parameter types, including int, float, bool, and enum values:

Example

from langchain.tools import tool
from typing import Literal


@tool
def search_courses(
    keyword: str,
    level: Literal["Beginner", "Intermediate", "Advanced"],
    max_results: int = 5,
    free_only: bool = True,
) -> str:
    """Search for courses on EXAMPLE Tutorial.

    Args:
keyword: search keyword
level: course difficulty level
max_results: maximum number of results to return
free_only: whether to show only free courses
    """

    # Simulate search results
    courses = {
        ("python", "Beginner"): "Python3 Basics Tutorial (Free)",
        ("python", "Intermediate"): "Python Data Analysis Practice (Free)",
        ("python", "Advanced"): "Python Machine Learning Advanced (Members only)",
        ("html", "Beginner"): "HTML Basics Tutorial (Free)",
    }
    key = (keyword.lower(), level)
    course = courses.get(key, f"No {keyword} courses found at the {level} level")
    if free_only and "Members only" in course:
        return f"Search result: {course} — this course requires membership and has been filtered out for you"
    return f"Search result: {course}"


# Test calls with different parameters
print(search_course.invoke({
    "keyword": "python",
    "level": "Beginner",
}))
# Output: Search result: Python3 Basics Tutorial (Free)

print(search_course.invoke({
    "keyword": "python",
    "level": "Advanced",
    "free_only": False,
}))
# Output: Search result: Python Machine Learning Advanced (Members only)

Registering Tools with the Agent

Pass the defined tool to the tools parameter of create_agent(), and the Agent will be able to use it:

Example

from dotenv import load_dotenv
load_dotenv()

from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage


@tool
def search_courses(keyword: str) -> str:
    """Search for courses on EXAMPLE Tutorial. Pass a keyword and get a list of related courses.

    Args:
keyword: search keyword, e.g., python, html, java
    """

    courses = {
        "python": "Python3 Basics Tutorial, Python Data Analysis, Python Web Crawler Introduction",
        "html": "HTML Basics Tutorial, HTML5 New Features, HTML Forms Practice",
        "java": "Java Basics Tutorial, Java Object-Oriented, Java Spring Framework",
    }
    return courses.get(keyword.lower(), f"No courses related to {keyword} found")


@tool
def get_course_detail(course_name: str) -> str:
    """Get detailed information about the specified course, including the number of chapters and study duration.

    Args:
course_name: course name, e.g., "Python3 Basics Tutorial"
    """

    details = {
        "python3 basics tutorial": "A total of 30 chapters, estimated study time 20 hours, suitable for complete beginners",
        "html basics tutorial": "A total of 25 chapters, estimated study time 15 hours, suitable for complete beginners",
    }
    return details.get(
        course_name.lower(),
        f"{course_name} details: suitable for beginners, rich in content, with practical cases"
    )


# Create the Agent
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    tools=[search_courses, get_course_detail],
    system_prompt="You are a learning consultant for EXAMPLE Tutorial, helping users find suitable courses.",
)


# Run the Agent
def ask(question: str):
    result = agent.invoke({"messages": [HumanMessage(content=question)]})
    print(f"User: {question}")
    print(f"Consultant: {result['messages'][-1].content}")
    print("-" * 60)


ask("I want to learn Python. What courses do you recommend?")
ask("How long does it take to complete Python3 Basics Tutorial?")

Output:

User: 我想学 Python,有什么课程推荐?
Advisor: 在Example 中有以下 Python 相关课程:
Python3 基础教程、Python 数据分析、Python 爬虫入门。
这些课程很适合 Python 初学者和进阶学习者,您可以根据兴趣选择!
------------------------------------------------------------
User: Python3 基础教程学完需要多久?
Advisor: Python3 基础教程共 30 章,预计学习时长 20 小时,非常适合零基础入门学习。
------------------------------------------------------------

Multiple Tools Working Together

An Agent can register multiple tools, and the model will automatically decide when to use which tool:

Example

from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage


@tool
def get_stock_price(symbol: str) -> str:
    """Query the current price of a stock.

    Args:
symbol: stock symbol, e.g., AAPL, GOOGL, TSLA
    """

    # Simulate stock data
    prices = {"AAPL": 185.50, "GOOGL": 142.30, "TSLA": 245.80}
    price = prices.get(symbol.upper())
    if price is None:
        return f"Stock symbol {symbol} not found"
    return f"{symbol.upper()} current price: ${price}"


@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
    """Currency conversion.

    Args:
amount: amount of money
from_currency: original currency code, e.g., USD, CNY
to_currency: target currency code, e.g., CNY, USD
    """

    # Simulate exchange rates
    rates = {
        ("USD", "CNY"): 7.25,
        ("CNY", "USD"): 0.138,
    }
    rate = rates.get((from_currency.upper(), to_currency.upper()))
    if rate is None:
        return f"Conversion from {from_currency} to {to_currency} is not supported"
    result = round(amount * rate, 2)
    return f"{amount} {from_currency} = {result} {to_currency}"


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    tools=[get_stock_price, convert_currency],
    system_prompt="You are a financial assistant that helps users check stock prices and convert currencies.",
)

# The Agent will automatically decide which tool to call
result = agent.invoke({
    "messages": [HumanMessage(content="What is Apple's stock price? How much is it in RMB?")]
})

for msg in result["messages"]:
    if msg.type == "tool":
        print(f"[Calling tool {msg.name}] {msg.content}")
    elif msg.type == "ai" and msg.content:
        print(f"\nFinal answer: {msg.content}")

Output:

[调用工具 get_stock_price] AAPL 当前价格:$185.5
[调用工具 convert_currency] 185.5 USD = 1344.88 CNY

Final answer: 苹果(AAPL)当前股价为 $185.50,按当前汇率换算约为 1344.88 人民币。

Optional Parameters and Default Values for Tools

Setting default values for tool parameters makes tools more flexible to use:

Example

from langchain.tools import tool


@tool
def recommend_tutorial(
    language: str,
    level: str = "Beginner",
    count: int = 3,
) -> str:
    """Recommend courses from EXAMPLE Tutorial based on programming language and difficulty.

    Args:
language: programming language, e.g., Python, Java, HTML
level: difficulty level, options "Beginner", "Intermediate", "Advanced". Default "Beginner"
count: number of courses to recommend, default 3
    """

    tutorials = {
        "python": ["Python3 Basics", "Python Object-Oriented", "Python Web Crawler", "Python Data Analysis"],
        "java": ["Java Basics", "Java Object-Oriented", "Java Collections Framework", "Java Multithreading"],
    }
    all_tutorials = tutorials.get(language.lower(), [f"{language} Basics Tutorial"])

    selected = all_tutorials[:count]
    return f"Recommended {language} {level} courses: {'、'.join(selected)}"


# You only need to pass required parameters when calling
print(recommend_tutorial.invoke({"language": "Python"}))
# Output: Recommended Python Beginner courses: Python3 Basics, Python Object-Oriented, Python Web Crawler

# You can also override the default parameters
print(recommend_tutorial.invoke({
    "language": "Java",
    "level": "Intermediate",
    "count": 2,
}))
# Output: Recommended Java Intermediate courses: Java Basics, Java Object-Oriented

Default values let the Agent avoid specifying all parameters every time when calling tools. But note: if a parameter has no default value and the Agent doesn't provide it, the call will fail. Don't set default values for key parameters.


The Tool's args_schema — Custom Parameter Validation

For complex parameter validation requirements, a Pydantic model can be used as args_schema:

Example

from pydantic import BaseModel, Field
from langchain.tools import tool


# Define parameter model (provides more fine-grained parameter control)
class CourseSearchInput(BaseModel):
    """Search course parameters"""
    keyword: str = Field(
        description="Search keyword, supports fuzzy matching",
        min_length=1,            # At least 1 character
        max_length=50,           # At most 50 characters
    )
    category: str = Field(
        default="all",
        description="Course category: all (all), frontend (frontend), backend (backend), data (data science)",
        pattern=r"^(all|frontend|backend|data)$",  # Restrict allowed values
    )
    page: int = Field(
        default=1,
        description="Page number, starting from 1",
        ge=1,                    # Greater than or equal to 1
        le=100,                  # Less than or equal to 100
    )


@tool(args_schema=CourseSearchInput)
def search_course(keyword: str, category: str = "all", page: int = 1) -> str:
    """Search courses on EXAMPLE tutorial site"""
    return f"Searching '{keyword}' (category: {category}, page {page}): found 15 results in total"


# Valid call
print(search_course.invoke({"keyword": "Python", "category": "backend", "page": 1}))

# Invalid call (category not in allowed values)
try:
    search_course.invoke({"keyword": "Python", "category": "invalid"})
except Exception as e:
    print(f"Parameter validation failed: {e}")

Running results:

搜索 'Python' (分类: backend, 第 1 页):共找到 15 条结果
参数校验失败: ...

Comparison of Tool Definition Methods

MethodCode amountApplicable scenariosExample
@tool decoratorLeastSimple to moderately complex toolsMost scenarios
@tool + args_schemaMediumTools requiring fine-grained parameter validationAPI wrapping, database operations
Pydantic class as toolMoreTools with complex business logicTools that contain internal state
Dictionary formatLeast (but not recommended)Describing remote/built-in toolsMCP tools, server-side tools
Other extensions