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
pip install langchain running successfully in Google Colab

Install the google-search-results module to give the agent internet search capability:

pip install openai==0.28.1 google-search-results
pip install openai and google-search-results modules in Google Colab

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:")
Entering OpenAI and SerpAPI keys in Google Colab using getpass

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)
Initializing ChatOpenAI language model with temperature=0 in LangChain

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"
    ),
]
Defining a Search tool using Tool.from_function wrapping SerpAPIWrapper in LangChain
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
    )
)
Appending a Calculator tool with args_schema to the tools list using Tool.from_function
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"
)
Running LangChain agent with the question about Tom Hanks age multiplied by 2

Output

Agent output showing Search tool finding Tom Hanks age and Calculator multiplying by 2

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")
CustomSearchTool and CustomCalculatorTool classes defined by subclassing BaseTool in LangChain
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"
)
Running LangChain agent with BaseTool subclass asking for US president age multiplied by 2

Output

Agent output using BaseTool subclass to find the US president age and multiply it

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
Using the @tool decorator to define a basic search_api function in LangChain
@tool("search", return_direct=True)
def search_api(query: str) -> str:
    """finds the API for the prompt"""
    return "Output"

search_api
Using @tool decorator with name and return_direct arguments in LangChain
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
Using @tool decorator with args_schema=SearchInput for typed input validation in LangChain

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)
StructuredTool.from_function wrapping a post_message function with multiple typed arguments

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")
Structured BaseTool subclass with SearchSchema defining query, engine, gl, and hl parameters

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}"
Using @tool decorator to create a structured post_message tool with url, body, and parameters arguments

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"
Loading existing serpapi and llm-math tools and renaming the search tool to 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"
)
Running agent with modified Google Search tool asking about Leo DiCaprio and his awards

Output

Agent output using modified Google Search tool to return Leo DiCaprio biography and awards

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,
)
Defining Search and Music Search tools with priority — Music Search description mentions it should be used more for music queries
agent.run("what is the most famous song of Christmas")
Agent output selecting Music Search over general Search to return the most famous Christmas song

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,
    )
]
Calculator tool defined with return_direct=True to bypass agent reasoning and return answer immediately
llm = OpenAI(temperature=0)
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)

agent.run("whats 2**12")
Running agent with return_direct Calculator tool asking for 2 to the power of 12

Output

Agent returning 4096 directly from Calculator tool without additional reasoning steps

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
Importing ToolException from langchain.tools.base for custom error handling in LangChain tools
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()
Defining _handle_error function and three search tools where tool1 and tool2 raise ToolException
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,
)
Three LangChain tools defined with handle_tool_error — Search_tool1 and Search_tool2 fail gracefully, Search_tool3 works
agent.run("Who is Leo DiCaprio's girlfriend")
Agent failing on Search_tool1 and Search_tool2 then successfully using Search_tool3 to answer Leo DiCaprio girlfriend question

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.