LangChain Tools Letting Your AI Use External Services
Language models generate text. They cannot search the web, check the current time, run code, query a database, or send an email on their own. Tools give your AI application those capabilities. A Tool is a Python function wrapped in a way the AI can discover and call. When the AI decides it needs to perform an action — search for something, calculate a value, look up a record — it calls the appropriate Tool and uses the result to formulate its answer. This topic covers how to create tools, how to connect them to models, and how to build applications where AI and real-world actions work together.
The Swiss Army Knife Analogy
A Swiss army knife has a blade, scissors, a screwdriver, and a file. Each tool has a specific purpose. You pick the right one for each job. LangChain Tools work the same way — each tool does one specific thing, and the AI picks the right tool for each step of a task. The AI reads the tool's description to understand its purpose, exactly like reading the label on a Swiss army knife blade.
Tools available to the AI:
┌────────────────────────────────────────────────────┐
│ search_web(query) → Search the internet │
│ get_weather(city) → Get current temperature │
│ calculate(expression) → Compute math results │
│ lookup_user(user_id) → Get user from database │
│ send_email(to, body) → Send an email │
└────────────────────────────────────────────────────┘
AI decision process:
User: "What is 15% of $340 and what is the weather in Mumbai?"
AI thinks: "I need calculate() for the math and get_weather() for Mumbai."
AI calls: calculate("0.15 * 340") → 51
AI calls: get_weather("Mumbai") → "29°C, partly cloudy"
AI answers: "15% of $340 is $51. The weather in Mumbai is 29°C and partly cloudy."
Creating a Tool with the @tool Decorator
The simplest way to create a tool is to write a regular Python function and decorate it with @tool. LangChain reads the function's name, docstring, and type annotations to understand what the tool does and what inputs it takes.
from langchain_core.tools import tool
@tool
def get_word_count(text: str) -> int:
"""Count the number of words in a text string.
Use this when the user asks how many words are in a piece of text."""
return len(text.split())
# The tool has a name, description, and schema
print(get_word_count.name) # "get_word_count"
print(get_word_count.description) # "Count the number of words..."
print(get_word_count.args) # {'text': {'type': 'string'}}
# You can call it directly like a regular function
result = get_word_count.invoke({"text": "Hello world this is a test"})
print(result) # 6
The docstring is critical. The AI reads it to decide whether to use this tool. Write clear, specific descriptions that describe exactly when and why to use the tool.
Tools with Multiple Parameters
@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
"""Convert an amount from one currency to another.
Use this when the user asks to convert money between currencies.
Example: convert 100 USD to EUR."""
# In a real app, call a currency API here
# This is a simplified example using fixed rates
rates = {"USD": 1.0, "EUR": 0.92, "GBP": 0.79, "INR": 83.5}
if from_currency not in rates or to_currency not in rates:
return f"Unsupported currency. Supported: {list(rates.keys())}"
usd_amount = amount / rates[from_currency]
converted = usd_amount * rates[to_currency]
return f"{amount} {from_currency} = {converted:.2f} {to_currency}"
result = convert_currency.invoke({
"amount": 100,
"from_currency": "USD",
"to_currency": "EUR"
})
print(result) # "100 USD = 92.00 EUR"
Connecting Tools to a Model with Tool Calling
Modern AI models (GPT-4, GPT-3.5-turbo, Claude, Gemini) support a feature called tool calling (also called function calling). When you give a model a list of tools, it can decide to call one or more tools, pass the right arguments, and use the results to generate its final answer. LangChain makes this seamless.
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
load_dotenv()
@tool
def get_current_temperature(city: str) -> str:
"""Get the current temperature for a city.
Use this when the user asks about weather or temperature."""
# Real apps call a weather API here
mock_temps = {"london": "14°C", "mumbai": "31°C", "new york": "22°C"}
return mock_temps.get(city.lower(), "Temperature data not available")
@tool
def calculate_tip(bill_amount: float, tip_percentage: float) -> str:
"""Calculate the tip amount and total for a restaurant bill.
Use this when users ask about tipping or splitting restaurant bills."""
tip = bill_amount * (tip_percentage / 100)
total = bill_amount + tip
return f"Tip: ${tip:.2f} | Total: ${total:.2f}"
# Bind tools to the model
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
model_with_tools = model.bind_tools([get_current_temperature, calculate_tip])
# The model can now decide to use tools
response = model_with_tools.invoke([
HumanMessage("What is the temperature in London?")
])
print(response.content) # May be empty if the model chose to call a tool
print(response.tool_calls) # Contains the tool call details
When the model decides to use a tool, response.tool_calls contains the tool name and arguments the model wants to use. response.content may be empty at this stage because the model is waiting for the tool result before generating its final answer.
Full Tool Execution Loop
After the model returns a tool call, you need to execute the tool, give the result back to the model, and let it generate the final answer. LangChain's ToolMessage carries the tool result back to the model.
from langchain_core.messages import AIMessage, ToolMessage, HumanMessage
tools = [get_current_temperature, calculate_tip]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)
def run_with_tools(user_message: str) -> str:
messages = [HumanMessage(content=user_message)]
# First call: model may request tool use
response = model_with_tools.invoke(messages)
messages.append(response)
# Execute any requested tool calls
while response.tool_calls:
for tool_call in response.tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
print(f" [Calling tool: {tool_name}({tool_args})]")
# Execute the tool
tool_result = tools_by_name[tool_name].invoke(tool_args)
# Add the result to message history
messages.append(ToolMessage(
content=str(tool_result),
tool_call_id=tool_call["id"]
))
# Ask the model to generate a response using tool results
response = model_with_tools.invoke(messages)
messages.append(response)
return response.content
print(run_with_tools("What is the temperature in Mumbai and what is a 15% tip on a $48 dinner?"))
Diagram: Tool Execution Flow
User: "Temperature in Mumbai and 15% tip on $48?"
│
▼
Model thinks: "I need get_current_temperature and calculate_tip"
│
▼
Model returns tool_calls:
[{name: "get_current_temperature", args: {city: "Mumbai"}},
{name: "calculate_tip", args: {bill_amount: 48, tip_percentage: 15}}]
│
▼
Execute tools locally:
get_current_temperature("Mumbai") → "31°C"
calculate_tip(48, 15) → "Tip: $7.20 | Total: $55.20"
│
▼
Send results back to model as ToolMessages
│
▼
Model generates final answer:
"The current temperature in Mumbai is 31°C.
A 15% tip on a $48 dinner is $7.20, making the total $55.20."
Built-In LangChain Tools
LangChain ships with ready-made tools for common tasks. Install the community package to access them.
pip install langchain-community duckduckgo-search wikipedia
Web Search Tool
from langchain_community.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
result = search.invoke("latest Python version 2025")
print(result) # Returns a text summary of search results
Wikipedia Tool
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
result = wikipedia.invoke("Large language model")
print(result[:500]) # Wikipedia article summary
Python REPL Tool
from langchain_experimental.tools import PythonREPLTool
python_repl = PythonREPLTool()
result = python_repl.invoke("print(sum(range(1, 101)))")
print(result) # 5050
The Python REPL tool lets the AI run actual Python code and return the result. Use this with caution — only allow it in controlled environments, as it executes arbitrary code on your machine.
Building Custom Tools for Your Business Logic
import sqlite3
from langchain_core.tools import tool
@tool
def search_products(query: str, max_results: int = 5) -> str:
"""Search the product catalog for items matching the query.
Returns product names, prices, and availability.
Use this when the user asks about products, prices, or inventory."""
# Connect to your actual database in a real app
conn = sqlite3.connect("products.db")
cursor = conn.cursor()
cursor.execute(
"SELECT name, price, stock FROM products WHERE name LIKE ? LIMIT ?",
(f"%{query}%", max_results)
)
results = cursor.fetchall()
conn.close()
if not results:
return f"No products found matching '{query}'"
output = f"Products matching '{query}':\n"
for name, price, stock in results:
status = "In Stock" if stock > 0 else "Out of Stock"
output += f"- {name}: ${price:.2f} ({status})\n"
return output
@tool
def get_order_status(order_id: str) -> str:
"""Look up the current status of a customer order by order ID.
Use this when a customer asks about their order status, delivery, or shipment."""
# Replace with real database lookup
mock_orders = {
"ORD-1234": "Shipped — estimated delivery June 10",
"ORD-5678": "Processing — will ship within 2 business days",
"ORD-9999": "Delivered on June 3"
}
return mock_orders.get(order_id.upper(), f"Order {order_id} not found.")
Tool Input Validation with Pydantic
For tools that take complex inputs, define the input schema explicitly using Pydantic. This prevents malformed inputs and gives the model precise instructions about what each parameter means.
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool
class EmailInput(BaseModel):
to_address: str = Field(description="Recipient email address")
subject: str = Field(description="Email subject line")
body: str = Field(description="Email body content, plain text")
priority: str = Field(default="normal", description="Priority: low, normal, or high")
def send_email_function(to_address: str, subject: str, body: str, priority: str = "normal") -> str:
"""Internal function that actually sends the email."""
# Real implementation calls an email API
print(f"[SENDING EMAIL to {to_address}: {subject}]")
return f"Email sent to {to_address} with subject '{subject}'"
send_email = StructuredTool.from_function(
func=send_email_function,
name="send_email",
description="Send an email to a specified recipient. Use this when the user asks to send, compose, or write an email.",
args_schema=EmailInput
)
Error Handling in Tools
Tools can fail — APIs go down, database connections drop, inputs are invalid. Handle errors gracefully so one failing tool does not crash the entire AI interaction.
@tool
def get_stock_price(ticker: str) -> str:
"""Get the current stock price for a ticker symbol.
Use this when users ask about stock prices or market values."""
try:
# Simulate API call
if not ticker.isalpha():
return f"Invalid ticker symbol: {ticker}. Use letters only like AAPL or GOOGL."
# mock_api_call(ticker) would go here
mock_prices = {"AAPL": 185.50, "GOOGL": 175.20, "MSFT": 420.10}
price = mock_prices.get(ticker.upper())
if price is None:
return f"No price data available for {ticker}."
return f"{ticker.upper()}: ${price:.2f}"
except Exception as e:
return f"Could not retrieve price for {ticker}: {str(e)}"
Always return a string from tools, even for errors. Raising exceptions from inside tools crashes the agent loop. Returning a descriptive error string lets the model inform the user gracefully.
Tool Descriptions: The Key to Reliable Behavior
The AI uses tool descriptions to decide which tool to call. Vague descriptions lead to wrong tool selection. Follow these guidelines:
Bad description: "Gets information about orders." Good description: "Look up the current status and estimated delivery date of a customer order. Requires a valid order ID in the format ORD-XXXX. Use this when a customer asks about their order status, shipment tracking, or delivery date."
Include: what the tool does, what format inputs must be in, and when to use it (the trigger conditions). This precision dramatically improves how reliably the model chooses the right tool.
Summary
Tools are Python functions wrapped in a way that AI models can discover and call. The @tool decorator creates a tool from any function using its name and docstring. Modern models support tool calling, where they decide which tools to run, pass arguments, receive results, and use them to generate final answers. LangChain includes built-in tools for web search, Wikipedia, and code execution. Custom tools encapsulate your business logic — database lookups, API calls, calculations. Always handle errors inside tool functions and return descriptive strings.
