How to Build Tool-Using Agents with LangChain

Build tool-using agents with LangChain by implementing a ReAct (Reasoning and Acting) loop that combines a custom prompt template, structured output parser, and AgentExecutor to enable LLMs to dynamically invoke external tools like web search APIs.

The openai/openai-cookbook repository provides a complete reference implementation in examples/How_to_build_a_tool-using_agent_with_Langchain.ipynb that demonstrates how to build tool-using agents with LangChain from scratch. This guide breaks down the modular architecture—covering tool definition, prompt engineering, and agent execution—that transforms a standard LLM into a reasoning system capable of calling external APIs to answer real-time queries.

Installing Dependencies and Imports

Start by installing the required packages and importing the necessary LangChain classes. According to the source code in examples/How_to_build_a_tool-using_agent_with_Langchain.ipynb (lines 69-76 and 85-104), you need langchain, openai, and supporting libraries for external integrations.

!pip install openai pinecone-client pandas langchain wget tqdm

import os, re
from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import BaseChatPromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain import LLMChain, SerpAPIWrapper
from langchain.schema import HumanMessage, AgentAction, AgentFinish

Configuring External Tools

Tool-using agents require callable functions that the LLM can invoke. The notebook demonstrates this using SerpAPIWrapper to create a web search tool (lines 200-218). Wrap the search functionality in a Tool object with a descriptive name and purpose so the LLM understands when to use it.

search = SerpAPIWrapper()

tools = [
    Tool(
        name="Search",
        func=search.run,
        description="useful for when you need to answer questions about current events",
    )
]

Designing the ReAct Prompt Template

The ReAct (Reasoning and Acting) pattern requires a specific prompt structure that guides the LLM through a loop of Thought → Action → Observation. As implemented in examples/How_to_build_a_tool-using_agent_with_Langchain.ipynb (lines 228-247), the template must include placeholders for {tools}, {tool_names}, user {input}, and {agent_scratchpad} for intermediate reasoning.

template = """
Answer the following questions as best you can, but speaking as a pirate might speak.
You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {input}
{agent_scratchpad}
"""

Implementing Custom Agent Components

Tool-using agents with LangChain require two critical custom classes: a prompt template handler and an output parser. These components translate between the LLM's text output and executable program logic.

The Custom Prompt Template Class

The CustomPromptTemplate class (lines 255-283) inherits from BaseChatPromptTemplate and injects dynamic content into the prompt. The format_messages method processes intermediate_steps to build the reasoning scratchpad and returns a HumanMessage for the LLM.

class CustomPromptTemplate(BaseChatPromptTemplate):
    template: str
    tools: list[Tool]

    def format_messages(self, **kwargs):
        intermediate_steps = kwargs.pop("intermediate_steps")
        thoughts = ""
        for action, observation in intermediate_steps:
            thoughts += action.log
            thoughts += f"\nObservation: {observation}\nThought: "
        
        kwargs["agent_scratchpad"] = thoughts
        kwargs["tools"] = "\n".join([f"{t.name}: {t.description}" for t in self.tools])
        kwargs["tool_names"] = ", ".join([t.name for t in self.tools])
        
        formatted = self.template.format(**kwargs)
        return [HumanMessage(content=formatted)]

prompt = CustomPromptTemplate(
    template=template,
    tools=tools,
    input_variables=["input", "intermediate_steps"]
)

The Output Parser Class

The CustomOutputParser class (lines 298-327) inherits from AgentOutputParser and converts raw LLM text into structured actions. It detects Final Answer: to terminate the loop or extracts Action: and Action Input: using regex to create AgentAction objects.

class CustomOutputParser(AgentOutputParser):
    def parse(self, llm_output: str):
        if "Final Answer:" in llm_output:
            return AgentFinish(
                return_values={"output": llm_output.split("Final Answer:")[-1].strip()},
                log=llm_output,
            )
        
        regex = r"Action: (.*?)[\n]*Action Input:[\s]*(.*)"
        match = re.search(regex, llm_output, re.DOTALL)
        
        if not match:
            raise ValueError(f"Could not parse LLM output: `{llm_output}`")
        
        action = match.group(1).strip()
        action_input = match.group(2).strip(' "')
        
        return AgentAction(tool=action, tool_input=action_input, log=llm_output)

output_parser = CustomOutputParser()

Assembling the Agent and Executor

Combine the components into a functional agent using LLMSingleActionAgent and AgentExecutor (lines 336-366). The stop parameter tells the LLM when to pause for tool execution, while verbose=True enables debugging output that shows the thought process.

llm = ChatOpenAI(temperature=0)
llm_chain = LLMChain(llm=llm, prompt=prompt)

agent = LLMSingleActionAgent(
    llm_chain=llm_chain,
    output_parser=output_parser,
    stop=["\nObservation:"],
    allowed_tools=[t.name for t in tools],
)

agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent, 
    tools=tools, 
    verbose=True
)

Executing Agent Queries

With the executor initialized, you can run queries that require tool usage. The example in lines 380-394 demonstrates the agent processing a question about current events, invoking the search tool twice, and synthesizing a pirate-styled final answer.

answer = agent_executor.run("Canada population 2023")
print(answer)

# Output: "The population of Canada as of 2023 is 38,664,637. Arg!"

The console output reveals the full ReAct trace: the agent generates a Thought, selects the Search tool with an Action Input, receives an Observation, and repeats until reaching a Final Answer.

Summary

  • Tool Definition: Wrap external APIs like SerpAPIWrapper in LangChain Tool objects to give the agent callable functions.
  • ReAct Prompting: Structure prompts with {tools}, {tool_names}, and {agent_scratchpad} placeholders to enforce the thought-action-observation loop.
  • Custom Classes: Implement CustomPromptTemplate to format intermediate reasoning steps and CustomOutputParser to translate LLM text into AgentAction or AgentFinish objects.
  • Agent Assembly: Use LLMSingleActionAgent to bind the LLM, prompt, and parser, then wrap with AgentExecutor to manage the execution loop.
  • Source Reference: The complete implementation resides in examples/How_to_build_a_tool-using_agent_with_Langchain.ipynb within the openai/openai-cookbook repository.

Frequently Asked Questions

What is the ReAct pattern in LangChain agents?

The ReAct (Reasoning and Acting) pattern is a framework that interleaves reasoning traces (Thoughts) with task-specific actions. In the openai/openai-cookbook implementation, the LLM generates a Thought about what to do next, selects an Action (tool name) and Action Input, observes the result, and repeats until determining a Final Answer. This pattern enables the agent to solve multi-step problems by combining internal reasoning with external tool usage.

How do I add custom tools to a LangChain agent?

Define a Python function that performs the desired operation, then wrap it in a LangChain Tool object with a name and description. As shown in the source code, you pass a list of these Tool objects to both the CustomPromptTemplate (for prompt rendering) and the AgentExecutor (for execution). The agent selects tools based on their descriptions in the prompt.

Why is a custom output parser necessary for tool-using agents?

Standard LLM outputs free-form text, but agents require structured decisions. The CustomOutputParser demonstrated in the cookbook intercepts the raw LLM output to detect termination (via "Final Answer:") or extract tool calls (via regex matching "Action:" and "Action Input:"). Without this parser, the system cannot convert text into executable AgentAction objects.

Can I use different LLMs or vector stores with this agent architecture?

Yes. The modular design allows you to swap components without rewriting the core logic. Replace ChatOpenAI with AzureChatOpenAI or another LangChain LLM class in the llm_chain initialization. Similarly, replace SerpAPIWrapper with other tools like vector stores (Pinecone, FAISS) or calculators by updating the tools list and corresponding descriptions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →