Skip to main content
Tools are functions that agents and language models can invoke to interact with external systems, perform computations, or retrieve information. LangChain provides a flexible system for defining, validating, and executing tools.

Tool Basics

Tools in LangChain are defined using the @tool decorator or by extending BaseTool:

Simple Tool with Decorator

from langchain_core.tools import tool

@tool
def calculator(expression: str) -> str:
    """Evaluate a mathematical expression.
    
    Args:
        expression: A valid Python math expression (e.g., "2 + 2", "10 * 5")
    """
    try:
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}"

# Use the tool
result = calculator.invoke({"expression": "25 * 4"})
print(result)  # "100"
The @tool decorator automatically:
  • Extracts the function name as tool name
  • Uses the docstring as tool description
  • Infers parameter schema from type hints
  • Creates a BaseTool subclass
See: /libs/core/langchain_core/tools/base.py

Tool with Pydantic Schema

For complex inputs, use Pydantic models:
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    """Input schema for search tool."""
    query: str = Field(description="Search query string")
    max_results: int = Field(default=5, description="Maximum number of results", ge=1, le=20)
    include_snippets: bool = Field(default=True, description="Include text snippets")

@tool(args_schema=SearchInput)
def search(query: str, max_results: int = 5, include_snippets: bool = True) -> str:
    """Search for information on the web.
    
    Returns formatted search results with titles and URLs.
    """
    # Search implementation
    return f"Found {max_results} results for '{query}'"

# Tool automatically validates inputs
result = search.invoke({
    "query": "LangChain documentation",
    "max_results": 10
})
See schema creation in /libs/core/langchain_core/tools/base.py:289

BaseTool Class

For more control, extend BaseTool directly:
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Optional

class WeatherInput(BaseModel):
    location: str = Field(description="City name or ZIP code")
    units: str = Field(default="fahrenheit", description="Temperature units")

class WeatherTool(BaseTool):
    name: str = "get_weather"
    description: str = "Get current weather for a location"
    args_schema: type[BaseModel] = WeatherInput
    
    def _run(self, location: str, units: str = "fahrenheit") -> str:
        """Synchronous implementation."""
        # Call weather API
        return f"Weather in {location}: 72°{units[0].upper()}, Sunny"
    
    async def _arun(self, location: str, units: str = "fahrenheit") -> str:
        """Async implementation."""
        # Async API call
        return f"Weather in {location}: 72°{units[0].upper()}, Sunny"

tool = WeatherTool()
result = tool.invoke({"location": "San Francisco"})
print(result)  # "Weather in San Francisco: 72°F, Sunny"

Tool Properties

Every tool has these key properties:

name

Unique identifier for the tool:
@tool
def my_tool(x: int) -> int:
    """Double a number."""
    return x * 2

print(my_tool.name)  # "my_tool"
Override the name:
@tool("custom_name")
def my_tool(x: int) -> int:
    """Double a number."""
    return x * 2

print(my_tool.name)  # "custom_name"

description

Helps the LLM understand when to use the tool:
@tool
def search_docs(query: str) -> str:
    """Search LangChain documentation for relevant information.
    
    Use this when the user asks about:
    - LangChain features and capabilities
    - Code examples and tutorials
    - API reference and function signatures
    
    Do not use for:
    - General web search
    - Real-time information
    - Non-LangChain topics
    """
    return search_api(query)

print(search_docs.description)
Clear, detailed descriptions significantly improve agent performance. Include:
  • What the tool does
  • When to use it
  • When NOT to use it
  • Parameter requirements

args_schema

Pydantic model defining input validation:
from langchain_core.tools import tool
from pydantic import BaseModel, Field, field_validator

class CalculatorInput(BaseModel):
    expression: str = Field(
        description="Mathematical expression to evaluate",
        min_length=1,
        max_length=200
    )
    
    @field_validator("expression")
    @classmethod
    def validate_expression(cls, v: str) -> str:
        # Only allow safe math operations
        allowed = set("0123456789+-*/(). ")
        if not all(c in allowed for c in v):
            raise ValueError("Expression contains invalid characters")
        return v

@tool(args_schema=CalculatorInput)
def calculator(expression: str) -> str:
    """Evaluate math expressions."""
    return str(eval(expression))

# Schema used for validation
print(calculator.args_schema.model_json_schema())
See: /libs/core/langchain_core/tools/base.py:289

Tool Invocation

Tools are Runnables and support standard invocation methods:

invoke / ainvoke

@tool
def greet(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"

# Synchronous
result = greet.invoke({"name": "Alice"})
print(result)  # "Hello, Alice!"

# Asynchronous
result = await greet.ainvoke({"name": "Bob"})

batch / abatch

inputs = [
    {"expression": "2 + 2"},
    {"expression": "5 * 5"},
    {"expression": "10 - 3"}
]

results = calculator.batch(inputs)
print(results)  # ["4", "25", "7"]

With Configuration

from langchain_core.runnables import RunnableConfig

result = tool.invoke(
    {"query": "search term"},
    config=RunnableConfig(
        tags=["production"],
        metadata={"user_id": "123"}
    )
)

Tool Integration with Models

Binding Tools to Models

Modern chat models support native tool calling:
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

@tool
def get_weather(location: str) -> str:
    """Get current weather."""
    return f"Weather in {location}: Sunny, 72°F"

@tool
def get_time(timezone: str = "UTC") -> str:
    """Get current time."""
    from datetime import datetime
    return datetime.now().strftime("%H:%M:%S")

# Bind tools to model
model = ChatOpenAI(model="gpt-4")
model_with_tools = model.bind_tools([get_weather, get_time])

# Model can now request tool calls
response = model_with_tools.invoke("What's the weather in NYC?")
print(response.tool_calls)
# [{'name': 'get_weather', 'args': {'location': 'NYC'}, 'id': 'call_...'}]

Executing Tool Calls

Handle tool call responses:
from langchain_core.messages import HumanMessage, ToolMessage

messages = [HumanMessage(content="What's the weather in SF?")]

# Get AI response with tool calls
ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)

# Execute each tool call
for tool_call in ai_message.tool_calls:
    tool_name = tool_call["name"]
    tool_args = tool_call["args"]
    
    # Find and invoke the tool
    if tool_name == "get_weather":
        result = get_weather.invoke(tool_args)
    elif tool_name == "get_time":
        result = get_time.invoke(tool_args)
    
    # Add tool result to messages
    messages.append(ToolMessage(
        content=result,
        tool_call_id=tool_call["id"]
    ))

# Get final response
final_response = model_with_tools.invoke(messages)
print(final_response.content)  # "The weather in SF is sunny and 72°F"

Tool Choice Control

Force or prevent tool usage:
# Force tool usage (must call a tool)
model_with_tools = model.bind_tools(
    [get_weather, get_time],
    tool_choice="required"
)

# Force specific tool
model_with_tools = model.bind_tools(
    [get_weather, get_time],
    tool_choice={"type": "tool", "name": "get_weather"}
)

# Disable tools (regular response)
model_with_tools = model.bind_tools(
    [get_weather, get_time],
    tool_choice="none"
)

Structured Tools

For tools that return structured data:
from langchain_core.tools import StructuredTool
from pydantic import BaseModel

class UserInfo(BaseModel):
    name: str
    email: str
    age: int

class GetUserInput(BaseModel):
    user_id: str

def get_user(user_id: str) -> UserInfo:
    # Database lookup
    return UserInfo(
        name="Alice",
        email="[email protected]",
        age=30
    )

tool = StructuredTool.from_function(
    func=get_user,
    name="get_user",
    description="Retrieve user information by ID",
    args_schema=GetUserInput,
)

result = tool.invoke({"user_id": "123"})
print(result)  # UserInfo object
See: /libs/core/langchain_core/tools/structured.py

Tool Error Handling

Handle tool failures gracefully:
from langchain_core.tools import tool

@tool
def divide(a: float, b: float) -> str:
    """Divide two numbers.
    
    Args:
        a: Numerator
        b: Denominator (must be non-zero)
    """
    try:
        if b == 0:
            return "Error: Cannot divide by zero"
        result = a / b
        return f"Result: {result}"
    except Exception as e:
        return f"Error: {str(e)}"

# Tool returns error message instead of raising
result = divide.invoke({"a": 10, "b": 0})
print(result)  # "Error: Cannot divide by zero"
For tools that should raise exceptions:
@tool(handle_tool_error=True)
def risky_operation(value: int) -> str:
    """Operation that might fail."""
    if value < 0:
        raise ValueError("Value must be positive")
    return f"Result: {value * 2}"

# Exceptions are caught and returned as error messages
result = risky_operation.invoke({"value": -5})
print(result)  # "Error: Value must be positive"

Retriever Tools

Convert retrievers to tools:
from langchain_core.tools import create_retriever_tool
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Create vector store retriever
vectorstore = FAISS.from_texts(
    ["LangChain is a framework", "Python is a language"],
    embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()

# Convert to tool
retriever_tool = create_retriever_tool(
    retriever,
    name="search_docs",
    description="Search documentation for relevant information"
)

# Use like any other tool
result = retriever_tool.invoke({"query": "What is LangChain?"})
See: /libs/core/langchain_core/tools/retriever.py

Tool Rendering

Convert tools to various formats:

OpenAI Format

from langchain_core.utils.function_calling import convert_to_openai_tool

@tool
def calculator(expression: str) -> str:
    """Evaluate math expressions."""
    return str(eval(expression))

openai_format = convert_to_openai_tool(calculator)
print(openai_format)
# {
#   "type": "function",
#   "function": {
#     "name": "calculator",
#     "description": "Evaluate math expressions.",
#     "parameters": {...}
#   }
# }

JSON Schema

schema = calculator.args_schema.model_json_schema()
print(schema)
# {
#   "type": "object",
#   "properties": {
#     "expression": {"type": "string", ...}
#   },
#   "required": ["expression"]
# }

Advanced Tool Patterns

Stateful Tools

Tools that maintain state:
from langchain_core.tools import BaseTool
from pydantic import Field

class Counter(BaseTool):
    name: str = "counter"
    description: str = "Increment and return a counter"
    count: int = Field(default=0)
    
    def _run(self) -> str:
        self.count += 1
        return f"Count: {self.count}"

counter = Counter()
print(counter.invoke({}))  # "Count: 1"
print(counter.invoke({}))  # "Count: 2"

Tool with Callbacks

from langchain_core.callbacks import CallbackManagerForToolRun

class VerboseTool(BaseTool):
    name: str = "verbose_tool"
    description: str = "A tool that logs its execution"
    
    def _run(
        self,
        query: str,
        run_manager: CallbackManagerForToolRun | None = None
    ) -> str:
        if run_manager:
            run_manager.on_text(f"Processing: {query}\n")
        
        result = f"Result for {query}"
        
        if run_manager:
            run_manager.on_text(f"Completed: {result}\n")
        
        return result

Tool Composition

Compose tools with other Runnables:
from langchain_core.runnables import RunnableLambda

@tool
def fetch_data(url: str) -> str:
    """Fetch data from URL."""
    return f"Data from {url}"

def parse_data(data: str) -> dict:
    return {"parsed": data.upper()}

# Compose tool with processing
chain = fetch_data | RunnableLambda(parse_data)
result = chain.invoke({"url": "https://example.com"})

Best Practices

The description is critical for LLM tool selection:
@tool
def search(query: str) -> str:
    """Search for current information on the web.
    
    Use this tool when you need:
    - Recent news or events (last 24 hours)
    - Real-time data (stock prices, weather)
    - Information not in your training data
    - Verification of facts
    
    Do NOT use for:
    - General knowledge questions
    - Mathematical calculations
    - Code generation
    
    Args:
        query: Specific search terms. Be precise and use keywords.
    
    Returns:
        Top search results with titles, snippets, and URLs.
    """
    return search_api(query)
Use Pydantic models for type safety:
from pydantic import BaseModel, Field, field_validator

class EmailInput(BaseModel):
    to: str = Field(description="Recipient email")
    subject: str = Field(min_length=1, max_length=200)
    body: str = Field(min_length=1)
    
    @field_validator("to")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email address")
        return v

@tool(args_schema=EmailInput)
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    # Email sending logic
    return "Email sent"
Tool outputs should be clear and actionable:
@tool
def database_query(sql: str) -> str:
    """Execute SQL query."""
    try:
        results = db.execute(sql)
        if not results:
            return "Query executed successfully. No rows returned."
        return f"Found {len(results)} rows:\n{format_results(results)}"
    except Exception as e:
        return f"Query failed: {str(e)}\nPlease check the SQL syntax."
Single-purpose tools are easier for LLMs to use correctly:
# Good: Focused tools
@tool
def get_user_email(user_id: str) -> str:
    """Get user email address."""
    return db.get_user(user_id).email

@tool
def get_user_age(user_id: str) -> int:
    """Get user age."""
    return db.get_user(user_id).age

# Avoid: Kitchen sink tools
@tool
def get_user_data(user_id: str, field: str) -> Any:
    """Get any user field."""
    return getattr(db.get_user(user_id), field)

Next Steps

Agents

Build agents that use tools dynamically

Runnables

Compose tools with other components

Messages

Handle tool messages in conversations

LangGraph

Build advanced tool-using systems

Build docs developers (and LLMs) love