By Sagar Shankaran, Founder of CallSphere
Langchain_core.tools tool decorator: master LangChain tool creation patterns including the @tool decorator, StructuredTool class, Pydantic input schemas, async tools, and error handling for production-grade agent tools.
Key takeaways
An LLM can reason and generate text, but it cannot look up a database, call an API, or read a file on its own. Tools bridge this gap. When you give an agent tools, the LLM can decide to invoke a function, receive its result, and incorporate that information into its reasoning. The quality of your tool definitions — names, descriptions, and input schemas — directly determines how reliably your agent uses them.
The simplest way to create a LangChain tool is the @tool decorator. It extracts the function name, docstring, and type annotations automatically.
flowchart TD
USER(["User message"])
LLM["LLM call<br/>with tools schema"]
DECIDE{"Model wants<br/>to call a tool?"}
EXEC["Execute tool<br/>sandboxed runtime"]
RESULT["Append tool_result<br/>to messages"]
GUARD{"Output passes<br/>guardrails?"}
DONE(["Final reply"])
BLOCK(["Refuse and log"])
USER --> LLM --> DECIDE
DECIDE -->|Yes| EXEC --> RESULT --> LLM
DECIDE -->|No| GUARD
GUARD -->|Yes| DONE
GUARD -->|No| BLOCK
style LLM fill:#4f46e5,stroke:#4338ca,color:#fff
style EXEC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style DONE fill:#059669,stroke:#047857,color:#fff
style BLOCK fill:#dc2626,stroke:#b91c1c,color:#fff
from langchain_core.tools import tool
@tool
def search_database(query: str, limit: int = 10) -> str:
"""Search the product database for items matching the query.
Args:
query: The search terms to look for.
limit: Maximum number of results to return.
"""
# Implementation here
results = db.search(query, limit=limit)
return f"Found {len(results)} products: {results}"
The docstring is critical — the LLM reads it to decide when and how to use the tool. Include what the tool does and what each parameter means. Type annotations define the input schema that the LLM must follow.
You can customize the name and control whether the result is returned directly to the user:
@tool("product_search", return_direct=True)
def search_database(query: str) -> str:
"""Search for products by name or category."""
return do_search(query)
Setting return_direct=True means the tool's output is returned as the final answer without further LLM processing. This is useful for tools that produce user-facing output.
For more complex inputs, define a Pydantic model as the input schema. This gives you validation, default values, and detailed field descriptions.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
from langchain_core.tools import tool
from pydantic import BaseModel, Field
class FlightSearchInput(BaseModel):
origin: str = Field(description="Airport code of departure city (e.g., SFO)")
destination: str = Field(description="Airport code of arrival city (e.g., JFK)")
date: str = Field(description="Travel date in YYYY-MM-DD format")
max_stops: int = Field(default=1, description="Maximum number of stops allowed")
@tool("search_flights", args_schema=FlightSearchInput)
def search_flights(
origin: str, destination: str, date: str, max_stops: int = 1
) -> str:
"""Search for available flights between two airports on a given date."""
flights = flight_api.search(origin, destination, date, max_stops)
return format_flight_results(flights)
The Field(description=...) values are included in the tool schema that the LLM sees, so write them to be informative.
When you need to build tools dynamically or from configuration, use StructuredTool.from_function.
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
class CalculatorInput(BaseModel):
expression: str = Field(description="A mathematical expression to evaluate")
def calculate(expression: str) -> str:
try:
return str(eval(expression))
except Exception as e:
return f"Error: {e}"
calculator_tool = StructuredTool.from_function(
func=calculate,
name="calculator",
description="Evaluate mathematical expressions using Python syntax.",
args_schema=CalculatorInput,
)
This approach is equivalent to the @tool decorator but gives you programmatic control over every attribute.
For tools that call external APIs, use async implementations to avoid blocking.
from langchain_core.tools import tool
import httpx
@tool
async def fetch_weather(city: str) -> str:
"""Get the current weather for a city."""
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.weather.example.com/current?city={city}"
)
data = response.json()
return f"{city}: {data['temp']}F, {data['condition']}"
When an agent calls this tool during an async execution (via ainvoke), the async version is used automatically. You can also provide both sync and async implementations:
calculator_tool = StructuredTool.from_function(
func=calculate_sync,
coroutine=calculate_async,
name="calculator",
description="Evaluate math expressions.",
)
Agents are more robust when tools handle errors gracefully instead of throwing exceptions.
@tool
def query_database(sql: str) -> str:
"""Execute a read-only SQL query against the analytics database."""
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries are allowed."
try:
results = db.execute(sql)
return format_results(results)
except Exception as e:
return f"Query failed: {str(e)}. Please check the syntax."
Returning error messages as strings lets the agent see what went wrong and adjust its approach. If you raise an exception instead, the agent loop may terminate or retry blindly.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
You can also set handle_tool_error=True on the tool or AgentExecutor to automatically catch exceptions and convert them to error messages for the agent.
For agents with many tools, organize them into a registry pattern.
from langchain_core.tools import tool
def build_tools(config: dict) -> list:
tools = []
if config.get("enable_search"):
@tool
def web_search(query: str) -> str:
"""Search the web for information."""
return search_api.query(query)
tools.append(web_search)
if config.get("enable_database"):
@tool
def sql_query(query: str) -> str:
"""Query the database."""
return db.execute(query)
tools.append(sql_query)
return tools
# Feature-flag tools per deployment
tools = build_tools({"enable_search": True, "enable_database": False})
There is no hard limit, but more tools mean a larger system prompt and more decisions for the LLM. In practice, agents work best with 5-15 well-defined tools. If you have more, consider using a tool selector or organizing tools into groups that are loaded based on the conversation context.
Detailed but concise. The LLM uses the description to decide when a tool is appropriate and how to call it. Include what the tool does, what inputs it expects, and any constraints. Avoid vague descriptions like "A useful tool" — be specific about the use case.
Call the tool directly using tool.invoke({"param": "value"}) or await tool.ainvoke({"param": "value"}). This runs the underlying function with schema validation. Write unit tests that call tools directly before integrating them into an agent.
#LangChain #ToolCreation #AIAgents #Pydantic #Python #AgenticAI #LearnAI #AIEngineering
This guide is written for engineers and operators evaluating langchain_core.tools tool decorator in real production systems. Langchain_core.tools tool decorator sits alongside create custom, create tools, description search, import tool tool def, langchain openai import in the daily work of teams shipping production AI. The notes below give a plain-language reference for terms used throughout the article.
For teams that want to ship langchain_core.tools tool decorator in voice and chat agents this quarter, CallSphere runs 37 agents and 90+ function tools across 6 verticals on a single dashboard. Start a 7-day free pilot, see live demo agents, or compare tiers on /pricing.

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Head-to-head: OpenAI Frontier and Anthropic's managed agent stack — strengths, fit, and what each means for enterprise AI voice and chat deployment.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI