Skip to content

Reasoning

Use this page for Cosmos3 Reasoner requests through OpenAI-compatible Chat Completions and Responses APIs. These workflows require a running Reasoner model. /v1/infer is a Generator endpoint and is not used here. Refer to Deployment to select and launch a Reasoner model.

The same clients work with edge, nano, and super. Launch Edge with the selectors in Deploy Edge Models.

For stateful reasoning over successive video frames, refer to Streaming Video. Its example client is bundled in this cookbook and uses the same pinned environment and NIM_URL setting, with a flag to choose WebSocket or REST.

Prepare the Client and Verify Readiness

Install the client tooling. The runnable examples use the pinned OpenAI Python client from the uv project in the cookbook directory:

cd .agents/skills/cookbooks/cosmos3/nim
export NIM_URL=${NIM_URL:-http://localhost:8000}
curl -f "$NIM_URL/v1/health/ready"
curl -fsS "$NIM_URL/v1/metadata" | python3 -m json.tool
curl -fsS "$NIM_URL/v1/models" | python3 -m json.tool

Confirm that metadata reports model_type as reasoner and inference_endpoint as /v1/chat/completions. Health and /v1/models alone do not prove that the URL reaches the Reasoner. The NIM needs no API key on localhost, but the OpenAI client requires a non-empty placeholder such as not-used.

Discover the Served Model

Do not hard-code the served model ID (for example, Edge serves nvidia/cosmos3-edge-reasoner):

import requests
from openai import OpenAI

nim_url = "http://localhost:8000"
metadata_response = requests.get(f"{nim_url}/v1/metadata", timeout=30)
metadata_response.raise_for_status()
metadata = metadata_response.json()
if (
    metadata.get("model_type") != "reasoner"
    or metadata.get("inference_endpoint") != "/v1/chat/completions"
):
    raise RuntimeError(f"NIM_URL does not target a Reasoner runtime: {metadata}")

client = OpenAI(base_url=f"{nim_url}/v1", api_key="not-used")
models = client.models.list()
model = models.data[0].id
print(model)

Run the Task Catalog

examples/reasoner_cases.yaml defines the media, prompt, sampling, output contract, and review criteria for each case, and examples/reasoner.py sends them to the NIM.

List or inspect cases without a running endpoint. The default output is YAML, and --format json emits machine-readable output:

uv run python examples/reasoner.py --list-cases
uv run python examples/reasoner.py --describe trajectory_2d
uv run python examples/reasoner.py --describe trajectory_2d --format json

The catalog groups its cases into these categories:

Category Cases
Captioning image_caption, video_caption
Temporal localization temporal_localization, event_timeline, timestamp_query, interval_question
Embodied reasoning robotics_next_action, drive_scene_next_action, robot_planning, assisted_task_next_action
Common sense common_sense_reasoning
Spatial reasoning grounding_2d, describe_anything
Action CoT trajectory_2d, flower_trajectory_2d, driving_scene_action_cot
Physical and situation reasoning physical_plausibility, situation_understanding

Run one case (--case is a runner option, not a NIM API field):

uv run python examples/reasoner.py --case image_caption

Every video case requests 4 frames per second (FPS) sampling, and all cases read the answer from message.content. --case all sends all 18 requests sequentially and takes substantial time. Use it for deliberate catalog validation, not as a first request:

uv run python examples/reasoner.py --case all

Super BF16 Example Baseline

The catalog was validated on Super BF16 (hardware floor in the Support Matrix) with NIM_USE_DFLASH=0 and NIM_VIDEO_PRUNING_RATE=0. Unless a case records an override, it explicitly sends temperature=0.7, top_p=0.8, top_k=20, presence_penalty=0, and repetition_penalty=1. Cases also send seed=0 where the vLLM notebook sets it. The runner only warns on another variant, so do not present an Edge or Nano result as Super-validated.

Artifacts and Validation Boundaries

Each run prints the answer and writes examples/outputs/reasoner_<case>/. Validation only checks that JSON parses. Review semantics yourself.

Image Reasoning With Chat Completions

Place the media item before the text instruction:

import base64
from pathlib import Path
from openai import OpenAI

nim_url = "http://localhost:8000"
image_path = Path("../reasoner/assets/robot_153.jpg")
image_url = "data:image/jpeg;base64," + base64.b64encode(
    image_path.read_bytes()
).decode()

with OpenAI(base_url=f"{nim_url}/v1", api_key="not-used") as client:
    model = client.models.list().data[0].id
    response = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": image_url}},
                    {"type": "text", "text": "Caption the image in detail."},
                ],
            }
        ],
        max_tokens=4096,
        seed=0,
    )
print(response.choices[0].message.content)

Run the equivalent cookbook example:

uv run python examples/reasoner.py --case image_caption

For direct HTTP, send the same object to POST /v1/chat/completions and keep large data URLs in a request file, not shell arguments.

Video Reasoning With Chat Completions

Use video_url content and pass NIM request extensions through extra_body. Input videos can be at most 3,840 pixels wide and 2,160 pixels tall, so portrait 4K is also rejected. Larger videos return HTTP 422, so downscale them first:

import base64
from pathlib import Path
from openai import OpenAI

nim_url = "http://localhost:8000"
video_path = Path("../reasoner/assets/video_caption.mp4")
video_url = "data:video/mp4;base64," + base64.b64encode(
    video_path.read_bytes()
).decode()

with OpenAI(base_url=f"{nim_url}/v1", api_key="not-used") as client:
    model = client.models.list().data[0].id
    response = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "video_url", "video_url": {"url": video_url}},
                    {"type": "text", "text": "Describe the video in detail."},
                ],
            }
        ],
        max_tokens=4096,
        extra_body={"media_io_kwargs": {"video": {"fps": 4.0}}},
    )
print(response.choices[0].message.content)

Run the complete example:

uv run python examples/reasoner.py --case video_caption

Prefer data URLs. A client-side file:// URL does not reach the container.

Use the Responses API

The Responses create route is enabled unless the deployment sets NIM_DISABLE_RESPONSES_ROUTE=true. For image input, put input_image before input_text:

uv run python examples/reasoner_responses.py

Requests need a non-empty model and use the Chat Completions sampling defaults. Read the answer from response.output_text. Send store=false, as the example does, because response storage is disabled by default. Retrieval, cancellation, background responses, and previous_response_id need response storage. Use Chat Completions for video.

Final Answers, Instructions, and Tool Calls

Both APIs map a developer turn to a system instruction. Chat Completions supports OpenAI tool definitions with automatic tool choice in the Hermes format. It requires top_logprobs to be an integer or null. If logprobs=true and top_logprobs is omitted, one is requested. Some prompts ask for literal <think> tags, which appear in message.content as ordinary text.

Reasoner DFlash

Nano and Super Reasoner use their bundled DFlash drafts by default. Set NIM_USE_DFLASH=0 to run target-only. Edge has no DFlash and rejects NIM_USE_DFLASH=1. Requests do not change. Refer to Speculative Decoding and Reasoner Checkpoint.

Advanced Sampling and Request Extensions

The service supplies these values when omitted:

Field Current Default Current Validation
temperature 0.7 [0, 2]
top_p 0.8 (0, 1]
top_k 20 -1 or integer >= 1

The service does not inject a default max_tokens. Send max_tokens explicitly.

top_k, media_io_kwargs, structured_outputs, guided-output fields, and nvext are request extensions. With the OpenAI client, put them in extra_body. Set video sampling per request with media_io_kwargs. The example requests 4 FPS. Video-token pruning is enabled by default at a rate of 0.6, using vidcom2 unless another method is selected. Set NIM_VIDEO_PRUNING_RATE=0 or NIM_VIDEO_PRUNING_RATE=none to disable it. Media limits and pruning settings are under Reasoner Configuration.

Text-Only Requests

This guide covers image and video input only. Validate text-only workflows yourself.

Structured Output

Prefer the standard OpenAI response_format JSON-schema shape. The service enforces it with guided decoding:

{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "temporal_events",
      "schema": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "start": {"type": "number"},
            "end": {"type": "number"},
            "caption": {"type": "string"}
          },
          "required": ["start", "end", "caption"],
          "additionalProperties": false
        }
      }
    }
  }
}

Responses requests use the native text.format shape instead:

{
  "text": {
    "format": {
      "type": "json_schema",
      "name": "temporal_events",
      "schema": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "start": {"type": "number"},
            "end": {"type": "number"},
            "caption": {"type": "string"}
          },
          "required": ["start", "end", "caption"],
          "additionalProperties": false
        }
      },
      "strict": true
    }
  }
}

By default the catalog extracts the first JSON value from message.content. Add --guided-output to use JSON Schema enforcement. Do not recover structured results with a regular expression over prose or Markdown fences.

Prompting Patterns

All task families use the same Chat Completions endpoint. Task names do not select separate routes. The prompt intent for each task is as follows:

Task Prompt Intent
Captioning and VQA Describe entities, actions, environment, or answer a focused question
Temporal localization Return timestamps or intervals for a named event
Physical plausibility Judge whether observed dynamics are physically plausible and state observable evidence
Planning and next action Propose the next safe action from the current scene and task
Situation understanding Summarize agents, interactions, risks, and likely near-term evolution
2D grounding Return normalized coordinates for named objects or regions
Action trajectories Return ordered normalized points or poses for a requested path

For 2D grounding and trajectory points, each axis is normalized to [0,1000], with the origin at the upper-left, X increasing rightward, and Y increasing downward. Convert a validated point to pixels with:

pixel_x = normalized_x / 1000 * image_width
pixel_y = normalized_y / 1000 * image_height

A Reasoner next-action answer or 2D trajectory is text/JSON describing a semantic proposal in visual coordinates. It is not a domain-specific Generator Action tensor and must not be sent directly to a robot. Refer to Action Domains and Representations.

The catalog reuses the exact user prompts from the vLLM Reasoner notebook. Answers in the Reasoner Prompt Guide come from another run and are not golden NIM output.

Errors

Reasoner requests commonly fail as follows:

Status/Symptom Meaning Action
Chat Completions or Responses route 404 NIM_URL reaches Generator, Responses is disabled, or the route is absent Inspect /v1/metadata; start Reasoner or check NIM_DISABLE_RESPONSES_ROUTE
HTTP 422 Media validation or preprocessing failed Check the data URL, media ordering, and media limits

Refer to Reasoner Troubleshooting for other errors, and check the live schema before depending on additional fields. To serve a local or Hugging Face Reasoner checkpoint, refer to Bring Your Own Checkpoint.