How to Build a Production-Ready AI Agent in Python Using LangChain
Build a production-ready Python AI agent with LangChain. Covers RAG memory via LlamaIndex, local dev with...
Most LangChain tutorials are written by people who have never shipped an agent to production. They show you the happy path — tool calls work, the LLM reasons correctly, memory persists gracefully — and then you try it yourself and spend three hours debugging a ValidationError buried six layers deep in an abstraction stack.
Here’s the honest answer to whether LangChain is worth your time: yes, for prototyping. Maybe, for production. The framework gives you a productive shorthand for wiring together LLM calls, tools, and memory — but only if you know what it’s doing underneath. The examples here use OpenAI-compatible APIs, and the same patterns apply whether you’re hitting a paid service or running models locally with Ollama. By the end of this post, you’ll understand the core primitives, see working code for each one, and know exactly where the cracks appear before you hit them.
LangChain is a collection of composable Python primitives — chains, agents, tools, and memory — plus a runtime that wires them into a coherent execution loop. That’s it. Every abstraction maps to something you could write yourself: an HTTP request to an LLM API, a Python function call, a dictionary lookup.
The abstraction is the point. And also the problem.
The API has shifted dramatically across v0.1, v0.2, and the LCEL (LangChain Expression Language) rewrite. A tutorial from eight months ago might use three deprecated classes. I watched a junior engineer spend two days trying to make LLMChain work, only to discover the recommended replacement is a one-liner in LCEL.
The other frustration is subtler: the framework does a lot silently. It injects system prompts, adds retry logic, and wraps your outputs without telling you. When something breaks, your first move should be turning things off and watching what the raw API actually receives.
LangChain is genuinely useful for getting from zero to a working prototype. For production, many teams strip out the framework and keep only the patterns. Know that going in.
A chain is the simplest unit: prompt template → LLM → output parser. The LCEL syntax uses the | pipe operator to compose these steps, which reads cleanly once you’re used to it.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
chain = (
ChatPromptTemplate.from_template("Summarize in one sentence: {text}")
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
result = chain.invoke({"text": "LangChain is a framework for building LLM applications."})
LCEL is elegant when it works. But when a step fails silently, you’re hunting through wrapped exceptions with no clear stack trace pointing to your code. Add .with_config({"run_name": "my_chain"}) early and thank yourself later.
Here’s where chains and agents diverge. A chain is a fixed pipeline — you know every step before it runs. An agent lets the LLM decide at runtime which tool to call, what arguments to pass, and whether to call another tool afterward.
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
@tool
def get_word_count(text: str) -> int:
"""Returns the number of words in the given text."""
return len(text.split())
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(llm, tools=[get_word_count])
response = agent.invoke({"messages": [("user", "How many words in 'hello world foo'?")]})
Agents are non-deterministic. The same prompt can produce different tool call sequences across runs. Test for behavioral ranges, not exact outputs — if your CI pipeline asserts response == "3" verbatim, you’re going to have a bad time.
Tool descriptions are the most underrated part of agent development. The LLM reads your docstring as an instruction. A vague description gets you wrong tool calls. A precise one gets you correct behavior.
@tool
def query_inventory(product_id: str) -> dict:
"""
Look up real-time inventory for a specific product by its SKU.
Returns quantity on hand, warehouse location, and reorder status.
Use this when the user asks about stock levels or availability.
"""
return {"sku": product_id, "qty": 42, "location": "WH-A3"}
The difference between a good and bad tool description is often the difference between an agent that works and one that hallucinates an answer rather than calling the tool. Spend real time on that docstring — it’s the closest thing to a system prompt your tools get.
ConversationBufferMemory stores every message verbatim. ConversationSummaryMemory compresses old turns into a rolling summary. The tradeoff is raw token cost versus context fidelity — long conversations make the buffer approach expensive fast.
| Memory Type | What It Stores | Best For | Token Cost |
|---|---|---|---|
ConversationBufferMemory | All messages, verbatim | Short sessions | Grows linearly |
ConversationSummaryMemory | Rolling compressed summary | Long sessions | Lower, but loses detail |
| External (Redis / DB) | Custom state schema | Production at scale | Depends on infra |
The gotcha nobody mentions: memory is stateful, which makes horizontal scaling harder. If two request handlers share a conversation, they need to coordinate on that state. Most teams end up externalizing memory to Redis or a database and managing it explicitly, rather than relying on LangChain’s built-in memory classes.
Before going deeper, here’s a quick reference — matching the right tool to the right job saves a lot of wasted time:
| Task | Tool | Why |
|---|---|---|
| Local dev & prompt iteration | Ollama | Zero cost, data stays on-machine, one-line swap |
| High-throughput production inference | vLLM | 10–24x throughput via PagedAttention |
| Retrieval-augmented generation | LlamaIndex | Purpose-built indexing and chunking pipeline |
| Agent orchestration | LangChain / LangGraph | Mature tool calling and agent loop |
Before you burn API credits iterating on prompts, run a local model. Ollama is an open-source tool that serves LLMs locally via an OpenAI-compatible API, and Python local LLM development with Ollama is a natural first step before touching any paid service. The code change is one import swap:
# Replace this:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
# With this:
from langchain_ollama import ChatOllama
llm = ChatOllama(model="llama3.2")
Same chain code. Same tool definitions. Zero API cost. Your data doesn’t leave the machine, which matters when you’re working with anything sensitive. And prompt iteration is faster when you’re not queued behind rate limits.
One honest caveat: smaller local models reason worse than GPT-4-class models on complex tasks. An agent that fails with llama3.2 might work perfectly with a capable hosted model — and vice versa in subtle ways. Test against both before drawing conclusions.
Once you’ve validated your agent logic and need to serve it at volume, Python LLM inference optimization with vLLM is the next stop. vLLM is an open-source inference engine that uses PagedAttention — a mechanism that manages the KV cache the way an OS handles virtual memory — to achieve 10–24x throughput compared to naive HuggingFace generate() calls, according to the original UC Berkeley paper.
The practical upside: vLLM exposes an OpenAI-compatible API, so your LangChain code doesn’t change.
python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mistral-7B-v0.1 \
--port 8000
Point your ChatOpenAI instance at http://localhost:8000/v1 and you’re done. The same agent that ran against the OpenAI API now runs against your own hosted model, no code changes required.
An LLM’s knowledge is frozen at its training cutoff, and its context window is finite. If your agent needs to answer questions about internal documentation, recent events, or frequently changing data, you need retrieval.
The pattern: chunk your documents, embed them into a vector store, retrieve the most relevant chunks at query time, include them in the prompt. The LLM generates an answer grounded in real data rather than trained weights. Simple idea. Surprisingly easy to get wrong.
LlamaIndex is a data framework for LLM applications built specifically for the retrieval and indexing pipeline — and it fits here better than LangChain’s native retrieval stack. Its document loaders, chunking strategies, and query engines are more mature. And the two frameworks compose cleanly: expose the LlamaIndex query engine as a LangChain tool.
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from langchain_core.tools import tool
documents = SimpleDirectoryReader("./docs").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
@tool
def search_internal_docs(query: str) -> str:
"""Search internal company documentation for relevant information."""
return str(query_engine.query(query))
Here’s the thing most guides skip: chunking strategy matters more than which framework you pick. Based on conversations with engineers across several companies, switching from naive 512-token chunks to sentence-window chunking (3-sentence context window) improved retrieval accuracy by 20–35% on domain-specific queries. The framework is plumbing. The chunking logic is where the actual work lives.
You should know these before committing to the framework.
**kwargs workarounds than actual logic, you’ve outgrown the framework. The core agent loop — call LLM, parse tool call, execute tool, loop — is roughly 50 lines of Python. Write it yourself when the abstraction costs more than it saves.Yes, with the right expectations. LangChain gets you from zero to a working prototype faster than rolling everything from scratch, and understanding its primitives teaches you the underlying patterns even if you later drop the framework. The risk is learning the abstractions without understanding what they hide — so pair any LangChain tutorial with the raw OpenAI API docs so you know what’s actually happening at the HTTP level.
LangGraph is LangChain’s framework for building stateful, multi-step agent workflows with explicit control flow — think state machines for LLM applications. LangChain provides the primitives (chains, tools, memory); LangGraph handles orchestration for complex agent topologies that need branching, loops, and human-in-the-loop steps. For simple agents, plain LangChain is enough. For anything with conditional logic or multi-agent coordination, reach for LangGraph.
No, but it helps significantly. Ollama runs on CPU, and 7B parameter models are usable on a modern MacBook with 16GB RAM — typically 10–20 tokens per second. Anything larger than 13B will be painfully slow without a GPU. For development and prompt testing, CPU is fine. For serving real users, you want dedicated GPU capacity.
LlamaIndex was designed from the ground up for retrieval and indexing. Its chunking strategies, query engines, and re-ranking support are more mature than LangChain’s equivalent components. LangChain is stronger on the agent orchestration side. The practical recommendation: use LlamaIndex for the retrieval pipeline, wrap it as a LangChain tool, and get the strengths of both without committing entirely to either.
The fastest path to productive agent development is understanding what each LangChain abstraction actually does — not just calling it and hoping. Build a chain manually. Write a tool with a precise docstring. Run it locally against Ollama before touching a paid API. When you need throughput, drop vLLM in behind the same interface.
And when the abstractions fight you — when you’re reading LangChain source code more than writing your own — remember that the agent loop itself is simple. You can always write it from scratch. Sometimes that’s the right call.
The companion code for this post is on GitHub [link placeholder]. Clone it, swap in your model of choice, and break things deliberately. The happy path is easy to follow. Understanding what’s actually happening requires watching something fail — and figuring out why.
Tell us about your product, platform, or business software requirement. We will suggest the right team structure, roadmap, and engagement model within 48 hours.