AI News / 2026-06-01

LangChain vs Custom LLM Wrapper: How to Choose at Scale

XycAi
LangChain vs Custom LLM Wrapper: How to Choose at Scale

This decision matters more than most teams realize

Every team building an LLM application hits the same fork in the road early on: reach for LangChain, or roll your own wrapper?

The default answer is "LangChain first, move fast." That's fine for a proof of concept. But when you hit production and start chasing weird token spikes, fighting opaque stack traces, or getting burned by a pip upgrade that silently breaks your agent chain — that's when teams wish they'd thought harder about LangChain vs custom LLM wrapper before committing.

This isn't a take-down piece or a LangChain defense. It's a decision framework: which tool fits which stage, and how to evaluate the cost of switching.


What LangChain actually gives you

Start here, because the selection question is meaningless without an honest accounting of the value.

The abstraction layer is genuinely useful. LangChain standardizes the "call LLM → parse output → decide next step" loop. A basic RAG pipeline runs in 40–60 lines. Building the same thing from scratch is 200+ lines once you handle retries, streaming, and async edge cases properly.

# Minimal LangChain RAG pipeline
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.vectorstores import Chroma

llm = ChatOpenAI(model="gpt-4o-mini")
retriever = Chroma(...).as_retriever(search_kwargs={"k": 5})

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt_template
    | llm
    | StrOutputParser()
)

The ecosystem integrations are a hard advantage too. LangChain ships standardized interfaces for 100+ vector stores, document loaders, and tool-calling patterns. If you need to compare Pinecone against Weaviate, you swap one line instead of rewriting an adapter layer.

LangSmith deserves a separate mention. It records token usage, latency, and intermediate steps for every chain invocation. For debugging agent behavior, that observability is genuinely hard to replicate on your own without significant investment.


The hidden complexity you'll eventually hit

LangChain's problems aren't about quality — they're about what happens when the abstraction layer stops being an asset and starts being a liability.

Version stability is the biggest operational headache. Since LangChain split into langchain-core, langchain-community, langchain-openai, and friends, dependency management has become non-linear. A single pip install --upgrade can pull in five new package versions simultaneously. If any of them has a breaking change, your agent chain can fail silently — no exception, just wrong behavior.

Debugging complex chains is painful. LCEL's pipeline syntax is clean to write, but when something breaks, the stack trace points into LangChain internals, not your business logic. Tracking down why a tool call didn't fire can eat two hours. With a direct SDK call, you'd find it in ten minutes.

Performance overhead is real at scale. Every Runnable node in a LangChain chain carries serialization and deserialization cost. In load testing, equivalent logic runs 15–30% slower through LangChain than through a direct SDK call, depending on chain depth. Above 200 QPS, that gap compounds.

Cognitive overhead compounds with team size. New engineers joining the project have to learn LCEL syntax, RunnablePassthrough, RunnableLambda, and the broader abstraction model before they can read your business code. That learning tax is small on a two-person team and significant on a ten-person one.


Framework selection by project stage

This is based on real engineering tradeoffs, not theory:

Stage Recommended approach Reason
POC / validation (< 2 weeks) Full LangChain Speed is everything; bugs are acceptable
Growth (1–3 engineers, steady iteration) LangChain Core + custom business layer Keep the ecosystem, control the critical path
Production (QPS > 100, SLA requirements) Bare SDK + minimal wrapper Observability, performance, full control
Multi-model routing / complex agents Custom scheduler + LangSmith for observability Framework lock-in risk too high

At the POC stage, just pip install langchain langchain-openai and don't overthink the architecture. The goal is a demo in three days.

During the growth stage, a middle path works well: depend only on langchain-core's Runnable interface and langchain-openai's model client. Write your own adapters for vector retrieval and document parsing. You keep LangSmith integration, but your core business logic isn't coupled to the framework.

# Depend on langchain-core only; own the routing logic yourself
from langchain_core.language_models import BaseChatModel
from langchain_openai import ChatOpenAI

class LLMRouter:
    """Custom multi-model router — no LangChain Agent dependency."""
    def __init__(self):
        self.models = {
            "fast": ChatOpenAI(model="gpt-4o-mini"),
            "balanced": ChatOpenAI(model="gpt-4o"),
            "flagship": ChatOpenAI(model="gpt-4-turbo"),
        }

    def route(self, task_complexity: float) -> BaseChatModel:
        if task_complexity < 0.3:
            return self.models["fast"]
        elif task_complexity < 0.7:
            return self.models["balanced"]
        return self.models["flagship"]

For production systems where you're already maintaining your own call layer, treat LangChain as an optional observability tool rather than load-bearing infrastructure. Drive the core chain with official SDKs (openai, anthropic) so you have full control over every HTTP request.


How to migrate away from LangChain incrementally

This isn't about rewriting everything. It's a step-by-step path with rollback points at each stage:

  1. Wrap first. Add your own service class around your existing LangChain chains. All business logic calls through that layer — nothing touches LangChain objects directly.
  2. Find the hot paths. Use LangSmith or your own logs to identify the 3–5 most frequently called chains. Those are your migration priorities.
  3. Replace from the outside in. Start by swapping the outermost ChatOpenAI call for a bare SDK call. Keep the surrounding structure, verify behavior matches, then move deeper.
  4. Keep what's stateless. PromptTemplate, StrOutputParser, and similar utilities don't cause lock-in. There's no reason to replace them.

Each step is independently deployable. You can canary-shift traffic at the module level and roll back cleanly if something breaks.


The real tradeoff: speed vs control

LangChain vs custom LLM wrapper ultimately comes down to when you need each one.

Frameworks give you speed. Custom wrappers give you control. Early projects need speed; mature production systems need control. That's not a contradiction — it's just different priorities at different stages. The trap is applying POC thinking to a production system, or applying production-grade architectural standards to a two-day experiment.

Whatever you decide, there's one thing worth doing from day one: design your model call layer to be replaceable. When you need to swap GPT-4o for Claude Sonnet to cut costs, that should be a one-line config change, not a codebase-wide refactor.


For my own model testing and framework evaluation, I've been routing calls through XycAi. It's an OpenAI-compatible API that covers 200+ models — GPT, Claude, Gemini, DeepSeek — starting at 14% of list price, which makes multi-model comparison testing much cheaper. When you're evaluating how different models perform on the same chain, the one-command setup for Claude Code and Codex means you can run the comparison without touching your application code.

One API for 200+ global AI models

GPT · Claude · Gemini official models from 14% of list price. Licensed LLM filing, CN2 direct connect at ~5ms, compliant global invoicing.

Try XycAi →