
Our AI pipeline was elegant. LangChain orchestrated everything: prompts, chains, memory, agents. It was "best practice." The tutorials said so.
Then I looked at the actual code.
500 lines of LangChain wrappers around what could have been a 20-line API call to OpenAI.
Every debugging session became archaeology — digging through abstraction layers to find where the actual work happened. When we needed to do something slightly non-standard, we fought the framework more than we built features.
We ripped out LangChain. Replaced it with plain Python + OpenAI SDK.
Development velocity doubled. Debugging time dropped 80%. Engineers were happier.
Here's when frameworks help — and when they're just complexity theater.
Section 1: How LangChain Became "The Way"
In 2023, if you wanted to build an AI application, the first question was: "Are you using LangChain?"
It wasn't really a question. It was a test. If you weren't using LangChain, you were doing it wrong.
The Rise:
LangChain appeared at the perfect moment. ChatGPT had exploded. Everyone wanted to build AI apps. But the patterns were unclear. How do you chain prompts? How do you add memory? How do you build agents?
LangChain offered answers. Abstractions for common patterns. A vocabulary for the emerging field. A community of developers figuring things out together.
Tutorials proliferated. "Build a RAG app with LangChain in 10 minutes!" Every AI course, every blog post, every YouTube video defaulted to LangChain.
The Promise:
LangChain promised to handle the hard parts:
- Chains: Compose LLM calls into pipelines
- Memory: Give conversations context
- Agents: Let LLMs use tools dynamically
- Retrievers: Connect LLMs to knowledge bases
You'd focus on your application logic. LangChain would handle the AI orchestration.
The Reality:
The promise was premature abstraction.
Abstractions make sense when patterns are well-understood and stable. In 2023, AI patterns were neither. The field was moving too fast. What was "best practice" in January was obsolete by June.
LangChain tried to abstract over a moving target. The result was a framework that was constantly changing, constantly breaking, and constantly fighting against your actual needs.
The Framework Tax:
Nobody talks about the framework tax. Every abstraction layer you add has a cost:
- Learning curve: Your team must learn the framework's concepts, not just the underlying technology
- Debugging complexity: Problems are now hidden behind abstraction layers
- Upgrade burden: Framework updates require code changes
- Flexibility constraints: The framework's patterns may not match your patterns
For LangChain, the tax was high. Very high.
Section 2: The Problems We Encountered
Let me be specific about what went wrong.
Debugging Hell:
When something broke, the stack trace was useless.
Error: "Failed to parse output"
Where? In which chain? In which step? The error was 5 abstraction layers deep. Finding the actual problem meant adding print statements throughout the LangChain internals.
A bug that would take 5 minutes to find in plain Python took 2 hours in LangChain. Every time.
Our engineers started to dread AI work. Not because AI was hard, but because the framework made simple things complicated.
Breaking Changes:
LangChain moved fast. Too fast.
Every few weeks, a new version would break our code. APIs renamed. Abstractions restructured. Deprecation warnings everywhere.
We spent significant engineering time just keeping up with framework changes. Not building features. Not fixing bugs. Just updating to the latest LangChain version.
The framework was supposed to save us time. It was consuming it.
Over-Engineering:
Simple tasks required complex setups.
Want to call OpenAI with a prompt? Create a PromptTemplate, create an LLMChain, configure the output parser, set up the memory... for what could have been:
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
The abstraction didn't simplify. It complicated. Every simple operation required learning the "LangChain way" of doing it.
Documentation Lag:
The framework moved faster than documentation could follow.
You'd find a tutorial from 3 months ago. The code wouldn't work. The APIs had changed. The imports were different. You'd spend an hour adapting the tutorial to the current version.
Stack Overflow answers were perpetually outdated. The Discord was helpful but overwhelmed. Everyone was confused, all the time.
Section 3: When Frameworks Actually Help
I'm not anti-framework. Frameworks serve a purpose. But that purpose is narrower than the hype suggests.
Production Agents with Complex State:
If you're building sophisticated agents that need to manage complex state, tool selection, and multi-step reasoning, a framework can help.
The patterns here are genuinely complicated. The abstractions save real work.
But most AI applications are not sophisticated agents. They're chatbots. They're RAG apps. They're simple pipelines. They don't need agent frameworks.
Teams with Dedicated Framework Expertise:
If you have engineers who specialize in LangChain, who know its internals, who can debug the abstraction layers — frameworks make more sense.
But most teams don't have this. They have generalist engineers who are best served by tools they can understand end-to-end.
When Patterns Match Exactly:
If the framework's patterns exactly match your use case, the abstraction is valuable.
But frameworks are built for generic use cases. Your application is specific. The mismatch creates friction.
The Honest Assessment:
Most AI projects don't need LangChain. They need direct API calls, some helper functions, and good engineering practices.
The framework adds complexity without adding proportional value. For the majority of use cases, it's overhead.
Section 4: Our "Boring Stack" Replacement
When we ripped out LangChain, we replaced it with something almost embarrassingly simple.
Python + OpenAI SDK:
Direct API calls. No abstraction layers.
from openai import OpenAI
client = OpenAI()
def generate_response(prompt, context):
return client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": context},
{"role": "user", "content": prompt}
]
).choices[0].message.content
That's it. No chains. No templates. No output parsers. Just a function that calls the API.
Simple Functions for Prompts:
Prompts are strings. We use Python's f-strings or simple template functions.
def build_prompt(user_query, documents):
context = "\n".join([doc.content for doc in documents])
return f"Based on the following context:\n{context}\n\nAnswer: {user_query}"
No PromptTemplate class. No special syntax. Just Python.
Postgres for State:
Memory? That's just database storage.
We store conversation history in Postgres. We load it when needed. We save new messages.
No special "memory" abstractions. Just SQL.
Debugging:
When something breaks, the stack trace points to our code. We can read it. We can fix it. No archaeology required.
Results:
- Development velocity: Up 100% (doubled)
- Debugging time: Down 80%
- Onboarding time for new engineers: Down 60%
- Lines of code: Down 70%
- Framework update burden: Zero
Conclusion
LangChain solved a real problem: the AI landscape in 2023 was confusing, and developers needed guidance.
But the solution became the problem. The abstractions added complexity without proportional benefit. The rapid changes created instability. The framework fought against practical needs instead of enabling them.
For most AI applications, the boring stack is better: direct API calls, simple functions, standard databases. Code you can read. Code you can debug. Code you control.
The best abstraction is often no abstraction.
Written by XQA Team
Our team of experts delivers insights on technology, business, and design. We are dedicated to helping you build better products and scale your business.