Repository navigation
feat: add inference_provider server for hosted inference providers - #1286
Merged
Merged
Conversation
Extract the Responses <-> Chat Completions converter from vllm_model into a shared module (nemo_gym/responses_converter.py) and introduce a new chat_completions_model server for eval workloads against any provider that exposes an OpenAI-compatible /v1/chat/completions endpoint. Includes convenience configs for: Fireworks, Together.ai, OpenRouter, DeepInfra, Nebius, Friendli, Baseten, HF Inference, and Gemini. Closes #1285 Ref #1147 Signed-off-by: Chris Wing <cwing@nvidia.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
- Exclude nemo_gym/responses_converter.py from core unit test coverage (tested by server-level tests, same as before extraction from vllm_model) - Add pragma: allowlist secret to test dummy API key Signed-off-by: Chris Wing <cwing@nvidia.com> Co-authored-by: Cursor <cursoragent@cursor.com>
4 tasks
After extracting ResponsesConverter to nemo_gym/responses_converter.py, the uuid4 monkeypatch must target the new module path. Signed-off-by: Chris Wing <cwing@nvidia.com> Co-authored-by: Cursor <cursoragent@cursor.com>
ananthsub
previously approved these changes
May 29, 2026
2 tasks
Contributor
|
I think the naming is pretty confusing, because vllm and openai also support chat completions endpoint. Any thought to rename this? |
Contributor
|
It also seems to clash with responses_api_model/chat_completions_model -- feels strange |
kajalj22
previously approved these changes
May 29, 2026
Names the server by use case (hosted inference providers) rather than the API endpoint shared by all model servers. Signed-off-by: Chris Wing <cwing@nvidia.com>
Keep both coverage omit entries: responses_converter.py (PR) and environments/*/prepare.py (main). Signed-off-by: Chris Wing <cwing@nvidia.com>
Merge with main dropped the pydantic Field import while keeping default_headers on VLLMModelConfig. Signed-off-by: Chris Wing <cwing@nvidia.com>
Contributor
Author
kajalj22
previously approved these changes
Jun 5, 2026
…1289) ## Summary - Add `inference-providers.mdx` — docs page for the `inference_provider` server covering setup, config reference, usage example - Rewrite `model-server/index.mdx` — rename to "Configure Model", simplify intro to focus on Responses API middleware story, add cloud/self-hosted/training badges, remove implementation details ## Notes - Usage example follows the `mcqa` environment chosen in the updated quickstart (#1283) - Depends on #1286 (which adds the `inference_provider` server code and provider configs) ## Test plan - [ ] `fern docs dev` — verify both pages render correctly - [ ] Tabs component renders on inference-providers page - [ ] Badge components render on index page - [ ] All internal links resolve --------- Signed-off-by: Chris Wing <cwing@nvidia.com> Signed-off-by: Lawrence Lane <llane@nvidia.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Lawrence Lane <llane@nvidia.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
🌿 Preview your docs: https://nvidia-preview-cwing-chat-completions-model.docs.buildwithfern.com/nemo/gym Here are the markdown pages you've updated: |
Signed-off-by: Lin Jia <linj@nvidia.com> # Conflicts: # fern/versions/latest/pages/model-server/vllm.mdx
This was referenced Jun 22, 2026
1 task
- Make the model-server index cards consistent on a cloud vs self-hosted axis, with gym-managed/training as secondary qualifiers; add the missing Local vLLM Proxy card and sharpen the Inference Providers and vLLM blurbs - Drop the redundant policy_* note from the inference providers config reference (the env.yaml interpolation convention is repo-wide and documented centrally) - Fix Local vLLM / Local vLLM Proxy nav ordering Signed-off-by: Chris Wing <cwing@nvidia.com>
ffrujeri
added a commit
that referenced
this pull request
Jun 24, 2026
…se Gym… (#1627) # What does this PR do? [Design note](#1620 (comment)) Makes **every NeMo Gym model server speak the Anthropic Messages API** by adding a `POST /v1/messages` route to the base `SimpleResponsesAPIModel`, with a default implementation that maps Anthropic Messages ↔ Responses around the server's own `responses()`. External harnesses that require an Anthropic endpoint — notably `claude_code_agent` (the Claude Code CLI only talks `/v1/messages`) — can now run against **any** Gym backend (vLLM, OpenAI, an inference provider), not just Anthropic's API. The Messages ↔ Responses mapping lives in one shared, transport-free, SDK-free converter, `nemo_gym/anthropic_converter.py`, used in both directions and shared with the egress `anthropic_model` provider (#1146/#1546). See `DESIGN-NOTE.md` for rationale. # Why Gym standardizes on the Responses API, but real blackbox agents speak other protocols. Rather than a bespoke per-agent bridge or a separate proxy server, the Messages spoke becomes a base-class capability: implement `responses()` once and get `/v1/messages` for free. This is the model-proxy layer the CustomAgent EPIC (#1042) assumes — its sidecar "path-preserving forwards" `/v1/messages` to these routes. # Key design points - **Route on the base class, not a separate proxy.** Every model server inherits `/v1/messages`; no extra process or network hop. (An earlier `anthropic_messages_proxy` + `SimpleMessagesAPIModel` iteration was removed in favor of this.) - **Delegates to the server's own `responses()`** — reuses whatever backend the server has; a small `inspect`-based dispatch handles both existing `responses()` signatures. - **Synthesized SSE.** Backends are non-streaming via Responses; the converter fabricates the Anthropic SSE event stream from the complete response (the Claude CLI requires streaming). - **Shared bidirectional converter**, transport-free and **no `anthropic` SDK** (local shapes over Gym's aiohttp). Fail-loud (`NotImplementedError`) on unsupported content. - **Eval-first.** Token-ids live only at the Responses hop and cannot ride the Anthropic SSE response; an RL path would side-channel them, not thread them through Messages. # Issues Closes #1620 (*"Add a default `/v1/messages` (Anthropic Messages) route to the base Gym model server"*). Closes #1566 Relationship to adjacent work: - **#1620** — this PR is its implementation: the `/v1/messages` route + default Messages ↔ Responses mapping on `SimpleResponsesAPIModel`, the shared converter, and the `claude_code_agent` end-to-end run satisfy its acceptance criteria. - **#1146 / #1546** (egress `anthropic_model`) — inverse direction; shares the same converter. - **#1286** (`responses_converter`) — sibling Chat-Completions ↔ Responses converter; same pattern. - **EPIC #1042** (CustomAgent) — its sidecar model proxy "path-preserving forwards" `/v1/messages` to the routes this PR adds. # Usage Every model server now answers Anthropic Messages directly — this is config-independent and is the core of the change. Against any running Gym model server: ```bash curl -s http://127.0.0.1:<model_server_port>/v1/messages -H 'content-type: application/json' -d '{ "model": "x", "max_tokens": 64, "messages": [{"role": "user", "content": "hi"}] }' ``` `claude_code_agent` reaches any Gym model server by setting its `model_server` ref — the harness resolves `ANTHROPIC_BASE_URL` from it and the CLI appends `/v1/messages` (see `responses_api_agents/claude_code_agent/app.py:_resolve_base_url`). The end-to-end run in **Testing** below used this path against a local vLLM. The verified `reasoning_gym` Claude Code config keeps its direct `anthropic_base_url` default; targeting a Gym model server is an opt-in `model_server` ref. # Additional information - **Changed:** `nemo_gym/base_responses_api_model.py` (new `/v1/messages` route + default `messages()`); **added** `nemo_gym/anthropic_converter.py` (shared bidirectional converter); `responses_api_agents/claude_code_agent/README.md` (documents the model-server path). No change to the verified `reasoning_gym_claude_code_agent.yaml` — targeting a Gym model server is an opt-in `model_server` ref on the agent. - **Converter surface:** `responses_to_anthropic` / `anthropic_to_responses` (egress, shared with #1546); `anthropic_request_to_responses` / `responses_to_anthropic_response` / `anthropic_response_to_sse` (ingress). Supports text, base64 images, function tools, tool_use/tool_result, thinking, stop-reason. Anthropic-API egress *policy* stays in the `anthropic_model` server, not the converter. # Testing - `tests/unit_tests/test_anthropic_converter.py` (ingress + round-trip) and `…_egress.py` (egress) — **100%** converter coverage. - `tests/unit_tests/test_responses_api_model_messages.py` — base-class `/v1/messages` for both `responses()` signatures + SSE framing. - Full unit suite green (284); `openai_model` tests pass unchanged. - **Live e2e:** `claude_code_agent → policy_model (vLLM) /v1/messages` on reasoning_gym — `finished_naturally=1`, 1 turn, ~5.5k tokens, 0 failures (reward reflects the small model's answer, not the plumbing). - Verified: `ruff check`, `ruff format --check`, `pre-commit`. # Open questions - Versioning/regression suite for the converters (OpenAI/Anthropic bump frequently). - Audit/align the existing Chat-Completions ↔ Responses tests. - Confirm fail-loud vs drop policy for items absent in a target schema (e.g. computer-use). --------- Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com> Signed-off-by: Lin Jia <linj@nvidia.com> Co-authored-by: Lin Jia <linj@nvidia.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ananthsub
previously approved these changes
Jun 25, 2026
ananthsub
left a comment
Contributor
There was a problem hiding this comment.
some endpoints need to be updated, along with another inline comment, but otherwise this lgtm
ritaneves
pushed a commit
that referenced
this pull request
Jun 25, 2026
…se Gym… (#1627) # What does this PR do? [Design note](#1620 (comment)) Makes **every NeMo Gym model server speak the Anthropic Messages API** by adding a `POST /v1/messages` route to the base `SimpleResponsesAPIModel`, with a default implementation that maps Anthropic Messages ↔ Responses around the server's own `responses()`. External harnesses that require an Anthropic endpoint — notably `claude_code_agent` (the Claude Code CLI only talks `/v1/messages`) — can now run against **any** Gym backend (vLLM, OpenAI, an inference provider), not just Anthropic's API. The Messages ↔ Responses mapping lives in one shared, transport-free, SDK-free converter, `nemo_gym/anthropic_converter.py`, used in both directions and shared with the egress `anthropic_model` provider (#1146/#1546). See `DESIGN-NOTE.md` for rationale. # Why Gym standardizes on the Responses API, but real blackbox agents speak other protocols. Rather than a bespoke per-agent bridge or a separate proxy server, the Messages spoke becomes a base-class capability: implement `responses()` once and get `/v1/messages` for free. This is the model-proxy layer the CustomAgent EPIC (#1042) assumes — its sidecar "path-preserving forwards" `/v1/messages` to these routes. # Key design points - **Route on the base class, not a separate proxy.** Every model server inherits `/v1/messages`; no extra process or network hop. (An earlier `anthropic_messages_proxy` + `SimpleMessagesAPIModel` iteration was removed in favor of this.) - **Delegates to the server's own `responses()`** — reuses whatever backend the server has; a small `inspect`-based dispatch handles both existing `responses()` signatures. - **Synthesized SSE.** Backends are non-streaming via Responses; the converter fabricates the Anthropic SSE event stream from the complete response (the Claude CLI requires streaming). - **Shared bidirectional converter**, transport-free and **no `anthropic` SDK** (local shapes over Gym's aiohttp). Fail-loud (`NotImplementedError`) on unsupported content. - **Eval-first.** Token-ids live only at the Responses hop and cannot ride the Anthropic SSE response; an RL path would side-channel them, not thread them through Messages. # Issues Closes #1620 (*"Add a default `/v1/messages` (Anthropic Messages) route to the base Gym model server"*). Closes #1566 Relationship to adjacent work: - **#1620** — this PR is its implementation: the `/v1/messages` route + default Messages ↔ Responses mapping on `SimpleResponsesAPIModel`, the shared converter, and the `claude_code_agent` end-to-end run satisfy its acceptance criteria. - **#1146 / #1546** (egress `anthropic_model`) — inverse direction; shares the same converter. - **#1286** (`responses_converter`) — sibling Chat-Completions ↔ Responses converter; same pattern. - **EPIC #1042** (CustomAgent) — its sidecar model proxy "path-preserving forwards" `/v1/messages` to the routes this PR adds. # Usage Every model server now answers Anthropic Messages directly — this is config-independent and is the core of the change. Against any running Gym model server: ```bash curl -s http://127.0.0.1:<model_server_port>/v1/messages -H 'content-type: application/json' -d '{ "model": "x", "max_tokens": 64, "messages": [{"role": "user", "content": "hi"}] }' ``` `claude_code_agent` reaches any Gym model server by setting its `model_server` ref — the harness resolves `ANTHROPIC_BASE_URL` from it and the CLI appends `/v1/messages` (see `responses_api_agents/claude_code_agent/app.py:_resolve_base_url`). The end-to-end run in **Testing** below used this path against a local vLLM. The verified `reasoning_gym` Claude Code config keeps its direct `anthropic_base_url` default; targeting a Gym model server is an opt-in `model_server` ref. # Additional information - **Changed:** `nemo_gym/base_responses_api_model.py` (new `/v1/messages` route + default `messages()`); **added** `nemo_gym/anthropic_converter.py` (shared bidirectional converter); `responses_api_agents/claude_code_agent/README.md` (documents the model-server path). No change to the verified `reasoning_gym_claude_code_agent.yaml` — targeting a Gym model server is an opt-in `model_server` ref on the agent. - **Converter surface:** `responses_to_anthropic` / `anthropic_to_responses` (egress, shared with #1546); `anthropic_request_to_responses` / `responses_to_anthropic_response` / `anthropic_response_to_sse` (ingress). Supports text, base64 images, function tools, tool_use/tool_result, thinking, stop-reason. Anthropic-API egress *policy* stays in the `anthropic_model` server, not the converter. # Testing - `tests/unit_tests/test_anthropic_converter.py` (ingress + round-trip) and `…_egress.py` (egress) — **100%** converter coverage. - `tests/unit_tests/test_responses_api_model_messages.py` — base-class `/v1/messages` for both `responses()` signatures + SSE framing. - Full unit suite green (284); `openai_model` tests pass unchanged. - **Live e2e:** `claude_code_agent → policy_model (vLLM) /v1/messages` on reasoning_gym — `finished_naturally=1`, 1 turn, ~5.5k tokens, 0 failures (reward reflects the small model's answer, not the plumbing). - Verified: `ruff check`, `ruff format --check`, `pre-commit`. # Open questions - Versioning/regression suite for the converters (OpenAI/Anthropic bump frequently). - Audit/align the existing Chat-Completions ↔ Responses tests. - Confirm fail-loud vs drop policy for items absent in a target schema (e.g. computer-use). --------- Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com> Signed-off-by: Lin Jia <linj@nvidia.com> Co-authored-by: Lin Jia <linj@nvidia.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Rita Fernandes Neves <rfernandesne@nvidia.com>
Address PR review: `"tool_choice" in body` always evaluated False on the pydantic Body model, forcing tool_choice to "auto" even when callers passed an explicit value. Use `body.tool_choice is not None` in both inference_provider and vllm_model, and add regression tests. Also update Baseten, Nebius, and Friendli base URLs to their current OpenAI-compatible endpoints. Signed-off-by: Chris Wing <cwing@nvidia.com>
kajalj22
self-requested a review
June 26, 2026 04:43
kajalj22
approved these changes
Jun 26, 2026
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
vllm_modelinto a shared module (nemo_gym/responses_converter.py) so it can be reused without pulling in vLLM-specific logicchat_completions_modelserver for eval workloads against any provider that exposes an OpenAI-compatible/v1/chat/completionsendpointMotivation
Many users want to evaluate benchmarks against models hosted on inference providers without self-hosting vLLM. The existing
vllm_modelworks but carries vLLM-specific baggage (token ID support,/tokenizecalls,chat_template_kwargs) that is irrelevant and potentially error-prone for hosted providers.Note
(tested by server-level tests, same as before extraction from vllm_model)
Test plan
responses_api_models/chat_completions_model/tests/)vllm_modeltests pass (backward-compatible re-exports verified)openai_modeltests passCloses #1285
Ref #1147