Build an AI Agent with LangChain: Complete Python Tutorial
LangChain is the most popular framework for building LLM-powered AI agents in Python. It provides pre-built components for tools, memory, chains, and agent orchestration — so you can focus on your agent's logic instead of infrastructure. In this tutorial, you'll build a fully functional agent from scratch.
What You'll Learn#
- Setting up a LangChain project from scratch
- Creating custom tools for your agent
- Building a ReAct agent with reasoning and action
- Adding conversation memory for multi-turn interactions
- Error handling and deployment considerations
Prerequisites#
- Python 3.10+ installed
- An OpenAI API key (or Anthropic / other LLM provider)
- Understanding of AI agent architecture
- Basic knowledge of prompt engineering
Step 1: Project Setup#
Create your project and install dependencies:
mkdir my-langchain-agent && cd my-langchain-agent
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install langchain langchain-openai langchain-community python-dotenv
Create a .env file for your API keys:
OPENAI_API_KEY=your-api-key-here
Create agent.py:
import os
from dotenv import load_dotenv
load_dotenv()
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain import hub
# Initialize the LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)
Step 2: Create Custom Tools#
Tools give your agent the ability to interact with the real world. Each tool is a Python function decorated with @tool:
from langchain.tools import tool
from datetime import datetime
import json
@tool
def get_current_time() -> str:
"""Get the current date and time. Use when the user asks
about today's date or current time."""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression. Use for any math
calculations. Input should be a valid Python math expression.
Args:
expression: A mathematical expression like '2 + 2' or
'math.sqrt(144)'
"""
import math
try:
result = eval(expression, {"__builtins__": {}}, {"math": math})
return str(result)
except Exception as e:
return f"Error: {e}"
@tool
def search_knowledge_base(query: str) -> str:
"""Search the internal knowledge base for information.
Use when the user asks about company policies, products,
or procedures.
Args:
query: The search query to look up
"""
# In production, this would query a vector database
knowledge = {
"refund policy": "Refunds are available within 30 days "
"of purchase. Contact support@example.com.",
"business hours": "Monday-Friday, 9 AM to 6 PM EST. "
"Closed on federal holidays.",
"pricing": "Starter: $19/mo, Pro: $49/mo, "
"Enterprise: Custom pricing."
}
query_lower = query.lower()
for key, value in knowledge.items():
if key in query_lower:
return value
return "No relevant information found in knowledge base."
tools = [get_current_time, calculate, search_knowledge_base]
Tool Design Best Practices#
| Practice | Why It Matters | |----------|---------------| | Clear docstrings | The LLM reads them to decide when to use each tool | | Specific input types | Reduces tool call errors | | Error handling | Prevents agent crashes on tool failures | | Return strings | LLM processes text — always return string results |
Step 3: Build the ReAct Agent#
The ReAct (Reasoning + Acting) pattern alternates between thinking and tool-calling:
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
# Define the agent prompt
prompt = PromptTemplate.from_template("""You are a helpful assistant with access to tools.
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}
Thought:{agent_scratchpad}""")
# Create the agent
agent = create_react_agent(llm, tools, prompt)
# Create the executor (handles the agent loop)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # Print reasoning steps
max_iterations=5, # Prevent infinite loops
handle_parsing_errors=True
)
Run the Agent#
# Simple query
result = agent_executor.invoke({
"input": "What's our refund policy and how many days is that in hours?"
})
print(result["output"])
Expected agent reasoning:
Thought: The user wants to know the refund policy and convert
the days to hours. I'll search the knowledge base first.
Action: search_knowledge_base
Action Input: refund policy
Observation: Refunds are available within 30 days of purchase.
Thought: Now I need to calculate 30 days in hours.
Action: calculate
Action Input: 30 * 24
Observation: 720
Thought: I now know the final answer.
Final Answer: Our refund policy allows refunds within 30 days
(720 hours) of purchase. Contact support@example.com.
Step 4: Add Conversation Memory#
Without memory, each query starts from scratch. Add memory for multi-turn conversations:
from langchain.memory import ConversationBufferWindowMemory
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
# Memory stores the last 10 exchanges
memory = ConversationBufferWindowMemory(
memory_key="chat_history",
return_messages=True,
k=10
)
# Updated prompt with memory
prompt_with_memory = PromptTemplate.from_template("""You are a helpful assistant.
Previous conversation:
{chat_history}
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}
Thought:{agent_scratchpad}""")
# Rebuild agent with memory
agent_with_memory = create_react_agent(llm, tools, prompt_with_memory)
agent_executor_with_memory = AgentExecutor(
agent=agent_with_memory,
tools=tools,
memory=memory,
verbose=True,
max_iterations=5,
handle_parsing_errors=True
)
# Multi-turn conversation
agent_executor_with_memory.invoke({"input": "What's the pricing?"})
agent_executor_with_memory.invoke({"input": "Which plan do you recommend for a startup?"})
# The agent remembers the pricing info from the first query
Memory Types#
| Memory Type | Use Case | Trade-off |
|-------------|----------|-----------|
| ConversationBufferMemory | Short conversations | Uses more tokens |
| ConversationBufferWindowMemory | Most use cases | Forgets old messages |
| ConversationSummaryMemory | Long conversations | Loses detail |
| ConversationTokenBufferMemory | Token-limited contexts | Truncates by tokens |
Step 5: Structured Output#
For production agents, you often need structured JSON output:
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
class AgentResponse(BaseModel):
answer: str = Field(description="The answer to the user's question")
confidence: float = Field(description="Confidence score 0-1")
sources: list[str] = Field(description="Data sources used")
parser = JsonOutputParser(pydantic_object=AgentResponse)
# Add format instructions to your prompt
format_instructions = parser.get_format_instructions()
Step 6: Error Handling & Production Readiness#
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def run_agent_safely(query: str) -> dict:
"""Run the agent with error handling and timeouts."""
try:
result = agent_executor.invoke(
{"input": query},
config={"max_execution_time": 30} # 30 second timeout
)
return {
"status": "success",
"output": result["output"]
}
except Exception as e:
logger.error(f"Agent error for query '{query}': {e}")
return {
"status": "error",
"output": "I encountered an error processing your "
"request. Please try rephrasing."
}
Production Checklist#
- [ ] Set
max_iterationsto prevent infinite loops - [ ] Add timeout limits for tool calls
- [ ] Handle parsing errors gracefully
- [ ] Log all agent reasoning steps
- [ ] Monitor token usage and costs
- [ ] Add rate limiting for external tool calls
- [ ] Test with adversarial inputs
Complete Working Example#
"""Complete LangChain agent example."""
import os
from dotenv import load_dotenv
from datetime import datetime
load_dotenv()
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts import PromptTemplate
# LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# Tools
@tool
def get_current_time() -> str:
"""Get the current date and time."""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def calculate(expression: str) -> str:
"""Evaluate a math expression. Input: valid Python math."""
import math
try:
return str(eval(expression, {"__builtins__": {}}, {"math": math}))
except Exception as e:
return f"Error: {e}"
tools = [get_current_time, calculate]
# Prompt
prompt = PromptTemplate.from_template("""You are a helpful assistant.
Chat history: {chat_history}
Tools: {tools}
Format:
Question: {input}
Thought: think about what to do
Action: one of [{tool_names}]
Action Input: the input
Observation: the result
Thought: I now know the final answer
Final Answer: the answer
Question: {input}
Thought:{agent_scratchpad}""")
# Memory
memory = ConversationBufferWindowMemory(
memory_key="chat_history", return_messages=True, k=10
)
# Agent
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent, tools=tools, memory=memory,
verbose=True, max_iterations=5, handle_parsing_errors=True
)
# Run
if __name__ == "__main__":
while True:
query = input("\nYou: ")
if query.lower() in ("quit", "exit"):
break
result = executor.invoke({"input": query})
print(f"\nAgent: {result['output']}")
Common Mistakes to Avoid#
- No max_iterations: Without a limit, agents can loop forever, burning tokens
- Vague tool descriptions: The LLM relies on docstrings to pick tools — be specific
- Missing error handling: Tools fail in production — always catch exceptions
- Too many tools: Start with 3-5 tools, add more only when needed
- Ignoring verbose output: During development, always set
verbose=Trueto debug reasoning
Next Steps#
- Build an AI Agent with CrewAI — multi-agent orchestration
- Introduction to RAG for AI Agents — add knowledge retrieval
- Multi-Agent Systems Guide — scale to multiple agents
Frequently Asked Questions#
LangChain vs. LangGraph — which should I use?#
LangChain is best for linear agent workflows with tools and memory. LangGraph (part of the LangChain ecosystem) is for complex stateful graphs with cycles, conditional routing, and human-in-the-loop patterns. Start with LangChain, migrate to LangGraph when your agent needs branching logic.
How much does it cost to run a LangChain agent?#
Costs depend on LLM calls per query. A typical ReAct agent makes 2-5 LLM calls per task. With GPT-4o at ~$5/1M input tokens, a simple agent costs $0.01-0.05 per interaction. Complex agents with many tool calls can reach $0.10-0.50 per task.
Can I use local LLMs instead of OpenAI?#
Yes. LangChain supports Ollama, llama.cpp, vLLM, and other local inference providers. Replace ChatOpenAI with ChatOllama and point it to your local model. Note that smaller models may struggle with ReAct reasoning — test thoroughly.
How do I deploy a LangChain agent?#
Common patterns: wrap the agent in a FastAPI server, deploy with Docker, and expose via REST API. LangServe (official LangChain deployment tool) provides built-in REST endpoints. For serverless, use AWS Lambda or Google Cloud Functions with lightweight dependencies.