n4nAI

Switching LlamaIndex between GPT-4o and Llama 3.1 via routing

Learn how to implement llamaindex gpt-4o llama 3.1 routing with OpenAI-compatible endpoints, dynamic model selection, and verification steps for production.

n4n Team3 min read664 words

Audio narration

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

Implementing llamaindex gpt-4o llama 3.1 routing lets you serve high-complexity queries with GPT-4o and offload bulk or latency-sensitive traffic to Llama 3.1 without maintaining two code paths. This guide shows how to stand up both models behind LlamaIndex’s OpenAI-compatible interfaces and switch between them per request using explicit routing logic.

Step 1: Install dependencies and import modules

LlamaIndex splits LLM providers into separate packages. For OpenAI and any OpenAI-compatible endpoint you only need llama-index-llms-openai.

pip install llama-index-core llama-index-llms-openai python-dotenv

Import the pieces you will actually use:

import os
from dotenv import load_dotenv
from llama_index.core import Settings, VectorStoreIndex, Document
from llama_index.llms.openai import OpenAI, OpenAILike

load_dotenv()

Keep your dependency surface small. If you are not using the full llama-index meta-package, you avoid pulling in dozens of unused integrations.

Step 2: Configure the two model clients

GPT-4o goes through OpenAI’s official API. Llama 3.1 is an open-weight model served by many providers; the cleanest way to avoid per-provider SDK churn is to treat it as an OpenAI-compatible endpoint. If you want a single endpoint that addresses both proprietary and open-weight models, point OpenAILike at a gateway such as n4n.ai, which exposes one OpenAI-compatible API across 240+ models and honors client routing directives.

# GPT-4o via OpenAI
llm_gpt4o = OpenAI(
    model="gpt-4o",
    api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.0,
)

# Llama 3.1 70B via an OpenAI-compatible gateway
llm_llama31 = OpenAILike(
    model="meta-llama/llama-3.1-70b-instruct",
    api_base=os.getenv("OPENAI_COMPAT_BASE"),  # e.g. https://api.n4n.ai/v1
    api_key=os.getenv("OPENAI_COMPAT_KEY"),
    temperature=0.0,
)

Store keys in .env:

OPENAI_API_KEY=sk-...
OPENAI_COMPAT_BASE=https://api.n4n.ai/v1
OPENAI_COMPAT_KEY=sk-...

The core of llamaindex gpt-4o llama 3.1 routing is a unified interface: both objects expose .chat(), .complete(), and the same metadata shape, so your calling code stays identical.

Step 3: Define an explicit routing policy

Do not hide model selection inside a vague “auto” router. Write a small, testable function that maps query characteristics to a model. A useful heuristic: route to GPT-4o when the query implies multi-step reasoning, code generation, or low tolerance for hallucination; send everything else to Llama 3.1.

class ModelRouter:
    def __init__(self, gpt4o, llama31):
        self.gpt4o = gpt4o
        self.llama31 = llama31
        self._complexity_markers = [
            "why", "explain", "debug", "refactor",
            "compare", "design", "prove"
        ]

    def select(self, query: str):
        q = query.lower()
        if any(marker in q for marker in self._complexity_markers):
            return self.gpt4o
        if len(query.split()) > 120:
            return self.gpt4o
        return self.llama31

router = ModelRouter(llm_gpt4o, llm_llama31)

In production you might replace the keyword check with a cheap classifier or a latency budget check. The point is that the routing decision is visible and logged.

Step 4: Wire routing into a LlamaIndex query flow

LlamaIndex uses a global Settings.llm for index query engines. Swap it inside your request handler. For a runnable example, build an in-memory index from a single document:

doc = Document(text="""
LlamaIndex is a data framework for LLM applications.
It connects custom data sources to large language models.
GPT-4o is OpenAI's multimodal flagship model.
Llama 3.1 is Meta's open-weight model family.
""")
index = VectorStoreIndex.from_documents([doc])

Now write the routed query function:

def routed_query(query: str) -> str:
    llm = router.select(query)
    Settings.llm = llm
    engine = index.as_query_engine(similarity_top_k=1)
    response = engine.query(query)
    # attach model id for verification
    model_id = llm.metadata.model_name
    print(f"[routed to {model_id}]")
    return str(response)

Call it:

print(routed_query("What is LlamaIndex?"))
print(routed_query("Explain why LlamaIndex improves RAG latency compared to raw API calls."))

The first query hits Llama 3.1; the second contains “explain” and routes to GPT-4o. This pattern keeps llamaindex gpt-4o llama 3.1 routing explicit and testable without subclassing query engines.

If you need concurrent requests with different models, do not mutate global Settings from multiple threads. Instead pass the llm directly to a RetrieverQueryEngine instance:

from llama_index.core.query_engine import RetrieverQueryEngine

def routed_query_concurrent(query: str) -> str:
    llm = router.select(query)
    retriever = index.as_retriever(similarity_top_k=1)
    engine = RetrieverQueryEngine.from_args(retriever, llm=llm)
    return str(engine.query(query))

Step 5: Verify the routing works

Verification is two-fold: confirm the expected model answered, and confirm the responses are coherent.

Add a tiny assertion helper:

def assert_routing(query, expected_model_substr):
    llm = router.select(query)
    assert expected_model_substr in llm.metadata.model_name
    print(f"OK: '{query[:30]}...' -> {llm.metadata.model_name}")

assert_routing("List the models mentioned.", "llama")
assert_routing("Compare GPT-4o and Llama 3.1 tradeoffs.", "gpt-4o")

Run the script. You should see:

OK: 'List the models mention...' -> meta-llama/llama-3.1-70b-instruct
OK: 'Compare GPT-4o and Llam...' -> gpt-4o
[routed to meta-llama/llama-3.1-70b-instruct]
LlamaIndex is a data framework...
[routed to gpt-4o]
GPT-4o and Llama 3.1 differ in...

If the assertions pass and the printed model IDs match the heuristic, your llamaindex gpt-4o llama 3.1 routing is functioning. For production, emit the model ID to your metrics pipeline alongside per-token usage so you can audit cost savings.

Edge cases worth handling

  • Provider degradation: When GPT-4o is rate-limited, fall back to Llama 3.1 instead of erroring. Wrap engine.query in try/except and re-route on RateLimitError.
  • Streaming: Both OpenAI and OpenAILike support stream_complete. Your router should stay identical; just call llm.stream_complete() and iterate.
  • Cache hints: If your gateway forwards provider cache-control hints, set extra_headers={"cache-control": "max-age=300"} on the Llama 3.1 client to cut repeat-query cost.

Why not use RouterQueryEngine?

RouterQueryEngine is designed to pick between multiple knowledge sources (e.g., a SQL index vs. a vector index), not between LLMs. Shoving model selection into it forces you to build dummy tools and adds indirection. A plain router object and a temporary Settings.llm swap is 20 lines and debuggable in an hour.

Production notes

Once this pattern is proven, move the router behind your API boundary. Accept a prefer_model field in the request schema to let callers override the heuristic. Keep the default policy in code, not config, so you can unit-test it. If you adopt a gateway that honors client routing directives, you can also push the selection server-side, but client-side routing gives you stack traces when the logic breaks.

Verifying llamaindex gpt-4o llama 3.1 routing in CI is cheap: mock the LLM clients, assert select() returns the right object, and you are covered. The runtime swap is boring on purpose.

Tagsllamaindexgpt-4ollama-3-1routing

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 llamaindex llm api integration posts →