Building AI Agents with LangChain

The most powerful AI applications of today are not simple chatbots that answer questions — they are agents that can reason, plan, and take action. An AI agent perceives its environment, decides which tool to use, executes actions, observes the results, and iterates until it has solved the problem.

LangChain is the leading open-source framework for building such agents in Python. It provides a composable set of building blocks — LLMs, tools, memory, chains, and agent executors — that let you assemble production-grade AI agents without writing a reasoning loop from scratch.

This guide takes you from zero to a fully functional LangChain agent. You will set up your environment, understand the core concepts, build tools, wire up memory, and run a ReAct agent that reasons step by step. Every section includes working code you can run immediately.

A traditional LLM call is stateless: you send a prompt and receive a response. An AI agent is fundamentally different — it operates in a loop where the LLM acts as a reasoning engine that decides what to do next, not just what to say.

ConceptDescription
LLM (Brain)The language model that reasons, plans, and decides which action to take next
ToolsFunctions the agent can call — web search, calculators, databases, APIs, file readers
MemoryStorage of past conversation turns or facts so the agent can reason across multiple steps
Agent ExecutorThe runtime loop: calls the LLM, parses its output, runs tools, feeds results back
ReAct PatternReasoning + Acting: the agent alternates between Thought, Action, and Observation steps

The ReAct (Reasoning + Acting) pattern is the most widely used agent strategy. At each step the LLM emits a Thought (what it is trying to do), an Action (which tool to call and with what input), and then receives an Observation (the tool’s output). It repeats this loop until it can produce a Final Answer.

Install LangChain and its dependencies. LangChain has split its packages by integration, so you install only what you need.

# Install core LangChain packages
pip install langchain langchain-core langchain-community
# Install OpenAI integration (or use any other LLM provider)
pip install langchain-openai
# Install additional tools used in this guide
pip install duckduckgo-search wikipedia numexpr
# Optional: LangSmith for tracing and debugging
pip install langsmith

Configure Your API Keys

import os
# Set your OpenAI API key
os.environ['OPENAI_API_KEY'] = 'your-openai-api-key-here'
# Optional: LangSmith tracing (highly recommended for debugging)
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_API_KEY'] = 'your-langsmith-api-key'
os.environ['LANGCHAIN_PROJECT'] = 'langchain-agent-demo'
LangSmith is LangChain’s observability platform. Even in development, enabling tracing gives you a visual trace of every LLM call, tool invocation, and token count — invaluable for debugging agent loops that go wrong.

Before building an agent, you need to understand LangChain’s four fundamental building blocks: LLMs, Prompts, Chains, and Tools.

LangChain wraps any LLM behind a standard interface. ChatOpenAI is the most common — it wraps OpenAI’s GPT models with a message-based interface. You can swap in Anthropic, Google, Mistral, or any local model with a single line change.

from langchain_openai import ChatOpenAI
# Initialise the LLM
llm = ChatOpenAI(
model = 'gpt-4o', # or 'gpt-3.5-turbo', 'gpt-4-turbo'
temperature = 0, # 0 = deterministic; higher = more creative
max_tokens = 1000
)
# Simple invocation
response = llm.invoke('What is LangChain?')
print(response.content)

OUTPUT: LLM response

FieldValue
response.contentLangChain is a framework for developing applications powered by language models…
response.usage_metadata{‘input_tokens’: 12, ‘output_tokens’: 87, ‘total_tokens’: 99}

Prompt templates let you parameterise your prompts with dynamic values. ChatPromptTemplate is the standard for chat models — it separates the system instruction from the human message.

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
('system', 'You are a helpful assistant specialising in {domain}.'),
('human', 'Answer this question: {question}'),
])
# Format the prompt with values
formatted = prompt.format_messages(
domain = 'data engineering',
question = 'What is Apache Spark?'
)
print(formatted)

LangChain Expression Language (LCEL) lets you compose components using the | pipe operator — similar to Unix pipes. Each component receives the output of the previous one. This is the modern way to build LangChain pipelines.

from langchain_core.output_parsers import StrOutputParser
# Build a chain: prompt | llm | output parser
chain = prompt | llm | StrOutputParser()
# Invoke the chain
result = chain.invoke({
'domain': 'data engineering',
'question': 'What is Apache Spark?'
})
print(result) # Returns plain string, not a Message object
LCEL chains are lazy — they don’t execute until .invoke(), .stream(), or .batch() is called. They also support async execution with .ainvoke() and parallel branches with RunnableParallel, making them production-ready from day one.

Tools are functions the agent can call to interact with the world. LangChain tools are standard Python functions decorated with @tool — the decorator extracts the function name, docstring, and type hints to generate the tool’s schema automatically. The docstring is critical: the LLM reads it to decide when and how to use the tool.

from langchain_core.tools import tool
# The docstring is what the LLM reads to decide when to use this tool
@tool
def calculate_compound_interest(
principal: float,
rate: float,
years: int
) -> str:
'''
Calculate compound interest.
Use this tool when the user asks about investment growth,
compound interest, or future value of money.
Args:
principal: Initial investment amount in dollars
rate: Annual interest rate as a decimal (e.g., 0.08 for 8%)
years: Number of years to compound
'''
amount = principal * (1 + rate) ** years
interest = amount - principal
return f'Future value: ${amount:,.2f} | Interest earned: ${interest:,.2f}'
# Test the tool directly
print(calculate_compound_interest.invoke({
'principal': 10000, 'rate': 0.08, 'years': 10
}))

OUTPUT: Tool output

InputOutput
principal=10000, rate=0.08, years=10Future value: $21,589.25 | Interest earned: $11,589.25

LangChain Community provides dozens of pre-built tools. Here are the most commonly used ones in agent workflows.

from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.tools import tool
# Web search tool
search_tool = DuckDuckGoSearchRun()
# Wikipedia tool
wiki_tool = WikipediaQueryRun(
api_wrapper=WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=500)
)
# Test built-in tools
print(search_tool.invoke('LangChain latest version 2026'))
print(wiki_tool.invoke('Large Language Model'))

Every tool has a schema that LangChain uses to tell the LLM what tools are available and how to call them. You can inspect it at any time.

# Inspect a tool's schema
print(calculate_compound_interest.name)
# 'calculate_compound_interest'
print(calculate_compound_interest.description)
# 'Calculate compound interest. Use this tool when...'
print(calculate_compound_interest.args_schema.schema())
# {'properties': {'principal': {'type': 'number'}, 'rate': ..., 'years': ...}}
ToolPackageWhat It Does
DuckDuckGoSearchRunlangchain-communityWeb search using DuckDuckGo — no API key required
WikipediaQueryRunlangchain-communityQuery Wikipedia articles by topic
PythonREPLToollangchain-communityExecute Python code in a sandboxed REPL
SQLDatabaseToolkitlangchain-communityQuery SQL databases with natural language
FileManagementToolkitlangchain-communityRead, write, and manage local files
RequestsGetToollangchain-communityMake HTTP GET requests to any URL
@tool (custom)langchain-coreDecorate any Python function to make it a tool

By default, each agent invocation is stateless — the agent has no memory of previous interactions. For conversational agents, you need to persist the conversation history across turns. LangChain provides several memory options.

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# Store for conversation histories, keyed by session ID
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# Wrap your chain with message history
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key = 'input',
history_messages_key= 'chat_history',
)
# Invoke with a session ID
config = {'configurable': {'session_id': 'user_abc'}}
response1 = chain_with_history.invoke({'input': 'My name is Alice.'}, config=config)
response2 = chain_with_history.invoke({'input': 'What is my name?'}, config=config)
print(response2) # Agent remembers: 'Your name is Alice.'

# pip install langchain-redis
from langchain_redis import RedisChatMessageHistory
def get_redis_history(session_id: str):
return RedisChatMessageHistory(
session_id = session_id,
url = 'redis://localhost:6379'
)
chain_with_redis = RunnableWithMessageHistory(
chain,
get_redis_history,
input_messages_key = 'input',
history_messages_key= 'chat_history',
)
# Now conversation history survives server restarts
For production agents, always use a persistent store like Redis or a database. In-memory history is lost when your server restarts. Use different session IDs for different users to keep their conversations isolated.

With tools and memory in place, you are ready to build the agent itself. LangChain’s create_react_agent function wires together the LLM, tools, and a ReAct prompt into a runnable agent.

from langchain_openai import ChatOpenAI
from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain_core.tools import tool
# LLM
llm = ChatOpenAI(model='gpt-4o', temperature=0)
# Built-in tools
search = DuckDuckGoSearchRun()
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(top_k_results=1))
# Custom tool
@tool
def calculate(expression: str) -> str:
'''Evaluate a mathematical expression. Use for any arithmetic or calculation.
Input must be a valid Python math expression, e.g. '2 ** 10' or '100 * 0.15'.'''
try:
return str(eval(expression, {'__builtins__': {}}, {}))
except Exception as e:
return f'Error: {e}'
# Collect all tools
tools = [search, wiki, calculate]

from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
# Pull the standard ReAct prompt from LangChain Hub
# This prompt instructs the LLM to follow Thought/Action/Observation format
react_prompt = hub.pull('hwchase17/react')
# Create the agent (LLM + tools + prompt wired together)
agent = create_react_agent(
llm = llm,
tools = tools,
prompt = react_prompt
)
# Wrap in AgentExecutor — this is the runtime loop
agent_executor = AgentExecutor(
agent = agent,
tools = tools,
verbose = True, # print each Thought/Action/Observation
max_iterations = 10, # prevent infinite loops
handle_parsing_errors = True
)

# Ask the agent a question that requires tool use result = agent_executor.invoke({     ‘input’: ‘What is the current population of India and what is 1.4 billion times 1.08?’ })   print(result[‘output’])

OUTPUT: Agent ReAct trace — Thought / Action / Observation loop

StepTypeContent
1ThoughtI need to find the current population of India. I’ll use the search tool.
2Actionduckduckgo_search(‘current population of India 2026’)
3ObservationIndia’s population is approximately 1.44 billion as of 2026…
4ThoughtNow I need to calculate 1.4 billion times 1.08.
5Actioncalculate(‘1_400_000_000 * 1.08’)
6Observation1512000000.0
7Final AnswerIndia’s population is approximately 1.44 billion. 1.4 billion x 1.08 = 1,512,000,000.

For models that support function/tool calling natively (GPT-4, Claude, Gemini), LangChain’s create_tool_calling_agent is more reliable than ReAct. The model returns structured JSON tool calls instead of parsing free-form text — eliminating parsing errors entirely.

from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# Prompt with placeholders for chat history and agent scratchpad
prompt = ChatPromptTemplate.from_messages([
('system', 'You are a helpful AI assistant with access to tools. '
'Use tools whenever they would help answer the question accurately.'),
MessagesPlaceholder(variable_name='chat_history', optional=True),
('human', '{input}'),
MessagesPlaceholder(variable_name='agent_scratchpad'),
])
# Create the tool-calling agent
agent = create_tool_calling_agent(
llm = llm,
tools = tools,
prompt = prompt
)
agent_executor = AgentExecutor(
agent = agent,
tools = tools,
verbose = True,
max_iterations = 10
)
# Invoke
result = agent_executor.invoke({'input': 'Search for LangChain 2026 updates'})
print(result['output'])
Prefer create_tool_calling_agent over create_react_agent when your LLM supports it. Tool calling is faster (fewer tokens), more reliable (structured JSON output, no parsing failures), and produces cleaner traces in LangSmith.

Combining the tool-calling agent with conversation memory gives you a stateful agent that remembers past interactions within a session.

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# Session store
session_store = {}
def get_history(session_id: str):
if session_id not in session_store:
session_store[session_id] = InMemoryChatMessageHistory()
return session_store[session_id]
# Wrap the executor with message history
agent_with_memory = RunnableWithMessageHistory(
agent_executor,
get_history,
input_messages_key = 'input',
history_messages_key = 'chat_history',
)
config = {'configurable': {'session_id': 'session_001'}}
# Turn 1
r1 = agent_with_memory.invoke(
{'input': 'My name is Alice and I am interested in investing $50,000.'},
config=config
)
print(r1['output'])
# Turn 2 — agent remembers context from Turn 1
r2 = agent_with_memory.invoke(
{'input': 'What would my investment be worth in 10 years at 8% interest?'},
config=config
)
print(r2['output'])
# Agent uses calculate tool with principal=50000, rate=0.08, years=10
# And addresses the user as Alice

The system prompt is how you give your agent a personality, scope, and set of rules. A well-crafted system prompt transforms a general-purpose agent into a domain expert.

SYSTEM_PROMPT = '''You are FinBot, an expert financial analysis assistant.
Your capabilities:
- Search for current financial news and market data
- Perform precise financial calculations
- Explain complex financial concepts in simple terms
Your rules:
- Always use tools to verify current data before answering
- Never fabricate financial figures — if unsure, search first
- Present numbers in a clear, formatted way
- Always include a disclaimer that this is not financial advice
Respond concisely and professionally.'''
prompt = ChatPromptTemplate.from_messages([
('system', SYSTEM_PROMPT),
MessagesPlaceholder(variable_name='chat_history', optional=True),
('human', '{input}'),
MessagesPlaceholder(variable_name='agent_scratchpad'),
])
finbot = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
finbot_executor = AgentExecutor(agent=finbot, tools=tools, verbose=True)

Production agents need robust error handling. Tools can fail, the LLM can produce unparseable output, and loops can run indefinitely. LangChain provides several mechanisms to handle these gracefully.

# 1. Handle tool errors gracefully
@tool
def safe_search(query: str) -> str:
'''Search the web for current information.'''
try:
result = DuckDuckGoSearchRun().run(query)
return result if result else 'No results found for this query.'
except Exception as e:
return f'Search failed: {str(e)}. Try rephrasing the query.'
# 2. Limit iterations and handle errors in AgentExecutor
agent_executor = AgentExecutor(
agent = agent,
tools = tools,
max_iterations = 8, # stop after 8 steps
max_execution_time = 60, # stop after 60 seconds
handle_parsing_errors = True, # recover from LLM output parse errors
early_stopping_method = 'generate', # generate a final answer if limit hit
)
# 3. Catch executor-level errors
try:
result = agent_executor.invoke({'input': user_query})
except Exception as e:
result = {'output': f'Agent encountered an error: {str(e)}'}
ParameterDefaultPurpose
max_iterations15Stop the agent loop after N steps — prevents infinite loops
max_execution_timeNoneStop after N seconds — important for production timeouts
handle_parsing_errorsFalseIf True, retry when the LLM output can’t be parsed
early_stopping_method‘force’‘generate’ asks the LLM for a final answer; ‘force’ stops abruptly
return_intermediate_stepsFalseIf True, returns each Thought/Action/Observation in the output

Here is a complete, self-contained agent you can run immediately. It combines all the concepts: tool-calling agent, multiple tools, conversation memory, system prompt, and error handling.

import os
from langchain_openai import ChatOpenAI
from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain.agents import create_tool_calling_agent, AgentExecutor
os.environ['OPENAI_API_KEY'] = 'your-api-key'
# ── LLM ──
llm = ChatOpenAI(model='gpt-4o', temperature=0)
# ── TOOLS ──
search = DuckDuckGoSearchRun()
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(top_k_results=1))
@tool
def calculate(expression: str) -> str:
'''Evaluate a Python math expression. Input: valid Python expression string.'''
try:
return str(eval(expression, {'__builtins__': {}}, {}))
except Exception as e:
return f'Calculation error: {e}'
tools = [search, wiki, calculate]
# ── PROMPT ──
prompt = ChatPromptTemplate.from_messages([
('system', 'You are a helpful AI assistant. Use tools when needed.'),
MessagesPlaceholder('chat_history', optional=True),
('human', '{input}'),
MessagesPlaceholder('agent_scratchpad'),
])
# ── AGENT ──
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
executor = AgentExecutor(
agent=agent, tools=tools, verbose=True,
max_iterations=10, handle_parsing_errors=True
)
# ── MEMORY ──
store = {}
def get_history(sid):
store.setdefault(sid, InMemoryChatMessageHistory())
return store[sid]
agent = RunnableWithMessageHistory(
executor, get_history,
input_messages_key='input',
history_messages_key='chat_history'
)
# ── RUN ──
cfg = {'configurable': {'session_id': 'demo'}}
while True:
user_input = input('You: ')
if user_input.lower() in ['quit', 'exit']: break
response = agent.invoke({'input': user_input}, config=cfg)
print(f'Agent: {response["output"]}')

StrategyFunctionBest ForRequires
ReActcreate_react_agentAny LLM; text-based reasoning loophwchase17/react prompt
Tool Callingcreate_tool_calling_agentGPT-4, Claude, Gemini; structured JSON callsMessagesPlaceholder
OpenAI Funcscreate_openai_functions_agentOpenAI models only; legacy approachOpenAI functions API
Structuredcreate_structured_chat_agentComplex multi-input toolsStructured chat prompt
Common MistakeWhat Goes WrongFix
Vague tool docstringsLLM doesn’t know when to use the toolWrite clear, specific docstrings describing exactly when to use it
No max_iterations limitAgent loops forever on hard questionsAlways set max_iterations (8-15 is usually sufficient)
Using in-memory history in prodHistory lost on restartUse Redis or a database-backed history store
Trusting LLM mathLLM arithmetic is unreliableAlways provide a calculate tool; never let the LLM do arithmetic alone
Temperature > 0 for agentsNon-deterministic tool selectionSet temperature=0 for agents that need consistent, predictable behaviour
No error handling on toolsOne tool failure crashes the agentWrap tool logic in try/except and return descriptive error strings

LangChain makes building AI agents accessible without sacrificing control. The framework’s composable design — LLMs, tools, memory, prompts, and executor all as independent, swappable components — means you can start simple and scale to production without rewriting your architecture.

The key principles to carry forward: write clear tool docstrings so the LLM knows when to use each tool; always set max_iterations to prevent runaway loops; use create_tool_calling_agent over ReAct for models that support it; enable LangSmith tracing from day one; and use persistent memory for any agent that needs to remember across sessions.

AI agents are not just a technical pattern — they represent a new way of building software where language models drive logic, tools extend capability, and memory provides continuity. LangChain gives you all the building blocks. The rest is up to what you build.

Happy Building!


Discover more from DataSangyan

Subscribe to get the latest posts sent to your email.

Leave a Reply