n4nAI

LangChain LCEL chains with multi-provider model routing

Learn how to implement langchain lcel multi-provider routing with a single OpenAI-compatible endpoint, including LCEL chains, fallback, and verification.

n4n Team3 min read736 words

Audio narration

Coming soon — every post will get a voice note here.

Building langchain lcel multi-provider routing on top of a single OpenAI-compatible endpoint removes the headache of juggling separate SDKs for each model vendor. You get one ChatOpenAI client, a unified message format, and the ability to switch models inside a chain based on cost, latency, or task complexity.

Step 1: Install dependencies and configure the gateway client

Install the LangChain OpenAI integration and core runnables:

pip install langchain-openai langchain-core

Set environment variables for your gateway key and base URL. If you use a gateway such as n4n.ai, one OpenAI-compatible endpoint fronts 240+ models and automatically fails over when a provider is rate-limited or degraded. That matters because your LCEL code never needs to know whether anthropic/claude-3-5-sonnet is served directly or via a proxy.

import os

os.environ["OPENAI_API_KEY"] = "sk-gateway-xxx"  # your gateway key
BASE_URL = "https://api.n4n.ai/v1"  # OpenAI-compatible gateway base

from langchain_openai import ChatOpenAI

def make_client(model: str) -> ChatOpenAI:
    return ChatOpenAI(
        model=model,
        base_url=BASE_URL,
        api_key=os.environ["OPENAI_API_KEY"],
        temperature=0.2,
        max_retries=2,
    )

Keep BASE_URL consistent across all clients. The gateway translates the model field into the correct upstream provider call, normalizes auth, and returns OpenAI-shaped response objects. This is what makes langchain lcel multi-provider routing feasible without custom LLM subclasses.

Step 2: Define a routing function for langchain lcel multi-provider routing

Routing logic belongs in a plain Python function wrapped as a RunnableLambda. Inspect the input and return a model identifier your gateway understands. Use provider-prefixed names to avoid ambiguity.

from langchain_core.runnables import RunnableLambda

def select_model(inp: dict) -> str:
    q = inp["question"]
    # Short, low-stakes queries go to a cheap model.
    if len(q) < 60 and "code" not in q.lower():
        return "openai/gpt-4o-mini"
    # Long or code-related prompts use a stronger model.
    if "code" in q.lower() or len(q) > 200:
        return "anthropic/claude-3-5-sonnet"
    # Default mid-tier
    return "meta-llama/llama-3-70b-instruct"

router = RunnableLambda(select_model)

The heuristic above is deliberately dumb. In production you might call a tiny classifier first, but the LCEL shape stays identical: a runnable that emits a string. This is the core of langchain lcel multi-provider routing—a deterministic selector that maps request shape to a model string, decoupling routing from execution.

Step 3: Pre-instantiate model clients and pick at runtime

Creating a ChatOpenAI instance per request is wasteful and can leak connections. Build a small registry keyed by model name at startup.

MODEL_REGISTRY = {
    "openai/gpt-4o-mini": make_client("openai/gpt-4o-mini"),
    "anthropic/claude-3-5-sonnet": make_client("anthropic/claude-3-5-sonnet"),
    "meta-llama/llama-3-70b-instruct": make_client("meta-llama/llama-3-70b-instruct"),
}

def pick_client(model_name: str) -> ChatOpenAI:
    return MODEL_REGISTRY[model_name]

model_picker = router | RunnableLambda(pick_client)

Compose the picker with the prompt using RunnableAssign so the downstream step sees both the question and the selected client.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableAssign

prompt = ChatPromptTemplate.from_messages([("user", "{question}")])

chain = (
    RunnableAssign({"client": model_picker})
    | RunnableLambda(
        lambda d: d["client"].invoke(
            prompt.format_messages(question=d["question"])
        )
    )
)

The registry pattern is thread-safe for sync invocation because ChatOpenAI wraps a shared HTTP client. If you need async, the same registry works with .ainvoke.

Step 4: Add explicit fallbacks for client-side resilience

The gateway already handles provider-side degradation, but you should still guard against malformed model names or unexpected upstream errors. Wrap the invocation in RunnableWithFallbacks.

from langchain_core.runnables import RunnableWithFallbacks

fallback_client = make_client("openai/gpt-4o-mini")

safe_chain = chain.with_fallbacks(
    [RunnableLambda(lambda d: fallback_client.invoke(
        prompt.format_messages(question=d["question"])
    ))]
)

Now if the routed model raises, the chain retries on the cheap fallback model. This complements gateway-level automatic fallback with a predictable local behavior. You can stack multiple fallbacks by passing more runnables; LangChain tries them in order.

Step 5: Run the chain and verify routing, usage, and cache hints

Invoke with a few distinct inputs to confirm the router picks different models.

queries = [
    {"question": "What's the capital of France?"},
    {"question": "Write a Python function to parse ISO timestamps with timezone support and explain it."},
]

for q in queries:
    resp = safe_chain.invoke(q)
    print(resp.content)
    print("Usage:", resp.usage_metadata)

Verification checklist:

  • The short query should route to openai/gpt-4o-mini (assert by calling select_model(q) in a unit test).
  • The code query should route to anthropic/claude-3-5-sonnet.
  • resp.usage_metadata shows input_tokens and output_tokens — the gateway meters per-token usage even across providers.
  • No exception is raised; if you temporarily pass an unknown model, the fallback returns a valid response.

If your gateway honors client routing directives and forwards provider cache-control hints, you can pass cache settings via default_headers on the client:

cached_client = ChatOpenAI(
    model="anthropic/claude-3-5-sonnet",
    base_url=BASE_URL,
    api_key=os.environ["OPENAI_API_KEY"],
    default_headers={"x-cache-control": "max-age=3600"},
)

This only works if the gateway propagates the hint to the upstream provider; verify against your gateway docs. For streaming, swap .invoke for .stream and iterate resp.content chunks—the same routing and fallback logic applies.

Step 6: Production notes for langchain lcel multi-provider routing

In production, externalize the routing table to configuration so you can shift traffic without code changes. A simple JSON file works:

{
  "routes": {
    "short": "openai/gpt-4o-mini",
    "code": "anthropic/claude-3-5-sonnet",
    "default": "meta-llama/llama-3-70b-instruct"
  },
  "thresholds": {"short_max_len": 60}
}

Load it at startup and feed the values into select_model. Because the LCEL chain is just runnables, you can also branch on streaming vs non-streaming by swapping the invoked method (stream instead of invoke).

Keep the gateway base URL and key in secret storage. The per-token metering from the gateway gives you an auditable cost breakdown per route, which you can aggregate by model name in your observability stack. If you run multiple regions, pin the gateway URL per deployment.

Langchain lcel multi-provider routing is not magic: it is a function that returns a string and a registry of clients. The win is that the rest of your LCEL pipeline—prompts, output parsers, retries—stays identical regardless of which vendor serves the token. When you add a new provider, you add one line to the registry and one route key; no prompt changes required.

Test the chain in CI with mocked clients to assert the router picks the expected model for representative inputs. That catches regressions when you change thresholds or introduce a new model class. Once verified, the pattern scales to dozens of models behind one endpoint without touching chain composition.

Tagslangchainlcelroutingmulti-provider

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langchain + openai-compatible gateway integration posts →