← Back to DevBytes

LangChain Agents Explained: Tools, Memory, and Chains

Introduction to LangChain Agents

LangChain Agents represent a powerful paradigm where a Large Language Model (LLM) is not just a static oracle but an active decision-maker. Instead of following a fixed sequence of operations, an agent uses the LLM as a reasoning engine to determine which actions to take, which tools to invoke, and how to process their outputs. In essence, agents combine Tools, Memory, and Chains to create dynamic, context-aware applications.

This tutorial will guide you through the core concepts: what agents are, why they are essential, how to build them with practical code, and best practices to follow.

What Are LangChain Agents?

An Agent in LangChain is a system that uses an LLM to decide on a sequence of actions. It differs from a simple chain where the flow is predetermined. An agent receives an objective, reasons about what to do next, selects a tool (if needed), observes the result, and repeats until the task is complete. The key components are:

Why Agents Matter

Static chains are great for deterministic tasks like summarization or classification. However, many real-world problems require dynamic decision-making:

Agents bring flexibility, enabling applications that can adapt on the fly, handle edge cases, and provide more intelligent, assistant-like behavior.

Core Components Explained

Tools: The Agent's Hands

Tools are functions that the agent can call. They are defined with a name, a description (crucial for the LLM to know when to use them), and a function that executes the logic. LangChain provides built-in tools (like SerpAPI for web search, LLM-math for calculations) and allows you to define custom tools easily.

A tool definition typically uses the Tool class or the @tool decorator. The description is parsed by the LLM to decide which tool fits the current step.


from langchain.tools import Tool
from langchain.agents import tool
import requests

# Example of a custom tool using the @tool decorator
@tool
def get_current_stock_price(symbol: str) -> float:
    """Fetches the latest stock price for a given ticker symbol (e.g., AAPL, GOOG)."""
    # In a real app, you'd call a financial API
    response = requests.get(f"https://api.example.com/stock/{symbol}")
    data = response.json()
    return data["price"]

# Defining a tool with the Tool class (for more control)
def compute_square_root(input: str) -> str:
    """Computes the square root of a number given as a string."""
    try:
        number = float(input)
        return str(number ** 0.5)
    except:
        return "Could not compute."

sqrt_tool = Tool(
    name="Square Root Calculator",
    func=compute_square_root,
    description="Useful for calculating the square root of a number. Input must be a numeric string."
)

Toolkit: You can group related tools into a toolkit. For instance, a SQLDatabaseToolkit provides tools for querying databases. The agent receives all tools in a list when initialized.

Memory: Giving the Agent Context

Memory enables the agent to remember previous messages, tool outputs, and user preferences across a conversation. Without memory, each agent invocation is stateless, losing all history. LangChain offers several memory classes that can be attached to the agent chain.

The most common is ConversationBufferMemory, which simply stores the entire chat history. Others like ConversationSummaryMemory compress the history to save tokens. Memory is integrated by passing it to the agent executor.


from langchain.memory import ConversationBufferMemory

# Create a memory object that stores conversation history
memory = ConversationBufferMemory(
    memory_key="chat_history",   # Key used in the prompt template
    return_messages=True          # Return messages as objects (HumanMessage, AIMessage)
)

# Later, when creating the agent executor, pass this memory

Chains: The Execution Backbone

Chains are the structured workflows that orchestrate LLM calls, tool execution, and memory lookups. An agent itself is a chain. LangChain provides the AgentExecutor chain, which handles the loop:

  1. Call the LLM with the agent's prompt (including tools, history, user input).
  2. Parse the LLM output to see if it requires a tool action or a final answer.
  3. If a tool is needed, execute it and feed the result back into the agent.
  4. Repeat until a final answer is generated.

This executor chain can be used standalone or embedded within larger chains. For example, you can create a chain that first processes a user query, then invokes an agent, and finally formats the result.

Building Your First Agent: A Step-by-Step Guide

We will build a simple agent that can answer math questions and fetch stock prices (simulated). We'll use the modern LangChain approach with create_openai_tools_agent (recommended for OpenAI models) and the AgentExecutor.

1. Setup and Imports

Install dependencies: langchain, langchain-openai, and optionally python-dotenv for API keys.


from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.tools import Tool
from langchain.memory import ConversationBufferMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
import os

os.environ["OPENAI_API_KEY"] = "your-openai-key"  # better to use dotenv

2. Define Tools

We create a calculator tool and a custom stock price tool (simulated).


# Calculator tool (built-in LLM-math is better, but for demo we use simple eval)
def calculator(expression: str) -> str:
    """Evaluates a mathematical expression. Input: a valid math expression like '2+2'."""
    try:
        # WARNING: eval is dangerous in production; use a safe parser
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except Exception as e:
        return f"Error: {e}"

calc_tool = Tool(
    name="Calculator",
    func=calculator,
    description="Performs arithmetic calculations. Input must be a math expression string."
)

# Simulated stock tool
def fake_stock_price(symbol: str) -> str:
    """Returns a simulated stock price for demonstration."""
    prices = {"AAPL": 175.5, "GOOG": 140.2, "MSFT": 330.1}
    return str(prices.get(symbol.upper(), 100.0))

stock_tool = Tool(
    name="StockPriceLookup",
    func=fake_stock_price,
    description="Looks up current stock price for a given ticker symbol (e.g., AAPL)."
)

tools = [calc_tool, stock_tool]

3. Initialize the LLM and Agent Prompt

We'll use ChatOpenAI with function calling. The agent prompt needs a MessagesPlaceholder for chat history and one for agent scratchpad (internal tool call history).


llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)

# Prompt template – crucial for structuring the agent's behavior
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant. Use tools only when necessary. Be concise."),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])

4. Create the Agent and Executor with Memory

We instantiate the agent using create_openai_tools_agent, then wrap it in AgentExecutor with memory attached.


# Create the agent (the LLM + tools + prompt)
agent = create_openai_tools_agent(llm, tools, prompt)

# Memory to retain conversation
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True
)

# Agent executor: runs the agent loop and manages memory
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=True,          # See the reasoning steps
    handle_parsing_errors=True,   # Gracefully handle malformed tool calls
    max_iterations=5,      # Prevent infinite loops
)

5. Invoke the Agent

We can now call the executor with user input. The memory will automatically store the conversation.


response1 = agent_executor.invoke({"input": "What's the square root of 144?"})
print(response1["output"])

response2 = agent_executor.invoke({"input": "Now multiply that by the stock price of Apple."})
print(response2["output"])

# Access conversation history
print(agent_executor.memory.chat_memory.messages)

The agent will call the calculator for sqrt(144) = 12, then look up Apple stock (175.5), and finally multiply 12 * 175.5 = 2106.0. Because we used memory, the second call knows "that" refers to the previous result.

Agent Types and Choosing the Right One

LangChain supports multiple agent styles. The choice depends on your LLM and use case:

For production, prefer create_openai_tools_agent if using OpenAI, otherwise create_react_agent. The factory methods standardize agent creation and integrate easily with memory.

Advanced: Customizing Memory and Chains

Using ConversationSummaryMemory

Long conversations can overflow the context window. ConversationSummaryMemory uses an LLM to condense history into a concise summary.


from langchain.memory import ConversationSummaryMemory

summary_memory = ConversationSummaryMemory(
    llm=ChatOpenAI(model="gpt-4-turbo", temperature=0),
    memory_key="chat_history",
    return_messages=True
)

# Replace memory in executor
agent_executor_summary = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=summary_memory,
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=5,
)

Adding Memory to an Existing Chain (LCEL)

LangChain Expression Language (LCEL) allows composability. You can insert memory checkpoints manually using RunnableWithMessageHistory or integrate with LangGraph for more complex state.


from langchain_core.runnables import RunnableWithMessageHistory

# Assuming you have a chain 'agent_chain' that expects 'chat_history' in input
chain_with_history = RunnableWithMessageHistory(
    agent_executor,
    get_session_history=lambda session_id: memory.load_memory_variables({})["chat_history"],
    input_messages_key="input",
    history_messages_key="chat_history",
)

Best Practices for Production Agents

Conclusion

LangChain Agents unlock a new level of dynamic, tool-augmented AI applications. By combining Tools for external actions, Memory for persistent context, and Chains for structured execution, you can build assistants that reason, plan, and act iteratively. The journey from simple chains to agents requires careful designβ€”crafting precise tool descriptions, choosing appropriate memory strategies, and adhering to safety best practices. With the code examples and patterns provided here, you're now equipped to build robust, intelligent agents that go far beyond static prompts. Start experimenting, iterate on your toolset, and watch your application become truly interactive.

β€” Ad β€”

Google AdSense will appear here after approval

← Back to all articles