| title | Tools |
|---|
import ToolReturnValuesPy from '/snippets/code-samples/tool-return-values-py.mdx'; import ToolReturnValuesJs from '/snippets/code-samples/tool-return-values-js.mdx'; import ToolReturnObjectPy from '/snippets/code-samples/tool-return-object-py.mdx'; import ToolReturnObjectJs from '/snippets/code-samples/tool-return-object-js.mdx'; import ToolReturnCommandPy from '/snippets/code-samples/tool-return-command-py.mdx'; import ToolReturnCommandJs from '/snippets/code-samples/tool-return-command-js.mdx'; import ToolUpdateStatePy from '/snippets/code-samples/tool-update-state-py.mdx';
Tools extend what agents can doβletting them fetch real-time data, execute code, query external databases, and take actions in the world.
Under the hood, tools are callable functions with well-defined inputs and outputs that get passed to a chat model. The model decides when to invoke a tool based on the conversation context, and what input arguments to provide.
For details on how models handle tool calls, see [Tool calling](/oss/langchain/models#tool-calling).:::python
The simplest way to create a tool is with the @[@tool] decorator. By default, the function's docstring becomes the tool's description that helps the model understand when to use it:
from langchain.tools import tool
@tool
def search_database(query: str, limit: int = 10) -> str:
"""Search the customer database for records matching the query.
Args:
query: Search terms to look for
limit: Maximum number of results to return
"""
return f"Found {limit} results for '{query}'"Type hints are required as they define the tool's input schema. The docstring should be informative and concise to help the model understand the tool's purpose. :::
:::js
The simplest way to create a tool is by importing the tool function from the langchain package. You can use zod to define the tool's input schema:
import * as z from "zod"
import { tool } from "langchain"
const searchDatabase = tool(
({ query, limit }) => `Found ${limit} results for '${query}'`,
{
name: "search_database",
description: "Search the customer database for records matching the query.",
schema: z.object({
query: z.string().describe("Search terms to look for"),
limit: z.number().describe("Maximum number of results to return"),
}),
}
);:::
**Server-side tool use:** Some chat models feature built-in tools (web search, code interpreters) that are executed server-side. See [Server-side tool use](#server-side-tool-use) for details. Prefer `snake_case` for tool names (e.g., `web_search` instead of `Web Search`). Some model providers have issues with or reject names containing spaces or special characters with errors. Sticking to alphanumeric characters, underscores, and hyphens helps to improve compatibility across providers.:::python
By default, the tool name comes from the function name. Override it when you need something more descriptive:
@tool("web_search") # Custom name
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
print(search.name) # web_searchOverride the auto-generated tool description for clearer model guidance:
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
"""Evaluate mathematical expressions."""
return str(eval(expression))Define complex inputs with Pydantic models or JSON schemas:
```python Pydantic model from pydantic import BaseModel, Field from typing import Literalclass WeatherInput(BaseModel):
"""Input for weather queries."""
location: str = Field(description="City name or coordinates")
units: Literal["celsius", "fahrenheit"] = Field(
default="celsius",
description="Temperature unit preference"
)
include_forecast: bool = Field(
default=False,
description="Include 5-day forecast"
)
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""Get current weather and optional forecast."""
temp = 22 if units == "celsius" else 72
result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
if include_forecast:
result += "\nNext 5 days: Sunny"
return result
```
```python JSON Schema
weather_schema = {
"type": "object",
"properties": {
"location": {"type": "string"},
"units": {"type": "string"},
"include_forecast": {"type": "boolean"}
},
"required": ["location", "units", "include_forecast"]
}
@tool(args_schema=weather_schema)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""Get current weather and optional forecast."""
temp = 22 if units == "celsius" else 72
result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
if include_forecast:
result += "\nNext 5 days: Sunny"
return result
```
The following parameter names are reserved and cannot be used as tool arguments. Using these names will cause runtime errors.
| Parameter name | Purpose |
|---|---|
config |
Reserved for passing RunnableConfig to tools internally |
runtime |
Reserved for ToolRuntime parameter (accessing state, context, store) |
To access runtime information, use the @[ToolRuntime] parameter instead of naming your own arguments config or runtime.
:::
Tools are most powerful when they can access runtime information like conversation history, user data, and persistent memory. This section covers how to access and update this information from within your tools.
:::python
Tools can access runtime information through the @[ToolRuntime] parameter, which provides:
| Component | Description | Use case |
|---|---|---|
| State | Short-term memory - mutable data that exists for the current conversation (messages, counters, custom fields) | Access conversation history, track tool call counts |
| Context | Immutable configuration passed at invocation time (user IDs, session info) | Personalize responses based on user identity |
| Store | Long-term memory - persistent data that survives across conversations | Save user preferences, maintain knowledge base |
| Stream Writer | Emit real-time updates during tool execution | Show progress for long-running operations |
| Execution Info | Identity and retry information for the current execution (thread ID, run ID, attempt number) | Access thread/run IDs, adjust behavior based on retry state |
| Server Info | Server-specific metadata when running on LangGraph Server (assistant ID, graph ID, authenticated user) | Access assistant ID, graph ID, or authenticated user info |
| Config | @[RunnableConfig] for the execution |
Access callbacks, tags, and metadata |
| Tool Call ID | Unique identifier for the current tool invocation | Correlate tool calls for logs and model invocations |
graph LR
%% Runtime Context
subgraph "π§ Tool Runtime Context"
A[Tool Call] --> B[ToolRuntime]
B --> C[State Access]
B --> D[Context Access]
B --> E[Store Access]
B --> F[Stream Writer]
end
%% Available Resources
subgraph "π Available Resources"
C --> G[Messages]
C --> H[Custom State]
D --> I[User ID]
D --> J[Session Info]
E --> K[Long-term Memory]
E --> L[User Preferences]
end
%% Tool Capabilities
subgraph "β‘ Enhanced Tool Capabilities"
M[Context-Aware Tools]
N[Stateful Tools]
O[Memory-Enabled Tools]
P[Streaming Tools]
end
%% Connections
G --> M
H --> N
I --> M
J --> M
K --> O
L --> O
F --> P
classDef trigger fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D
classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E3A8A
classDef output fill:#F3E8FF,stroke:#9333EA,stroke-width:2px,color:#581C87
classDef neutral fill:#F3F4F6,stroke:#9CA3AF,stroke-width:2px,color:#374151
class A trigger
class B,C,D,E,F process
class G,H,I,J,K,L neutral
class M,N,O,P output
State represents short-term memory that exists for the duration of a conversation. It includes the message history and any custom fields you define in your graph state.
Add `runtime: ToolRuntime` to your tool signature to access state. This parameter is automatically injected and hidden from the LLM - it won't appear in the tool's schema.Tools can access the current conversation state using runtime.state:
from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage
@tool
def get_last_user_message(runtime: ToolRuntime) -> str:
"""Get the most recent message from the user."""
messages = runtime.state["messages"]
# Find the last human message
for message in reversed(messages):
if isinstance(message, HumanMessage):
return message.content
return "No user messages found"
# Access custom state fields
@tool
def get_user_preference(
pref_name: str,
runtime: ToolRuntime
) -> str:
"""Get a user preference value."""
preferences = runtime.state.get("user_preferences", {})
return preferences.get(pref_name, "Not set")Use @[Command] to update the agent's state. This is useful for tools that need to update custom state fields.
Include a ToolMessage in the update so the model can see the result of the tool call:
Context provides immutable configuration data that is passed at invocation time. Use it for user IDs, session details, or application-specific settings that shouldn't change during a conversation.
:::python
Access context through runtime.context:
from dataclasses import dataclass
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
USER_DATABASE = {
"user123": {
"name": "Alice Johnson",
"account_type": "Premium",
"balance": 5000,
"email": "alice@example.com"
},
"user456": {
"name": "Bob Smith",
"account_type": "Standard",
"balance": 1200,
"email": "bob@example.com"
}
}
@dataclass
class UserContext:
user_id: str
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
"""Get the current user's account information."""
user_id = runtime.context.user_id
if user_id in USER_DATABASE:
user = USER_DATABASE[user_id]
return f"Account holder: {user['name']}\nType: {user['account_type']}\nBalance: ${user['balance']}"
return "User not found"
model = ChatOpenAI(model="gpt-5.4")
agent = create_agent(
model,
tools=[get_account_info],
context_schema=UserContext,
system_prompt="You are a financial assistant."
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's my current balance?"}]},
context=UserContext(user_id="user123")
):::
:::js
Tools can access an agent's runtime context through the config parameter:
import * as z from "zod"
import { ChatOpenAI } from "@langchain/openai"
import { createAgent } from "langchain"
const getUserName = tool(
(_, config) => {
return config.context.user_name
},
{
name: "get_user_name",
description: "Get the user's name.",
schema: z.object({}),
}
);
const contextSchema = z.object({
user_name: z.string(),
});
const agent = createAgent({
model: new ChatOpenAI({ model: "gpt-5.4" }),
tools: [getUserName],
contextSchema,
});
const result = await agent.invoke(
{
messages: [{ role: "user", content: "What is my name?" }]
},
{
context: { user_name: "John Smith" }
}
);:::
The @[BaseStore] provides persistent storage that survives across conversations. Unlike state (short-term memory), data saved to the store remains available in future sessions.
:::python
Access the store through runtime.store. The store uses a namespace/key pattern to organize data:
from typing import Any
from langgraph.store.memory import InMemoryStore
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI
# Access memory
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
"""Look up user info."""
store = runtime.store
user_info = store.get(("users",), user_id)
return str(user_info.value) if user_info else "Unknown user"
# Update memory
@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
"""Save user info."""
store = runtime.store
store.put(("users",), user_id, user_info)
return "Successfully saved user info."
model = ChatOpenAI(model="gpt-5.4")
store = InMemoryStore()
agent = create_agent(
model,
tools=[get_user_info, save_user_info],
store=store
)
# First session: save user info
agent.invoke({
"messages": [{"role": "user", "content": "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev"}]
})
# Second session: get user info
agent.invoke({
"messages": [{"role": "user", "content": "Get user info for user with id 'abc123'"}]
})
# Here is the user info for user with ID "abc123":
# - Name: Foo
# - Age: 25
# - Email: foo@langchain.dev:::
:::js
Access the store through config.store. The store uses a namespace/key pattern to organize data:
import * as z from "zod";
import { createAgent, tool } from "langchain";
import { InMemoryStore } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
const store = new InMemoryStore();
// Access memory
const getUserInfo = tool(
async ({ user_id }) => {
const value = await store.get(["users"], user_id);
console.log("get_user_info", user_id, value);
return value;
},
{
name: "get_user_info",
description: "Look up user info.",
schema: z.object({
user_id: z.string(),
}),
}
);
// Update memory
const saveUserInfo = tool(
async ({ user_id, name, age, email }) => {
console.log("save_user_info", user_id, name, age, email);
await store.put(["users"], user_id, { name, age, email });
return "Successfully saved user info.";
},
{
name: "save_user_info",
description: "Save user info.",
schema: z.object({
user_id: z.string(),
name: z.string(),
age: z.number(),
email: z.string(),
}),
}
);
const agent = createAgent({
model: new ChatOpenAI({ model: "gpt-5.4" }),
tools: [getUserInfo, saveUserInfo],
store,
});
// First session: save user info
await agent.invoke({
messages: [
{
role: "user",
content: "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev",
},
],
});
// Second session: get user info
const result = await agent.invoke({
messages: [
{ role: "user", content: "Get user info for user with id 'abc123'" },
],
});
console.log(result);
// Here is the user info for user with ID "abc123":
// - Name: Foo
// - Age: 25
// - Email: foo@langchain.dev:::
Stream real-time updates from tools during execution. This is useful for providing progress feedback to users during long-running operations.
:::python
Use runtime.stream_writer to emit custom updates:
from langchain.tools import tool, ToolRuntime
@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
"""Get weather for a given city."""
writer = runtime.stream_writer
# Stream custom updates as the tool executes
writer(f"Looking up data for city: {city}")
writer(f"Acquired data for city: {city}")
return f"It's always sunny in {city}!":::js
Use config.writer to emit custom updates:
import * as z from "zod";
import { tool, ToolRuntime } from "langchain";
const getWeather = tool(
({ city }, config: ToolRuntime) => {
const writer = config.writer;
// Stream custom updates as the tool executes
if (writer) {
writer(`Looking up data for city: ${city}`);
writer(`Acquired data for city: ${city}`);
}
return `It's always sunny in ${city}!`;
},
{
name: "get_weather",
description: "Get weather for a given city.",
schema: z.object({
city: z.string(),
}),
}
);:::
Access thread ID, run ID, and retry state from within a tool via runtime.execution_info:
:::python
from langchain.tools import tool, ToolRuntime
@tool
def log_execution_context(runtime: ToolRuntime) -> str:
"""Log execution identity information."""
info = runtime.execution_info
print(f"Thread: {info.thread_id}, Run: {info.run_id}") # [!code highlight]
print(f"Attempt: {info.node_attempt}")
return "done":::
:::js
import { tool } from "langchain";
import * as z from "zod";
const logExecutionContext = tool(
async (_input, runtime) => {
const info = runtime.executionInfo;
console.log(`Thread: ${info.threadId}, Run: ${info.runId}`); // [!code highlight]
console.log(`Attempt: ${info.nodeAttempt}`);
return "done";
},
{
name: "log_execution_context",
description: "Log execution identity information.",
schema: z.object({}),
}
);:::
:::python
Requires deepagents>=0.5.0 (or langgraph>=1.1.5).
:::
:::js
Requires deepagents>=1.9.0 (or @langchain/langgraph>=1.2.8).
:::
When your tool runs on LangGraph Server, access the assistant ID, graph ID, and authenticated user via runtime.server_info:
:::python
from langchain.tools import tool, ToolRuntime
@tool
def get_assistant_scoped_data(runtime: ToolRuntime) -> str:
"""Fetch data scoped to the current assistant."""
server = runtime.server_info
if server is not None:
print(f"Assistant: {server.assistant_id}, Graph: {server.graph_id}") # [!code highlight]
if server.user is not None:
print(f"User: {server.user.identity}") # [!code highlight]
return "done"server_info is None when the tool is not running on LangGraph Server (e.g., during local development or testing).
:::
:::js
import { tool } from "langchain";
import * as z from "zod";
const getAssistantScopedData = tool(
async (_input, runtime) => {
const server = runtime.serverInfo;
if (server != null) {
console.log(`Assistant: ${server.assistantId}, Graph: ${server.graphId}`); // [!code highlight]
if (server.user != null) {
console.log(`User: ${server.user.identity}`); // [!code highlight]
}
}
return "done";
},
{
name: "get_assistant_scoped_data",
description: "Fetch data scoped to the current assistant.",
schema: z.object({}),
}
);serverInfo is null when the tool is not running on LangGraph Server.
:::
:::python
Requires deepagents>=0.5.0 (or langgraph>=1.1.5).
:::
:::js
Requires deepagents>=1.9.0 (or @langchain/langgraph>=1.2.8).
:::
@[ToolNode] is a prebuilt node that executes tools in LangGraph workflows. It handles parallel tool execution, error handling, and state injection automatically.
:::python
from langchain.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.graph import StateGraph, MessagesState, START, END
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
@tool
def calculator(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression))
# Create the ToolNode with your tools
tool_node = ToolNode([search, calculator])
# Use in a graph
builder = StateGraph(MessagesState)
builder.add_node("tools", tool_node)
# ... add other nodes and edges:::
:::js
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import * as z from "zod";
const search = tool(
({ query }) => `Results for: ${query}`,
{
name: "search",
description: "Search for information.",
schema: z.object({ query: z.string() }),
}
);
const calculator = tool(
({ expression }) => String(eval(expression)),
{
name: "calculator",
description: "Evaluate a math expression.",
schema: z.object({ expression: z.string() }),
}
);
// Create the ToolNode with your tools
const toolNode = new ToolNode([search, calculator]);:::
You can choose different return values for your tools:
- Return a
stringfor human-readable results. - Return an
objectfor structured results the model should parse. - Return a
Commandwith optional message when you need to write to state.
Return a string when the tool should provide plain text for the model to read and use in its next response.
:::python
:::
:::js
:::
Behavior:
- The return value is converted to a
ToolMessage. - The model sees that text and decides what to do next.
- No agent state fields are changed unless the model or another tool does so later.
Use this when the result is naturally human-readable text.
Return an object (for example, a dict) when your tool produces structured data that the model should inspect.
:::python
:::
:::js
:::
Behavior:
- The object is serialized and sent back as tool output.
- The model can read specific fields and reason over them.
- Like string returns, this does not directly update graph state.
Use this when downstream reasoning benefits from explicit fields instead of free-form text.
Return a @[Command] when the tool needs to update graph state (for example, setting user preferences or app state).
You can return a Command with or without including a ToolMessage.
If the model needs to see that the tool succeeded (for example, to confirm a preference change), include a ToolMessage in the update, using runtime.tool_call_id for the tool_call_id parameter.
:::python
:::
:::js
:::
Behavior:
- The command updates state using
update. - Updated state is available to subsequent steps in the same run.
- Use reducers for fields that may be updated by parallel tool calls.
Use this when the tool is not just returning data, but also mutating agent state.
Configure how tool errors are handled. See the @[ToolNode] API reference for all options.
:::python
from langgraph.prebuilt import ToolNode
# Default: catch invocation errors, re-raise execution errors
tool_node = ToolNode(tools)
# Catch all errors and return error message to LLM
tool_node = ToolNode(tools, handle_tool_errors=True)
# Custom error message
tool_node = ToolNode(tools, handle_tool_errors="Something went wrong, please try again.")
# Custom error handler
def handle_error(e: ValueError) -> str:
return f"Invalid input: {e}"
tool_node = ToolNode(tools, handle_tool_errors=handle_error)
# Only catch specific exception types
tool_node = ToolNode(tools, handle_tool_errors=(ValueError, TypeError)):::
:::js
import { ToolNode } from "@langchain/langgraph/prebuilt";
// Default behavior
const toolNode = new ToolNode(tools);
// Catch all errors
const toolNode = new ToolNode(tools, { handleToolErrors: true });
// Custom error message
const toolNode = new ToolNode(tools, {
handleToolErrors: "Something went wrong, please try again."
});:::
Use @[tools_condition] for conditional routing based on whether the LLM made tool calls:
:::python
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.graph import StateGraph, MessagesState, START, END
builder = StateGraph(MessagesState)
builder.add_node("llm", call_llm)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", tools_condition) # Routes to "tools" or END
builder.add_edge("tools", "llm")
graph = builder.compile():::
:::js
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { StateGraph, MessagesAnnotation } from "@langchain/langgraph";
const builder = new StateGraph(MessagesAnnotation)
.addNode("llm", callLlm)
.addNode("tools", new ToolNode(tools))
.addEdge("__start__", "llm")
.addConditionalEdges("llm", toolsCondition) // Routes to "tools" or "__end__"
.addEdge("tools", "llm");
const graph = builder.compile();:::
Tools can access the current graph state through @[ToolRuntime]:
:::python
from langchain.tools import tool, ToolRuntime
from langgraph.prebuilt import ToolNode
@tool
def get_message_count(runtime: ToolRuntime) -> str:
"""Get the number of messages in the conversation."""
messages = runtime.state["messages"]
return f"There are {len(messages)} messages."
tool_node = ToolNode([get_message_count]):::
For more details on accessing state, context, and long-term memory from tools, see Access context.
LangChain provides a large collection of prebuilt tools and toolkits for common tasks like web search, code interpretation, database access, and more. These ready-to-use tools can be directly integrated into your agents without writing custom code.
See the tools and toolkits integration page for a complete list of available tools organized by category.
Some chat models feature built-in tools that are executed server-side by the model provider. These include capabilities like web search and code interpreters that don't require you to define or host the tool logic.
Refer to the individual chat model integration pages and the tool calling documentation for details on enabling and using these built-in tools.