nemo_gym.base_responses_api_model
nemo_gym.base_responses_api_model
Model server base classes and per-rollout model-call capture.
Every Gym model server derives from SimpleResponsesAPIModel, which wires the three model
dialects (/v1/responses, /v1/chat/completions, /v1/messages) and installs the model-call capture
middleware.
Capture is opt-in, off by default. A pure-ASGI middleware records correlated /v1/responses, /v1/chat/completions, and /v1/messages exchanges — including failed calls — into a per-rollout CaptureStore, forwarding bytes downstream unchanged so it composes with streaming (SSE) responses. Best-effort; never alters the response. Correlation is carried by a /ng-rollout/<rollout_id>/v1/… base_url prefix, which is stripped before routing.
Module Contents
Classes
Functions
Data
API
Bases: BaseServer
Bases: BaseRunServerInstanceConfig
Append-only, rollout-keyed JSONL sink for model exchanges.
Append one exchange and fsync (durable across a killed box).
flock serializes appends across worker processes (a model server may run with
num_workers > 1, where the in-process lock can’t coordinate); the in-process lock
serializes threads. This does blocking file IO + fsync, so callers run it off the event
loop (the capture middleware offloads it via asyncio.to_thread).
Bases: BaseModel
Run-wide model-call capture settings from Gym’s global config.
Bases: BaseModel
Observability record derived from one captured model-server exchange.
Bases: BaseResponsesAPIModel, SimpleServer
Default Anthropic Messages <-> Responses mapping shared by every Gym model server.
Translates the inbound Anthropic Messages request to the Responses API, delegates to this
server’s own responses() (so it reuses whatever backend the server has), and maps the
result back to an Anthropic Messages response. When the client requested stream: true
(the Claude Code CLI always does), the complete response is re-emitted as a synthesized
Anthropic SSE event stream. Servers may override this for native Messages handling.
Default /v1/responses entrypoint shared by every Gym model server.
A plain JSON request validates strictly against
NeMoGymResponseCreateParamsNonStreaming and delegates to this server’s own
responses(), preserving the historical non-streaming behavior. When the client
requests stream: true (blackbox Responses-over-SSE harnesses like the Codex CLI
always do), the request is first sanitized from the streaming wire dialect (extra
bookkeeping fields, namespace tool specs — see nemo_gym.responses_streaming),
delegated to the same responses(), and the complete response is re-emitted as a
synthesized Responses SSE event stream. A responses() failure on this path is turned
into a terminal response.failed event rather than an HTTP 500 (bad-request validation
still fails eagerly, before the stream is committed).
Pure-ASGI per-rollout capture.
Always strips an optional /ng-rollout/<id> path prefix before routing (used as the capture
key) so the prefix is a stable routing feature independent of capture.
When store is set it buffers the request body and a copy of the response while forwarding both
downstream unchanged, so it composes with streaming (SSE) responses — it never consumes or rewraps
the stream. SSE chunks are forwarded immediately except for the terminal event, which is released
after the capture is durable. Every chunk is also buffered for post-hoc reassembly, so a very long
stream is held in memory until it completes. When store is None (capture disabled) it strips the
prefix and forwards only.
Cache hit/miss + cached-token count, from usage cache fields (OpenAI / Anthropic).
Normalized error_category for an exception raised while calling the model.
Normalized error_category from an HTTP status (None when < 400).
Parse an SSE byte stream into its JSON data: payloads (best-effort; non-JSON skipped).
Rebuild a complete Anthropic Messages response from its streamed events.
Rebuild a Chat Completions response from streamed chunks.
Rebuild a Responses API response: the terminal envelope carries the full response object.
Best-effort: reassemble a final response object from a streamed (SSE) body, by dialect.
Append one exchange (success or failure). Best-effort: never raises.
Structured tool calls (name, arguments, call_id) and reasoning text, across all three shapes.
Validate a /v1/responses body dict, surfacing failures as FastAPI’s standard 422.
Aggregate model-call metrics for one rollout id.
Aggregate token and latency values from model-call records.
Map one captured exchange and its transport metadata into an observability record.
Remove stale per-rollout capture files for these records before dispatch.
Capture files are keyed by a deterministic rollout id (task-rollout-attempt), so without this a fresh run or a kill-shaped retry would append onto the previous attempt’s capture for the same id. The caller passes only rows about to be dispatched, after assigning any retry suffix.
Normalize token totals across Responses, Chat Completions, and Anthropic Messages usage.
For native Anthropic /v1/messages with prompt caching, input_tokens is only the uncached
remainder, so cache-read + cache-creation tokens are folded into tokens_in to reflect the true
prompt size (and cache-creation is surfaced separately as cache_creation_tokens). OpenAI /
Responses usage already includes cached tokens in input_tokens / prompt_tokens (where
cached_tokens is a subset), so it is left untouched — no double counting.
tokens_in is a prompt-size metric, not a cost proxy: providers price cache-read (~0.1x) and
cache-creation (~1.25x) differently from base input, so cost-accurate consumers should weight
cached_tokens and cache_creation_tokens separately rather than summing tokens_in.
Install model-call capture middleware.
Always installed so the /ng-rollout/<id> correlation prefix is stripped before routing
regardless of whether capture is enabled (otherwise a default gym eval would 404 on every
prefixed model call). When capture is enabled the middleware additionally records each observed
call’s request + response into a rollout-keyed CaptureStore while forwarding bytes downstream
unchanged (non-terminal SSE chunks are forwarded as they arrive; the terminal event follows the
durable capture write).
Build a CaptureStore when observability is enabled; otherwise None.
Per-rollout model-call capture id from a run-request’s task/rollout indices.
Reads the canonical row keys (_ng_task_index / _ng_rollout_index) that
rollout_collection ships to an agent’s /run. When a resume re-dispatch attempt is present
(_ng_attempt_index > 0), an -a<n> suffix is appended so a retry’s captured model calls
stay separable from the prior attempt; the first attempt (0) keeps the bare <task>-<rollout>
key for backward compatibility.
Attach captured model-call observability data to a rollout record in place.
Keyed by the rollout id derived from the record’s task/rollout/attempt indices, so the attached
shape is identical for every agent harness. Adds
ng_model_call_capture = {rollout_id, metrics, calls} where calls are derived observability
records. Raw request and response payloads remain in the capture store unless include_payloads
is true. No-op when no capture exists. The harness output and reward are not modified.
Return the single run-wide capture directory when capture is enabled.
Read captured exchanges in durable append order.