Chat Completion and the OpenAI-Compatible Client Pattern
Prerequisite: an NVIDIA API key from build.nvidia.com (free tier, no AI Enterprise license needed for evaluation as of this NIM catalog). After this lesson, you can: write a chat-completion client against any NIM-hosted model using the OpenAI SDK, and explain why swapping the underlying model is a one-line change instead of a new integration.
The question this lesson answers
You've got an API key from build.nvidia.com. The model card you're looking at says
meta/llama-3.3-70b-instruct. Next week it might say something else. NVIDIA adds models to
this catalog often, and retires them too. So how do you write client code today that doesn't
need a rewrite the next time the catalog moves?
The answer is the single idea this lesson exists to teach: NIM's chat models all sit behind the same OpenAI-compatible endpoint shape. Learn that shape once, and the model name becomes a string you pass in, not an integration you build.
Why this isn't obvious from the model cards
Each model card on build.nvidia.com reads like its own product: its own name, its own
description, its own "Get API Key" button. Reasonably, from the page layout alone, it's easy to
conclude that meta/llama-3.3-70b-instruct and, say, a Nemotron reasoning model need different
client code, the way switching between two proprietary vendors' APIs usually does.
They don't. NVIDIA built the whole catalog to speak the OpenAI Chat Completions request and
response shape. One client class, one base_url, one auth header. The model ID is the only
thing that changes.
The pattern
NIM doesn't need a NVIDIA-specific SDK for chat. The standard openai Python package is the
client.
pip install openaiThe base URL is https://integrate.api.nvidia.com/v1. The API key is the nvapi-... string
from your build.nvidia.com model card.
from openai import OpenAI
client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key="nvapi-your-key-here",
)One difference: the model field carries NVIDIA's provider/model-name ID instead of an
OpenAI model name.
response = client.chat.completions.create(
model="meta/llama-3.3-70b-instruct",
messages=[{"role": "user", "content": "Explain what makes a NIM container portable."}],
temperature=0.5,
top_p=1,
max_tokens=1024,
stream=True,
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")Everything above the model= line stays identical for a small instruct model on a cheap first
pass or a larger reasoning model on the final answer.
# Same client, same call shape, different model:
response = client.chat.completions.create(
model="nvidia/llama-3.3-nemotron-super-49b-v1.5",
messages=[{"role": "user", "content": "Same prompt, different model."}],
)Wrapping it so the swap is even cheaper
The source pattern this lesson is built from used a small config object plus a thin client class, and that shape still holds up. It's the right instinct, just pointed at the current catalog:
from dataclasses import dataclass
from openai import OpenAI
@dataclass
class ChatConfig:
model: str
temperature: float = 0.5
top_p: float = 1
max_tokens: int = 1024
stream: bool = True
class NimChatClient:
def __init__(self, api_key: str, config: ChatConfig):
self.config = config
self.client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key=api_key,
)
def send(self, prompt: str, history: list[dict] | None = None):
messages = (history or []) + [{"role": "user", "content": prompt}]
c = self.config
return self.client.chat.completions.create(
model=c.model,
messages=messages,
temperature=c.temperature,
top_p=c.top_p,
max_tokens=c.max_tokens,
stream=c.stream,
)
# Usage: the model name lives in one place, the config.
nemotron = NimChatClient(api_key="nvapi-...", config=ChatConfig(model="nvidia/llama-3.3-nemotron-super-49b-v1.5"))
llama = NimChatClient(api_key="nvapi-...", config=ChatConfig(model="meta/llama-3.3-70b-instruct"))The whole point of the wrapper is that model is data, not code. When NVIDIA ships a new model
next quarter, you change a config value, not a class.
Every model ID in this lesson (meta/llama-3.3-70b-instruct, nvidia/llama-3.3-nemotron-super- 49b-v1.5) was confirmed live against docs.api.nvidia.com/nim/reference/llm-apis and its own
model reference page as of 2026-08-20. This isn't a hedge for its own sake: this lesson originally
shipped naming nvidia/llama-3.1-nemotron-70b-instruct as its reasoning-model example, and an
independent currency review the same day confirmed that exact ID had already been deprecated and
dropped from NVIDIA's own live API reference. The tutorial this course replaces named
llama3-8b-instruct in 2024, and that exact ID is stale two catalog generations later. Treat any
model ID as something to re-check against the live catalog before a production deploy, not
something to hardcode from a tutorial, including this one, and trust indefinitely.
What this buys you
This isn't a small convenience. NIM's catalog moves on a real, observable schedule: reranking
models get replaced and then deprecated within the same year, new reasoning and code models
appear every few months, and the naming scheme itself shifts (bare llama3 to versioned
meta/llama-3.3-70b-instruct). A client written against the pattern above survives every one of
those changes without a rewrite. A client written against one specific model's quirks doesn't.
Embeddings and reranking: the same client pattern applied to retrieval, plus a live example of why you check a model's deprecation date before you build on it.
Reply here and it goes straight to Rod. Same as replying to one of his emails.