LangChain enables developers to design agents for extracting information using language models like OpenAI and others. Agents handle intermediate processes like thoughts, actions, and observations — and they need tools to do it. The LangChain framework provides built-in tools and also enables developers to build their own custom tools.
Quick Answer: To create a custom tool in LangChain, decorate a function with @tool (simplest), use Tool.from_function() for named tools with schemas, or subclass BaseTool and implement _run() for full control. All three approaches let you define the tool’s name, description, and input schema.
LangChain Custom Tool Methods at a Glance
LangChain offers multiple ways to create and configure custom tools. Here’s a quick reference before diving in:
| Method | Approach | Best For |
|---|---|---|
Tool.from_function() |
Wrap any function as a named tool with optional args_schema | Quick wrapping of existing functions |
Subclass BaseTool |
Override _run() and _arun() in a class |
Full control, async support, complex logic |
@tool decorator |
Decorate a function; docstring becomes description | Simplest, fewest lines of code |
StructuredTool.from_function() |
Multi-argument structured tool from a function | Tools with multiple typed inputs |
| Modify existing tools | Load built-in tools and override name/description | Customizing LangChain’s built-in tools |
How to Create Custom Tools in LangChain?
Custom tools accept the following arguments:
name: A string identifying the tool — the agent uses this to select it.
description: Explains to the model what the tool does and when to call it.
return_direct: Boolean (default False) — if True, the tool’s output is returned directly without further agent processing.
args_schema: Optional BaseModel providing additional validation or schema information for the tool’s parameters.
The complete code for this guide is available on Google Colaboratory.
Step 1: Install Modules
pip install langchain

Install the google-search-results module to give the agent internet search capability:
pip install openai==0.28.1 google-search-results

Step 2: Setup Environments
import os
import getpass
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")
os.environ["SERPAPI_API_KEY"] = getpass.getpass("Serpapi API Key:")

Step 3: Build the Language Model
from langchain.chains import LLMMathChain
from langchain.utilities import SerpAPIWrapper
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import BaseTool, StructuredTool, Tool, tool
llm = ChatOpenAI(temperature=0)

Method 1: Configure New Tools
You can build tools from scratch using three approaches: Tool Dataclass, BaseTool Subclass, or the @tool Decorator.
Using Tool Dataclass
search = SerpAPIWrapper()
llm_math_chain = LLMMathChain(llm=llm, verbose=True)
tools = [
Tool.from_function(
func=search.run,
name="Search",
description="Ask the targeted prompts to get answers about recent affairs"
),
]

from pydantic import BaseModel, Field
class CalculatorInput(BaseModel):
question: str = Field()
tools.append(
Tool.from_function(
func=llm_math_chain.run,
name="Calculator",
description="helpful for answering questions about mathematical problems",
args_schema=CalculatorInput
)
)

agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
"What is the age of tom hanks multiplied with 2"
)

Output

Using BaseTool Subclass
from typing import Optional, Type
from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
class CustomSearchTool(BaseTool):
name = "custom_search"
description = "Ask the targeted prompts to get answers about recent affairs"
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool"""
return search.run(query)
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously"""
raise NotImplementedError("custom_search does not support async")
class CustomCalculatorTool(BaseTool):
name = "Calculator"
description = "helpful for answering questions about mathematical problems"
args_schema: Type[BaseModel] = CalculatorInput
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool"""
return llm_math_chain.run(query)
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously"""
raise NotImplementedError("Calculator does not support async")

tools = [CustomSearchTool(), CustomCalculatorTool()]
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
"What is the age of current president of USA multiplied by 2"
)

Output

Using the Tool Decorator
from langchain.tools import tool
@tool
def search_api(query: str) -> str:
"""finds the API for the prompt"""
return f"Output for the prompt {query}"
search_api

@tool("search", return_direct=True)
def search_api(query: str) -> str:
"""finds the API for the prompt"""
return "Output"
search_api

class SearchInput(BaseModel):
query: str = Field(description="should be a search query")
@tool("search", return_direct=True, args_schema=SearchInput)
def search_api(query: str) -> str:
"""finds the API for the prompt"""
return "Output"
search_api

Method 2: Custom Structured Tools
Structured tools accept multiple typed arguments, giving the agent more precise context for when and how to use them.
Using StructuredTool Dataclass
import requests
from langchain.tools import StructuredTool
def post_message(url: str, body: dict, parameters: Optional[dict] = None) -> str:
"""It will dispatch a POST call to the given URL with the provided content and arguments"""
result = requests.post(url, json=body, params=parameters)
return f"Status: {result.status_code} - {result.text}"
tool = StructuredTool.from_function(post_message)

Using Subclass BaseTool
from typing import Optional, Type
from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
class SearchSchema(BaseModel):
query: str = Field(description=" search query can be stated here")
engine: str = Field(description=" search engine can be stated here")
gl: str = Field(description="country code can be stated here")
hl: str = Field(description="language code can be stated here")
class CustomSearchTool(BaseTool):
name = "custom_search"
description = "Ask the targeted prompts to get answers about recent affairs"
args_schema: Type[SearchSchema] = SearchSchema
def _run(
self,
query: str,
engine: str = "google",
gl: str = "us",
hl: str = "en",
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
"""Use the tool"""
search_wrapper = SerpAPIWrapper(params={"engine": engine, "gl": gl, "hl": hl})
return search_wrapper.run(query)
async def _arun(
self,
query: str,
engine: str = "google",
gl: str = "us",
hl: str = "en",
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> str:
"""Use the tool asynchronously"""
raise NotImplementedError("custom_search does not support async")

Using the Decorator
import requests
from langchain.tools import tool
@tool
def post_message(url: str, body: dict, parameters: Optional[dict] = None) -> str:
"""It will dispatch a POST call to the given URL with the provided content and arguments"""
result = requests.post(url, json=body, params=parameters)
return f"Status: {result.status_code} - {result.text}"

Method 3: Modify Existing Tools
You can modify LangChain’s built-in tools to add more specific names or descriptions — without rebuilding from scratch.
Loading the Existing Tools
from langchain.agents import load_tools
tools = load_tools(["serpapi", "llm-math"], llm=llm)
tools[0].name = "Google Search"

Building and Testing the Agent
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
"Who is Leo DiCaprio and how many awards he got"
)

Output

Method 4: Defining the Priorities Among Tools
When two tools have overlapping capabilities, you can prioritize one over the other by making the higher-priority tool’s description more specific:
from langchain.agents import initialize_agent
from langchain.llms import OpenAI
search = SerpAPIWrapper()
tools = [
Tool(
name="Search",
func=search.run,
description="Ask the targeted prompts to get answers about recent affairs",
),
Tool(
name="Music Search.",
func=lambda x: "Mariah Carey's song called All I Want For Christmas",
description="helpful in searching music and should be used more than the other tools for queries related to Music, like 'tell me the most viewed song in 2022' or 'about the singer of a song'",
),
]
agent = initialize_agent(
tools,
OpenAI(temperature=0),
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
)

agent.run("what is the most famous song of Christmas")

Method 5: Using Tools to Return Directly
Use return_direct=True when the tool’s output is the final answer — bypassing the agent’s reasoning loop for faster, cleaner responses:
llm_math_chain = LLMMathChain(llm=llm)
tools = [
Tool(
name="Calculator",
func=llm_math_chain.run,
description="helpful for answering questions about mathematical problems",
return_direct=True,
)
]

llm = OpenAI(temperature=0)
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run("whats 2**12")

Output

Bonus Tip: Handling Tool Errors
When a tool fails, the default behavior stops the agent entirely. Use ToolException and handle_tool_error to keep the agent running and redirecting to a working tool instead:
from langchain.tools.base import ToolException

def _handle_error(error: ToolException) -> str:
return (
"The following errors occurred during tool execution:"
+ error.args[0]
+ "Choose or opt other tools"
)
def search_tool1(s: str):
raise ToolException("The tool1 you searched is not available right now")
def search_tool2(s: str):
raise ToolException("The tool2 you searched is not available right now")
search_tool3 = SerpAPIWrapper()

description = "Ask the targeted prompts to get answers about recent affairs and it should be given priority"
tools = [
Tool.from_function(
func=search_tool1,
name="Search_tool1",
description=description,
handle_tool_error=True,
),
Tool.from_function(
func=search_tool2,
name="Search_tool2",
description=description,
handle_tool_error=_handle_error,
),
Tool.from_function(
func=search_tool3.run,
name="Search_tool3",
description="Ask the targeted prompts to get answers about recent affairs",
),
]
agent = initialize_agent(
tools,
ChatOpenAI(temperature=0),
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
)

agent.run("Who is Leo DiCaprio's girlfriend")

Conclusion
To create custom tools in LangChain, use @tool for the simplest single-function approach, Tool.from_function() to wrap existing functions with a name and schema, or subclass BaseTool for full control including async support. For tools with multiple typed inputs, StructuredTool.from_function() is the right choice. You can also modify existing built-in tools by changing their name or description, prioritize tools through their descriptions, use return_direct=True for direct answers, and configure handle_tool_error to keep the agent running when a tool fails.