TURION.AI
Tutorials

Creating Custom Tools for LangChain Agents: A Practical Guide

Andrius Putna 5 min read
#ai#agents#langchain#tools#tutorial#python#api-integration

Creating Custom Tools for LangChain Agents

Tools are what transform a language model from a sophisticated text generator into a capable AI agent. While LangChain provides many built-in tools, real-world applications often require custom functionality specific to your domain. In this tutorial, we’ll explore how to create custom tools that extend your agents’ capabilities.

What You’ll Learn

By the end of this tutorial, you’ll be able to:

Prerequisites

Before we start, make sure you have:

Estimated time: 25 minutes


Understanding Tools in LangChain

Tools are the bridge between your agent’s reasoning and real-world actions. When an agent decides it needs information or wants to perform an action, it calls a tool. Each tool has:

The quality of your tool’s description directly impacts how well the agent uses it. A vague description leads to incorrect tool selection; a precise description helps the agent make smart decisions.


Step 1: Creating a Simple Tool

The easiest way to create a tool is with the @tool decorator. Let’s start with a weather lookup tool:

from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city.

    Args:
        city: The name of the city to check weather for

    Returns:
        A string describing current weather conditions
    """
    # In production, call a real weather API
    weather_data = {
        "new york": "72°F, partly cloudy",
        "london": "58°F, rainy",
        "tokyo": "68°F, clear skies"
    }

    city_lower = city.lower()
    if city_lower in weather_data:
        return f"Weather in {city}: {weather_data[city_lower]}"
    return f"Weather data not available for {city}"

The docstring is critical. LangChain extracts the description from it, and the agent uses this to decide when to call the tool. Include the purpose, expected inputs, and what it returns.


Step 2: Building a Structured Tool

For tools with multiple parameters or complex inputs, use the StructuredTool class with Pydantic schemas:

from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    """Input schema for database search."""
    query: str = Field(description="The search query")
    limit: int = Field(default=5, description="Maximum results to return")
    category: str = Field(default="all", description="Filter by category")

def search_database(query: str, limit: int = 5, category: str = "all") -> str:
    """Search the product database."""
    # Simulated database search
    results = [
        {"name": "Widget Pro", "category": "electronics", "price": 99.99},
        {"name": "Gadget Max", "category": "electronics", "price": 149.99},
        {"name": "Office Chair", "category": "furniture", "price": 299.99}
    ]

    # Filter by category
    if category != "all":
        results = [r for r in results if r["category"] == category]

    # Apply limit
    results = results[:limit]

    if not results:
        return f"No results found for '{query}' in category '{category}'"

    output = f"Found {len(results)} results for '{query}':\n"
    for r in results:
        output += f"- {r['name']}: ${r['price']}\n"
    return output

search_tool = StructuredTool.from_function(
    func=search_database,
    name="search_products",
    description="Search the product database. Use when the user asks about products or inventory.",
    args_schema=SearchInput
)

Pydantic validates inputs before your function runs, preventing errors from malformed data. The Field descriptions help the agent understand what each parameter expects.


Step 3: Handling Errors Gracefully

Tools can fail—APIs time out, data is missing, or inputs are invalid. Design your tools to fail gracefully:

from langchain_core.tools import tool
import httpx

@tool
def fetch_stock_price(symbol: str) -> str:
    """Fetch the current stock price for a ticker symbol.

    Args:
        symbol: Stock ticker symbol (e.g., AAPL, GOOGL)

    Returns:
        Current stock price or error message
    """
    try:
        # Validate input
        if not symbol or len(symbol) > 5:
            return f"Invalid ticker symbol: {symbol}"

        symbol = symbol.upper()

        # Simulated API call (use real API in production)
        prices = {"AAPL": 178.50, "GOOGL": 141.25, "MSFT": 378.90}

        if symbol in prices:
            return f"{symbol}: ${prices[symbol]:.2f}"
        return f"Ticker {symbol} not found. Check the symbol and try again."

    except httpx.TimeoutException:
        return "Stock API is temporarily unavailable. Please try again later."
    except Exception as e:
        return f"Error fetching stock price: {str(e)}"

Return human-readable error messages rather than raising exceptions. This lets the agent explain the issue to the user or try an alternative approach.


Step 4: Creating an Agent with Custom Tools

Now let’s combine our tools into a working agent:

from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

# Initialize LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# Collect our tools
tools = [get_weather, search_tool, fetch_stock_price]

# Create the agent
agent = create_react_agent(llm, tools)

# Test the agent
response = agent.invoke({
    "messages": [{"role": "user", "content": "What's the weather in Tokyo?"}]
})
print(response["messages"][-1].content)

The agent automatically selects the right tool based on the user’s question. Ask about weather, and it uses get_weather. Ask about products, and it uses search_products.


Step 5: Advanced Pattern - Tool Dependencies

Some tools need to call other tools or share state. Here’s a pattern for tools that build on each other:

from langchain_core.tools import tool

# Shared cache for expensive operations
_cache = {}

@tool
def analyze_company(ticker: str) -> str:
    """Analyze a company using stock price and recent news.

    Args:
        ticker: Stock ticker symbol

    Returns:
        Company analysis summary
    """
    # Get stock price (reuse our earlier tool logic)
    prices = {"AAPL": 178.50, "GOOGL": 141.25, "MSFT": 378.90}
    price = prices.get(ticker.upper(), "unknown")

    # Simulate fetching news
    news_summary = f"Recent headlines for {ticker.upper()} are positive."

    # Combine into analysis
    return f"""
Company Analysis: {ticker.upper()}
Current Price: ${price if price != 'unknown' else 'N/A'}
News Sentiment: Positive
Recommendation: Based on current data, {ticker.upper()} shows stable performance.
"""

Best Practices for Custom Tools

Write descriptive docstrings: The agent relies on descriptions to choose tools. Be specific about when to use each tool and what inputs it expects.

Keep tools focused: Each tool should do one thing well. Instead of a general “data_tool”, create specific tools like “search_users” and “update_user”.

Validate inputs early: Check parameters at the start of your function. Return clear error messages for invalid inputs.

Return structured output: When possible, format output consistently so the agent can parse and reason about results.

Log tool calls: In production, log every tool invocation for debugging and monitoring agent behavior.


Common Pitfalls

Description Too Vague

Problem: Agent doesn’t select the right tool because the description is unclear.

Solution: Include specific use cases in the description. Instead of “Gets data”, write “Retrieves user profile information including name, email, and account status.”

Missing Error Handling

Problem: Tool raises exceptions that crash the agent.

Solution: Wrap tool logic in try-except blocks and return error messages as strings.

Slow Tools Block the Agent

Problem: Tools that take too long make the agent unresponsive.

Solution: Add timeouts to external API calls and consider async tools for I/O-heavy operations.


Next Steps

You’ve learned how to create custom tools that extend your LangChain agents. Here are some ways to go further:

Key Takeaways

Custom tools are what make AI agents truly useful for your specific domain. Whether you’re integrating with internal APIs, processing specialized data, or automating workflows, the patterns in this tutorial apply across use cases.


Ready to build more sophisticated agents? Check out our RAG agent tutorial, explore Multi-Agent Collaboration Patterns, or see our Complete Guide to AI Agent Frameworks for framework comparisons. For terminology reference, visit the AI Agents Glossary.

← Back to Blog