Skip to content

Add Dynamo renderer transport#1287

Draft
AmeenP wants to merge 6 commits into
mainfrom
codex/dynamo-renderer-transport
Draft

Add Dynamo renderer transport#1287
AmeenP wants to merge 6 commits into
mainfrom
codex/dynamo-renderer-transport

Conversation

@AmeenP
Copy link
Copy Markdown
Collaborator

@AmeenP AmeenP commented May 5, 2026

Summary

Adds complete Dynamo renderer + TITO nvext support to verifiers in one PR.

  • adds ClientConfig.renderer_transport, defaulting to vllm, with public values vllm and dynamo
  • wires RendererClient to the standalone renderers transport API and forwards vllm / dynamo
  • pins the standalone PrimeIntellect-ai/renderers repo to renderers PR Question: Completion IDs for multi-turn conversations. #11 at 17005dd
  • teaches OpenAIChatCompletionsTokenClient to use renderer_transport="dynamo"
  • sends Dynamo TITO token-in requests through /chat/completions with nvext.token_data
  • requests nvext.extra_fields=["engine_data"] and grafts nvext.engine_data.{prompt_token_ids,completion_token_ids} onto the standard response token fields
  • keeps renderer-derived root stop_token_ids for Dynamo compatibility
  • preserves caller-provided nvext.extra_fields while adding engine_data
  • fixes prompt-id prefix matching for vf.Message input and forwards chat_template_kwargs to bridge tokenization
  • accepts current renderers RenderedTokens bridge return values

Context

This now absorbs the follow-up from #1313 so the verifiers Dynamo work is complete in this PR.

Paired stack:

  • PrimeIntellect-ai/renderers#11
  • ai-dynamo/dynamo#9509
  • prime-rl #2420 / #2446, which set renderer_transport="dynamo" for Dynamo renderer and TITO clients

Validation

  • uv run ruff check verifiers/types.py verifiers/clients/renderer_client.py verifiers/clients/openai_chat_completions_token_client.py tests/test_renderer_client.py tests/test_openai_chat_completions_token_client.py
  • uv run pytest tests/test_renderer_client.py tests/test_openai_chat_completions_token_client.py
  • pre-push hooks: ruff check, ruff format, Semgrep policy, ty (ci parity)

@AmeenP AmeenP force-pushed the codex/dynamo-renderer-transport branch from e746897 to 02256e0 Compare May 5, 2026 05:57
@AmeenP AmeenP changed the title [codex] Add Dynamo renderer transport Add Dynamo renderer transport May 5, 2026
@AmeenP AmeenP force-pushed the codex/dynamo-renderer-transport branch from f6b72e9 to f6f8224 Compare May 8, 2026 09:53
AmeenP and others added 5 commits May 15, 2026 05:54
…okenClient

    The verifiers TITO client previously only spoke vLLM's TITO surface
    (/v1/chat/completions/tokens for the final POST, /tokenize for bridge
    tokenization). Dynamo bis/dynamo-rl serves neither route, so multi-turn
    TITO against Dynamo silently degraded to MITO every turn-2+ via the
    existing fallback path.

    This commit teaches the TITO client to read ClientConfig.renderer_transport
    (same field RendererClient consults) and route accordingly:

    - prime_vllm_generate (default): unchanged - posts to
      /v1/chat/completions/tokens and uses /tokenize over HTTP.

    - dynamo_chat_nvext: bridge tokenize runs locally via the renderers
      package (zero RTTs); final POST goes to /v1/chat/completions with
      placeholder messages + nvext.token_data carrying the stitched
      prompt_ids + explicit stop_token_ids from renderer.get_stop_token_ids().
      Wire shape matches what RendererClient already produces for the
      same transport, so a Dynamo deployment validated against renderer
      mode automatically accepts TITO traffic too.

    Adds two unit tests that assert the dynamo-transport wire shape end-to-
    end via a recording client + stub renderer (no real tokenizer download).
OpenAIChatCompletionsTokenClient.get_prompt_ids' prefix-match between
the prompt_messages caller-input and the trajectory step messages was
asymmetric:

- prompt_messages went straight through normalize_for_comparison (which
  picks up vf.AssistantMessage.model_dump's exhaustive view, including
  thinking_blocks=None and other defaulted fields).
- step_messages went through to_native_prompt FIRST, which produces the
  slimmer OpenAI-format dict that omits thinking_blocks entirely.

The two normalized forms then never compared equal whenever the caller
handed the client Pydantic vf.Message types -- the form MultiTurnEnv
produces after maybe_normalize_messages -- so the prefix match always
returned None and TITO silently fell back to MITO every turn-2+.
Probe-3 and the upstream test suite both used raw dict input, so the
asymmetry only showed up under real orchestrator rollouts.

Fix: drop None-valued keys in normalize_for_comparison. Both sides
land on the same shape regardless of whether they came in as Pydantic
or as plain OpenAI dicts.

Validated end-to-end against bis-dev/5/always-continue-tito (multi-turn
TITO + Dynamo bis/dynamo-rl smoke):
  348 /v1/chat/completions, 21 SIDECAR-SKIP-TOKENIZE markers, 0 fall-
  back warnings. Same SIDECAR token-prefix appears across turns,
  confirming the engine reuses prior-turn ids verbatim.

The existing 6 unit tests (4 vanilla TITO + 2 dynamo_chat_nvext) all
still pass; their dict-shaped input always normalized to the same
shape on both sides, so the symmetric drop-None doesn't change them.
Signed-off-by: AmeenP <ameenp360@gmail.com>
Comment on lines +324 to +329
passthrough = {
k: v
for k, v in extra_body.items()
if k not in promotable and v is not None and k not in body
}
body.update(passthrough)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium clients/openai_chat_completions_token_client.py:324

When priority is present in extra_body, it gets added to both nvext["agent_hints"] (line 286) and also to the body directly via the passthrough logic (lines 324-329). Since priority is not in promotable and body only contains top-level keys, the passthrough filter allows priority through, causing the same value to appear twice in the request — once at nvext.agent_hints.priority and once at body["priority"]. This duplicates the field and may confuse the Dynamo server or cause validation errors.

Consider adding priority to the passthrough exclusion filter so it is only sent via nvext.agent_hints.

+        passthrough = {
+            k: v
+            for k, v in extra_body.items()
+            if k not in promotable and k != "priority" and v is not None and k not in body
+        }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file verifiers/clients/openai_chat_completions_token_client.py around lines 324-329:

When `priority` is present in `extra_body`, it gets added to both `nvext["agent_hints"]` (line 286) and also to the body directly via the passthrough logic (lines 324-329). Since `priority` is not in `promotable` and `body` only contains top-level keys, the passthrough filter allows `priority` through, causing the same value to appear twice in the request — once at `nvext.agent_hints.priority` and once at `body["priority"]`. This duplicates the field and may confuse the Dynamo server or cause validation errors.

Consider adding `priority` to the passthrough exclusion filter so it is only sent via `nvext.agent_hints`.

Evidence trail:
verifiers/clients/openai_chat_completions_token_client.py lines 281-329 at REVIEWED_COMMIT. Line 285 uses `extra_body.get('priority')` (not pop), leaving priority in extra_body. Lines 286-287 add it to nvext.agent_hints. Lines 324-329 passthrough filter does not exclude 'priority' (not in promotable tuple at lines 303-315, not a key in body dict built at lines 289-296), so it passes through to `body['priority']`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants