← all posts
// architecture · langchain

You probably don't need LangChain (I said it and I use it)

A junior engineer on the logistics account asked me last month why we were dragging LangChain into a service that talks to exactly one model and does exactly one thing. I didn't have a good answer, so we took it out. The replacement was about forty lines and lost nothing. That's the whole case here, and I'm the person who wrote the pro-LangChain piece, so bear with me.

the actual decision rule

The question is never whether LangChain is good. The question is whether the abstraction earns its weight on this specific service. Here's the rule I use now, and it fits on one hand:

  • One provider, one prompt, a loop with a branch or two: raw SDK wins. Every time.
  • Structured output you need validated the same way across two or more models: framework earns its keep.
  • Retrieval hairy enough that you'd otherwise hand-write six integrations: framework earns its keep.
  • Providers you'll swap under load: framework, or at minimum its provider seam.

Most services I meet are the first line wearing a costume borrowed from the other three.

the forty-line replacement

The service was a summarizer. Ticket in, three-sentence summary out, one model, one prompt template. Somebody had built it as a chain with a prompt runnable, an output parser, and a retry wrapper, and it pulled in half the world to do it. Our container's cold-start was measurably slower from the import graph alone.

I replaced the whole thing with a Pydantic model for the response, one function that formats the prompt, one call to the provider SDK, and a try with a single retry. Forty-ish lines. It started faster, the stack traces pointed at my code instead of a framework internal, and the next person to open the file understood it in a minute instead of an afternoon.

If you can't name the specific thing a framework is abstracting over, it's just sitting between you and the bug.

That sentence is the entire skill. Abstractions justify themselves by the variation they hide, and one provider with one prompt is no variation at all, so there's nothing to hide and the wrapper is pure overhead.

the counter-story, because I've been burned the other way

Now the honest half, because pure laziness can curdle into its own mess. On a different repo (an internal tool, not the client), I did the raw-SDK thing and felt clever about it. Then we added a second provider for fallback. Then a third for cost. Then structured output. Then a cache. Eighteen months on, I was staring at a directory called llm/ with a base class, a registry, retry-with-backoff, a half-baked message-normalization layer, and a real bug in how it merged tool-call deltas.

I had written a framework. A worse one than the thing I'd refused to import, with no docs and exactly one person who understood it, and that person was me on a good day.

so which is it

Both, depending on the service, and you have to keep re-asking the question. Picking wrong once is cheap. The expensive mistake is failing to notice when a service quietly crosses the line: a summarizer that grows into a multi-provider extraction engine has earned the framework it started without, and a 'flexible' harness that only ever talks to one model has become dead weight you maintain for an audience of nobody.

I keep a note in the repo now. The day you add the second provider is the day you re-open the build-versus-buy question, and I dropped the thin-harness argument and the fifty-lines version straight into the README so future-me doesn't relitigate it from scratch.

Single provider, simple loop? Don't import a framework to feel professional. Six variations you're actively juggling? Don't hand-roll one to feel clever. It's the same mistake wearing two different outfits: building for a shape of problem you don't actually have.

#langchain#architecture#yagni