Skip to content

Chat Completion and the OpenAI-Compatible Client Pattern

Before you start

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

Install the client

NIM doesn't need a NVIDIA-specific SDK for chat. The standard openai Python package is the client.

bash
pip install openai
Point it at NVIDIA's endpoint

The base URL is https://integrate.api.nvidia.com/v1. The API key is the nvapi-... string from your build.nvidia.com model card.

python
from openai import OpenAI

client = OpenAI(
    base_url="https://integrate.api.nvidia.com/v1",
    api_key="nvapi-your-key-here",
)
Call it like OpenAI's own Chat Completions API

One difference: the model field carries NVIDIA's provider/model-name ID instead of an OpenAI model name.

python
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="")
Swap the model by changing one string

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.

python
# 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."}],
)
One client, any NIM 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:

python
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.

Model names move: check before you ship

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.

Quick check — A team built their chat feature by writing a new client class for each NIM model they tried. NVIDIA adds a new, cheaper model to the catalog next month. What does their codebase need?

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.

Continue to Lesson 03

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.

Have a question about this lesson?

Reply here and it goes straight to Rod. Same as replying to one of his emails.