LangChain vs Custom Agents: When to Use Each
LangChain gets you started fast but adds complexity at scale. Custom agents are more work upfront but easier to operate. Here's when each is the right choice.
LangChain is the most popular framework for building LLM applications. I've used it, moved away from it for most production work, and have a clear view of when it helps and when it doesn't. Here's the honest assessment.
What LangChain is and isn't
LangChain is a framework providing abstractions for:
- LLM providers (unified interface to OpenAI, Anthropic, etc.)
- Memory (conversation history management)
- Chains (sequences of LLM calls + tool calls)
- Agents (ReAct-style tool-using agents)
- Vector stores (abstraction over Pinecone, pgvector, etc.)
- Document loaders (parse PDFs, HTML, etc.)
LangChain is NOT:
- A deployment platform
- A production reliability layer
- A performance optimization (it adds overhead)
- Simpler than the underlying APIs for complex use cases
Where LangChain genuinely helps
Prototyping
LangChain lets you build a working agent in 30 lines:
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import DuckDuckGoSearchRun, WikipediaQueryRun
llm = ChatAnthropic(model="claude-haiku-4-5")
tools = [DuckDuckGoSearchRun(), WikipediaQueryRun()]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
result = executor.invoke({"input": "Who invented the CRDT algorithm?"})
This works. For a demo, a prototype, or an internal tool where you need to ship in 2 hours, LangChain is the right choice.
Document pipelines
LangChain's document loaders and text splitters handle the boring-but-necessary work of turning raw documents into chunks:
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFLoader("architecture.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ".", " "]
)
chunks = splitter.split_documents(docs)
Writing this from scratch is ~100 lines of PDF parsing and text chunking. LangChain's loaders save that work.
Where LangChain becomes a liability
Debugging
LangChain abstractions stack. When something goes wrong, the error is often buried under 5–10 layers of abstraction:
Error in LLMChain.invoke → ConversationChain.call → BaseChain._call →
LLMMathChain.run → Claude.complete → Anthropic API → network timeout
Where did it actually fail? Why? The stack trace doesn't help because you're debugging LangChain internals, not your code.
With a custom agent:
Error in my_agent.complete_task → claude_client.complete → Anthropic API: "timeout after 30s"
The call stack is your code, immediately debuggable.
Version instability
LangChain has released breaking changes in nearly every minor version. Production code built on LangChain 0.1 required significant rewrites for 0.2. Community packages (langchain_community) moved to langchain_anthropic, langchain_openai, etc.
For production code that needs to run unchanged for 12+ months: either pin the version and never update (sacrificing security patches) or spend time on migration.
Custom control flow
LangChain's agents use preset patterns (ReAct, MRKL). When your logic doesn't fit: a negotiation state machine, a multi-step validation loop, tiered model routing — you fight the framework:
# What you want:
if message.confidence < 0.8:
response = high_quality_model.complete(prompt)
elif is_negotiation_phase(state):
response = negotiation_model.complete(negotiation_prompt)
else:
response = cheap_model.complete(prompt)
# What LangChain wants:
class CustomRouter(Chain):
class Config:
arbitrary_types_allowed = True
# ...15 lines of Chain subclass boilerplate...
The custom control flow becomes more complex with LangChain than without it.
Custom agent: what it looks like
A production-ready custom agent for BikroyBuddy-style intent routing:
class BikroyAgent:
def __init__(self, llm_router: ModelRouter, tools: Tools, memory: Memory):
self.router = llm_router
self.tools = tools
self.memory = memory
async def handle(self, message: str, context: ConversationContext) -> str:
# 1. Classify intent (cheap model)
intent = await self._classify_intent(message)
# 2. Route to appropriate handler
match intent.type:
case "product_search":
return await self._search_products(message, context)
case "negotiate":
return await self._negotiate(message, context)
case "greet":
return await self._greet(context)
case _:
return await self._general_reply(message, context)
async def _classify_intent(self, message: str) -> Intent:
response = await self.router.complete(
model="claude-haiku-4-5",
prompt=f"Classify the intent of this message as one of [product_search, negotiate, greet, other]:\n{message}",
)
return Intent(type=response.strip())
This is 30 lines, debuggable, and exactly as complex as it needs to be. No framework overhead.
Decision framework
| Situation | Use LangChain | Use custom | |-----------|--------------|------------| | Prototype or demo | ✅ | | | Internal tool, ship fast | ✅ | | | Production, < 6 months lifetime | ✅ | | | Production, 12+ months operation | | ✅ | | Custom control flow needed | | ✅ | | Complex debugging requirements | | ✅ | | Document loading/parsing | ✅ | | | Multiple LLM providers, uniform interface | ✅ | | | Performance critical | | ✅ |
FAQ
Is LangChain good for production AI applications? It depends on complexity. For standard use cases (chatbots, document Q&A, basic agents), LangChain is production-viable. For custom control flow, complex state machines, or applications requiring precise debugging, custom agents are easier to operate long-term.
What's LangGraph? LangGraph is LangChain's framework for building stateful, multi-actor agents as directed graphs. More flexible than LangChain chains but adds the same debugging complexity. Useful for multi-agent workflows if you're already in the LangChain ecosystem.
What do you use instead of LangChain? Direct provider SDKs (anthropic, openai), custom state machines for agent logic, pgvector for retrieval, and Temporal.io for durable multi-step workflows. More code upfront, easier to debug and operate.
Can I mix LangChain with custom code? Yes — use LangChain's document loaders and text splitters (they're genuinely useful) while writing custom agent logic. Cherry-pick the useful parts without adopting the whole framework.
Written by Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. See also: Multi-Agent AI Systems: Architecture Patterns · MCP Explained: Model Context Protocol for AI Agents.