NVIDIA NeMo
NeMo is a suite of libraries and microservices for the model and agent lifecycle. The served parts map to /v1/ai (272) for Nemotron and NIM, /v1/o11y (381) for Relay's telemetry, /v1/eval (16) and /v1/benchmark (9) for Evaluator, and /v1/agent (37) for the Agent Toolkit; the data and training libraries keep running, pointed at the same host.
NeMo is not one product. It is a set of libraries and containers that between them cover a model's life: open models (Nemotron), data curation and synthesis (Curator, Data Designer, Anonymizer, Safe Synthesizer), post-training (RL, Gym, Customizer, Megatron-Bridge), evaluation (Evaluator, Auditor), runtime safety (Guardrails), inference (NIM) and agent telemetry (Relay, Agent Toolkit). Some of those are services you run; some are libraries you import. The split matters here, because the two halves migrate differently.
The services have a capability that answers them. /v1/ai (272 operations)
serves the models at the addresses a NIM or integrate.api.nvidia.com client
already calls, and every call it serves is already a gen_ai trace in /v1/o11y
(381), which is the half of Relay that records. /v1/eval (16) and
/v1/benchmark (9) are Evaluator's two modes; /v1/agent (37) is the Agent
Toolkit's workflow with its tools resolved from /v1/tool (19).
The libraries have no counterpart and need none. Curator, Data Designer,
Anonymizer, Safe Synthesizer and NeMo RL take an OpenAI-compatible provider and
your own GPUs; they keep both, and the provider becomes api.hanzo.ai. Nothing on
this page asks you to stop using a NeMo library that works.
Start here
Mint a key, point the client that called Nemotron at a different host, then run a benchmark against what it now calls.
# 1. mint a key — sk- belongs on a server, pk- is safe in a browser
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
-H "Authorization: Bearer $HANZO_SESSION" \
-H 'Content-Type: application/json' \
-d '{"type":"secret"}'
# 2. the call a NIM client already makes, at a different host and model id
curl -sS -X POST https://api.hanzo.ai/v1/chat/completions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"zen5","messages":[{"role":"user","content":"summarise Q3"}]}'
# 3. Evaluator's benchmark mode, as an admission rather than a local run
curl -sS -X POST https://api.hanzo.ai/v1/benchmark/runs \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"zen5","benchmarks":["mmlu","gsm8k"]}'Step 2 is the request your code already sends. NIM and the NVIDIA API are
OpenAI-shaped, so the body is unchanged and the edit is the base URL, the key and
the model name; GET /v1/models publishes the names, zen5 among them. Step 3
answers 202 with a receipt, and the numbers appear on
GET /v1/benchmark/leaderboard as the harness completes them — the same
benchmarks nel eval run --bench would run on your machine, run for you.
Core capabilities
| Capability | What it does | Operations |
|---|---|---|
/v1/ai | Models, the router, files, fine-tuning jobs and their deployment | 272 |
/v1/o11y | Traces, LLM observations, sessions, scores and review queues | 381 |
/v1/eval | Datasets, stored judges and judged runs | 16 |
/v1/benchmark | The public benchmark catalog, runs against a model or your endpoint, the leaderboard | 9 |
/v1/agent | An agent, its runs and its live sessions | 37 |
/v1/sandbox | A leased computer for code and tool environments | 19 |
Nouns
Five tables, one per NeMo concern.
Models and inference
| NeMo | Hanzo |
|---|---|
Nemotron, called through integrate.api.nvidia.com/v1 | zen5, zen5-pro, zen5-coder, zen5-flash, zen5-mini at POST /v1/chat/completions — 14 Zen models among the 432 that GET /v1/models lists |
NIM container serving /v1/chat/completions on your GPU | The same address, served. No container to pull; the model is a name in the catalogue |
| NIM embedding and reranker microservices | POST /v1/embeddings with zen-embedding, POST /v1/rerank with zen-rerank |
| Nemotron content-safety model | zen-guard, a 128K-context safety classifier, called like any other model |
| Switchyard, the proxy that translates OpenAI and Anthropic wire formats | POST /v1/messages and POST /v1/messages/count_tokens sit beside the OpenAI routes on one host; no proxy in between |
| Switchyard's routing algorithms across backends | PUT /v1/ai/router/policy, or the model name router:general, with the ledger at GET /v1/ai/router/history |
NVIDIA_API_KEY | sk-… from POST /v1/account/keys; the org is a claim inside it |
Telemetry and control
Relay does two things: it emits OpenTelemetry GenAI spans for LLM and tool
calls, and it runs middleware around them. The first is already done for every
call /v1/ai serves. The second is a property of the agent definition.
| NeMo | Hanzo |
|---|---|
Relay wrapping an app-owned LLM call so it emits a gen_ai span | Nothing. A call served by /v1/ai is recorded because it was served; GET /v1/o11y/llm/observations has the model, tokens, cost and latency |
Relay's OTLP exporter and plugins.toml endpoint block | An ingestion key from POST /v1/o11y/gateway/ingestion_keys for an exporter you keep; a span you write yourself goes to POST /v1/metrics/traces/write with its gen_ai.* attributes |
| Relay scope — one agent run, its nested tool and LLM calls | A trace. GET /v1/o11y/llm/traces lists them; POST /v1/o11y/traces/{traceId}/waterfall draws one |
| Relay session across a coding-agent run | GET /v1/o11y/llm/sessions — spans grouped by session.id |
| Trajectory export (ATIF) | GET /v1/eval/traces, one per model call an evaluation made, narrowed by runName |
| Relay middleware: redaction, policy, allow or deny before execution | Hanzo Guard at the same three boundaries — guard-proxy in front of the model host, guard-wrap around a local agent, guard-mcp in front of a tool server — redacting PII and scoring injection before the call proceeds; and the agent's tools list, names resolved from GET /v1/tool at run time, omitting one denies it |
nemo-relay run -- claude observing a local coding agent | guard-wrap claude wraps the same process locally; hosted, POST /v1/agent/coding runs the coding agent on this side of the API and GET /v1/agent/sessions/stream is the live event feed |
Agent Toolkit workflow YAML: llms, functions, workflow | POST /v1/agent carrying model, instructions and tools; POST /v1/agent/{ref}/run runs it and answers the finished run |
| Agent Toolkit function | A name from GET /v1/tool, dispatched with POST /v1/tool/call; your own goes in as an MCP server at POST /v1/tool/mcp/servers |
| Agent Toolkit evaluation and profiling of a workflow | GET /v1/agent/runs for every run in the org, GET /v1/agent/metrics for the aggregate |
Guardrails
| NeMo | Hanzo |
|---|---|
config.yml naming a content_safety model and the rails that call it | guard-proxy --upstream https://api.hanzo.ai --port 8080 with OPENAI_BASE_URL pointed at it: PII redaction, injection scoring, content classification by zen-guard, rate limiting and audit, on the input and the output. Or zen-guard called directly at POST /v1/chat/completions; the classification is the response |
| NeMo Guardrails as a Python library in your process | hanzo-guard as a Rust crate — cargo install hanzo-guard --features full (0.1.4 on crates.io): sanitize_input and sanitize_output in your own service |
| Colang flows — dialog rails, topical rails, scripted turns | Instructions on the agent, and its tools allowlist. There is no rails language; see below |
| Self-check input and output rails using the main model | A second call to the same model with your check as the prompt, which is what the rail did |
| Jailbreak detection heuristics | Guard's injection stage — weighted patterns and a confidence threshold, about 20 µs and no model call; zen-guard for the classifier's verdict on top |
| Rail execution logged per turn | Guard's JSONL audit trail — content hashes, never content, per request; and the same trace as the call, where each check is one more observation on it |
Evaluation
| NeMo | Hanzo |
|---|---|
nel eval run --bench mmlu --model-url … --model-id … | POST /v1/benchmark/runs with model, or with endpoint for a model this side does not host; GET /v1/benchmark/catalog lists the ids it accepts |
Benchmark results in ./eval_results/ | GET /v1/benchmark/leaderboard, and GET /v1/benchmark/history for one model over time |
| Two models compared in a report | GET /v1/benchmark/compare — paired on the items both completed, with an exact-McNemar p |
| Custom dataset with an LLM-as-judge | POST /v1/eval/datasets, items at POST /v1/eval/datasets/{name}/items, then POST /v1/eval/runs with a judge — it calls the model, records the trace, calls the judge and answers the summary |
| A judge configuration reused across runs | POST /v1/eval/evaluators — judge model, criteria, the score name it files under |
| Per-item scores | GET /v1/eval/scores, narrowed by runName, name or traceId |
| Interceptor proxy recording requests during an eval | GET /v1/eval/traces — the eval already recorded them |
| NeMo Gym environment: dataset, harness, verifier, at scale | Dataset and judge are /v1/eval; the sandbox an environment needs is POST /v1/sandbox/lease then POST /v1/sandbox/run, which answers exit code, stdout and stderr as data. Rollouts for RL training stay in Gym |
| Auditor (garak) probing for jailbreaks, leakage and injection | No counterpart. garak's openai.OpenAICompatible generator takes a uri; give it https://api.hanzo.ai/v1/ and the model name |
Data and training
| NeMo | Hanzo |
|---|---|
| Data Designer: samplers, LLM columns, validators, judge, preview | gym synth design.yml --preview 5 — the same column kinds in one YAML, rows streamed to jsonl or parquet, any OpenAI-compatible endpoint |
| Curator, Anonymizer, Safe Synthesizer | The same libraries. Each takes an OpenAI-compatible provider; endpoint="https://api.hanzo.ai/v1", provider_type="openai" |
| Data Designer's LLM-as-judge columns, Anonymizer's LLM substitutions | Metered and traced like any other call on your key |
| Customizer, the hosted fine-tuning microservice | POST /v1/ai/finetune/jobs; GET /v1/ai/finetune/presets is the catalogue of base model, method and task with a recommended configuration for each |
| Serving the fine-tuned result as a NIM | POST /v1/ai/finetune/deploy — serves the checkpoints and registers the result as a routable model name on /v1/chat/completions |
| NeMo RL, Megatron-Bridge, Automodel on your cluster | hanzoai/gym — LoRA SFT and GRPO in Rust, one binary for CUDA and Metal, Axolotl-shaped configs, PEFT adapters out. Full fine-tuning and multi-node stay with NeMo on your cluster; GET /v1/compute/gpus is one row per accelerator your org holds here |
| A prompt kept in a NeMo recipe | POST /v1/prompt — a repeated name appends a version |
The call
One guarded, observed inference in NeMo is three components: a NIM serving the
model, Guardrails in front of it with a config.yml, and Relay around the client
so the call is a span.
# config.yml — Guardrails
models:
- type: main
engine: nim
model: meta/llama-3.3-70b-instruct
- type: content_safety
engine: nim
model: nvidia/llama-3.1-nemoguard-8b-content-safety
rails:
input:
flows: [content safety check input $model=content_safety]
output:
flows: [content safety check output $model=content_safety]# the client, wrapped so Relay emits the gen_ai span
nemo-relay run -- python app.py # app.py calls http://localhost:8000/v1 through the railsHanzo. The safety check is a model call, the inference is a model call, and both are traces because they were served:
curl -sS -X POST https://api.hanzo.ai/v1/chat/completions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"zen-guard","messages":[{"role":"user","content":"summarise Q3"}]}'
curl -sS -X POST https://api.hanzo.ai/v1/chat/completions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"zen5","messages":[{"role":"user","content":"summarise Q3"}]}'
curl -sS "https://api.hanzo.ai/v1/o11y/llm/traces?limit=20" \
-H "Authorization: Bearer $HANZO_API_KEY"The third command is a read. Nothing was instrumented and no exporter was
configured; the two calls are in the trace list because the model was served on
this side of the request. Where the check should block, your code reads the
classifier's answer and does not make the second call — which is what the input
rail did, with the decision in a flow file instead of in the caller. With
guard-proxy --upstream https://api.hanzo.ai --port 8080 in front and
OPENAI_BASE_URL=http://localhost:8080, the PII, injection and content stages
run on both calls and the caller does not change.
Evaluation separates the same way. Evaluator on your machine:
export NVIDIA_API_KEY=…
nel eval run --bench mmlu \
--model-url https://integrate.api.nvidia.com/v1 \
--model-id nvidia/nemotron-3-super-120b-a12b \
--api-key $NVIDIA_API_KEY --repeats 3 --max-problems 100
nel eval report ./eval_results/ -f markdown -o report.mdHere, for the standard benchmarks, an admission and a read:
curl -sS -X POST https://api.hanzo.ai/v1/benchmark/runs \
-H "Authorization: Bearer $HANZO_API_KEY" -H 'Content-Type: application/json' \
-d '{"model":"zen5","benchmarks":["mmlu"]}'
curl -sS "https://api.hanzo.ai/v1/benchmark/leaderboard" \
-H "Authorization: Bearer $HANZO_API_KEY"And for your own graded set, three commands from nothing to a scored run:
curl -sS -X POST https://api.hanzo.ai/v1/eval/datasets \
-H "Authorization: Bearer $HANZO_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"support-qa","description":"answers we grade each release against"}'
curl -sS -X POST https://api.hanzo.ai/v1/eval/datasets/support-qa/items \
-H "Authorization: Bearer $HANZO_API_KEY" -H 'Content-Type: application/json' \
-d '{"id":"rotate-key","input":{"question":"how do I rotate a key?"},
"expectedOutput":{"answer":"mint a new one, deploy it, revoke the old one"}}'
curl -sS -X POST https://api.hanzo.ai/v1/eval/runs \
-H "Authorization: Bearer $HANZO_API_KEY" -H 'Content-Type: application/json' \
-d '{"dataset":"support-qa","model":"zen5","runName":"rc-41",
"judge":{"model":"zen5-mini","criteria":"the answer names the right steps","name":"correctness"}}'Attempts on the benchmark plane are append-only and keyed by benchmark, item and
model, so re-queuing a run you already made is close to free, and a benchmark id
outside the catalog is a 422 naming it rather than a partial run. The eval run is
synchronous: it answers items, scored, avgScore and one row per example,
and a run where nothing scored is a 502 rather than a 200 with an empty table.
What does not carry
No rails language. Colang describes dialog as flows — what the bot may talk
about, what it says when asked to go elsewhere, how a multi-turn exchange should
proceed — and Guardrails executes them around every turn. Nothing here
interprets a flow file. Input and output decisions are Guard's fixed stages,
or a zen-guard call whose answer your code acts on; topical and conversational
control is the agent's instructions; what it may do is its tools list. A
Colang configuration doing substantive dialog control is rewritten as those
three, not ported.
No data curation or anonymization service. There is no address that
deduplicates a corpus or rewrites PII in a dataset. Curator, Anonymizer and
Safe Synthesizer remain the tools for that work and run where they ran; the
change is the provider block. Synthesis is gym synth, a binary rather than an
address: it covers Data Designer's sampler, expression and LLM columns,
validators and judge, and leaves out the persona samplers, code and SQL
validators, and MCP tool columns. zen-guard classifies a prompt at request
time and does not touch training data.
Hosted fine-tuning is a job; RL is a binary you run. POST /v1/ai/finetune/jobs
submits a training job with a base model, a method and a dataset, and
POST /v1/ai/finetune/deploy serves the result under a model name. That is
Customizer's shape, and it has no rollout loop. GRPO is
hanzoai/gym: rl: grpo in an Axolotl-shaped
config, verifiers named under trl.reward_funcs (exact_match,
numeric_match, format:, length:, http:), LoRA on Qwen3 or Llama, on
your own CUDA or Metal accelerators. It is single-node and LoRA-only; NeMo RL's
multi-node full-parameter runs stay in NeMo, and either way the output is an
adapter or checkpoint that /v1/ai/finetune/deploy or Engine serves.
No NIM to pull. Inference here is served, not shipped as a container, so
there is no image whose version you pin and no GPU you size for it. The
open-source engine behind the Zen models,
hanzoai/engine, runs the same /v1 surface
on your own hardware if you need one, but it is a project and not an address on
this API.
No red-teaming harness. Auditor is garak with NVIDIA's probe set: it
generates attacks, runs them, and scores what got through. There is no
capability that generates attacks. zen-guard is the classifier that sits on
the other side of that test. Point garak's openai.OpenAICompatible generator at
https://api.hanzo.ai/v1/ and it measures the same thing it measured before;
point it at a guard-proxy in front of that host and the difference between the
two runs is what Guard's stages caught.
Relay's runtime events have no bus. Relay emits scope lifecycle events —
started, tool called, model called, ended — that plugins subscribe to in
process. What exists here is the trace after the fact and the session stream
during an agent run at GET /v1/agent/sessions/stream. A plugin that acted on
a Relay event before the call proceeded has no hook to attach to; the decision
moves into the agent definition or into your caller.
How is this guide?
Last updated on
OpenAI
OpenAI serves models and the platform grown around them. Here that is /v1/ai (272 operations) for the models, their files and their routing, and /v1/agent (37) for the assistant that runs, with the OpenAI-shaped inference routes at the addresses your client already calls.
Replicate
Replicate runs containerised models behind one API and lets you push your own. Here that is /v1/ml (7 operations) — deploy a model under a name, then call it — with /v1/ai (272) for the hosted models and the training that produces them.