LangChain is powerful. It's also a cognitive tax. Here's why we stripped it out of three production systems and what we built instead.
The moment we decided
It was sprint 6 of a production copilot for a mid-size bank. The LangChain version was working. It was also 2,400 lines of abstracted magic that none of our engineers could fully read in their head at once. When a retrieval regression appeared, it took 4 hours to find the culprit across three nested chain objects.
That was the moment. We rewrote the orchestration layer in a Saturday afternoon.
What we kept from LangChain
Document loaders: they're good and cover almost every format we encounter.
Text splitters: battle-tested chunking implementations are worth keeping.
Callback handlers: the structured event system is genuinely useful for logging.
What we replaced with 200 lines
The core of our orchestrator is a single async function: retrieve_and_generate(query, context_config). It does three things: retrieves K documents from the vector store using hybrid search, builds a prompt from a Jinja2 template, and calls the LLM with structured output parsing.