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:
- Agent (the reasoning loop) β The LLM-powered brain that decides which tool to call and what parameters to pass.
- Tools β Functions or APIs that the agent can invoke to interact with the outside world (search, calculator, database queries, etc.).
- Memory β Mechanisms that allow the agent to remember past interactions, maintaining context across turns.
- Chains β The underlying execution patterns that combine LLM calls, tool usage, and memory retrieval into a coherent workflow.
Why Agents Matter
Static chains are great for deterministic tasks like summarization or classification. However, many real-world problems require dynamic decision-making:
- Ambiguous user queries β "Find the latest news about AI and summarize the key points in a table." This requires searching, extracting, and formatting.
- Multi-step reasoning β "What was the GDP of France in 2022 multiplied by its population growth rate?" Needs a calculator and possibly a web search.
- Tool orchestration β Combining internal APIs, databases, and external services based on context.
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:
- Call the LLM with the agent's prompt (including tools, history, user input).
- Parse the LLM output to see if it requires a tool action or a final answer.
- If a tool is needed, execute it and feed the result back into the agent.
- 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:
- OpenAI Tools Agent (used above) β Leverages native function calling; most reliable for OpenAI models.
- ReAct Agent β General-purpose, uses reasoning + action pattern; works with any LLM but requires careful prompt engineering.
- Structured Chat Agent β Supports tools with multiple inputs, ideal for complex structured data.
- Self-Ask with Search β Specialized for incremental search queries.
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
- Write Clear Tool Descriptions β The LLM relies entirely on the description to decide when and how to use a tool. Be specific about inputs, format, and expected output.
- Limit Tool Scope β Each tool should do one thing well. Avoid monolithic tools that try to handle many unrelated tasks; it confuses the agent.
- Set max_iterations and Timeouts β Prevent runaway loops by capping iterations (e.g., 10) and setting timeouts on tool calls.
- Handle Parsing Errors Gracefully β Use
handle_parsing_errors=Trueand implement fallback responses so the agent can recover. - Validate Tool Inputs β Sanitize inputs to your tool functions. Never use
eval()on user-supplied strings; use safe parsers likenumexpror custom logic. - Monitor Token Usage β Long conversations with full memory can exhaust the context window. Switch to summary memory or sliding window memory (
ConversationBufferWindowMemory). - Test with a Variety of Queries β Simulate ambiguous, edge-case, and adversarial inputs to ensure the agent doesn't call tools incorrectly or expose sensitive data.
- Use LangSmith or Logging β Trace agent runs to debug reasoning steps, tool inputs/outputs, and memory state.
- Consider Human-in-the-Loop β For critical actions (e.g., sending emails, making purchases), add approval steps before tool execution.
- Version Your Tools β Treat tools like APIs; changes to their descriptions or behavior can break agent reasoning. Maintain backward compatibility.
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.