Skip to content

[None][fix] Responses: fail over-length streamed requests in-stream with context_length_exceeded - #21060

Open
JunyiXu-nv wants to merge 5 commits into
NVIDIA:mainfrom
JunyiXu-nv:dev-junyix-fix-responses-context-length-in-stream
Open

JunyiXu-nv wants to merge 5 commits into
NVIDIA:mainfrom
JunyiXu-nv:dev-junyix-fix-responses-context-length-in-stream

Conversation

@JunyiXu-nv

@JunyiXu-nv JunyiXu-nv commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

A streamed /v1/responses request whose prompt is longer than the context window is not answered the way OpenAI answers it. OpenAI opens the stream and ends it with response.failed, whose response.error.code is context_length_exceeded. Clients act on that event. The Codex CLI reads it as a full context window: it ends the turn and compacts its history before the next one. It reads an HTTP 400 as an invalid request that it does not retry or recover from.

On main the server answers such a request in one of three ways, depending on where the prompt is rejected. None of them reaches the client as a full context window.

  • The executor runs in worker processes (the default). The rejection arrives while the stream runs. guard_responses_stream sends error and response.failed, but with error.code: "server_error", and then re-raises. The re-raise aborts the connection mid-body (an incomplete chunked read). The Codex CLI keeps the code of response.failed and acts on it only when the stream ends cleanly. On a transport error it reports a stream error and retries the same request.
  • The executor runs in the server process. generate_async raises at once, and the server answers HTTP 400 with code: "context_length_exceeded".
  • Disaggregated serving. The context phase never streams, so the context worker rejects the request with its 400. The frontend relays that 400, also for a streamed request.

This change answers all three with the same stream: response.created, response.in_progress, error, then response.failed. The failed snapshot carries error.code: "context_length_exceeded" and the rejection text as error.message. Sequence numbers run from 0 to 3, and the stream ends normally.

  • classify_stream_termination recognizes an over-length RequestError by its text (the text is what crosses the executor IPC boundary) and returns the new cause context_length_exceeded. describe_stream_termination reports such a rejection by its message alone, the text the non-streamed 400 carries.
  • ResponsesStreamingProcessor.get_stream_failed_events sets error.code to context_length_exceeded for that cause and keeps server_error for every other cause. The SDK's ResponseError.code literal does not list context_length_exceeded (checked with openai 2.48 and 3.39). So the event is validated with server_error in that slot, and the real code is written into the serialized event by the same serializer as every other frame. For every other cause the frame is byte-identical to before.
  • guard_responses_stream ends the stream normally after the terminal events of an over-length rejection, instead of re-raising. Every other failure is re-raised as before.
  • rejected_responses_stream (new) streams a request that was rejected before generation: the opening pair, then the same failure events. OpenAIServer.openai_responses uses it when generate_async itself raises the rejection for a streamed request.
  • OpenAIDisaggServer._handle_exception(self, exception) keeps its one-argument signature: subclasses such as NeMo-RL's OpenAIDisaggServerAdaptor override it with that signature and delegate to super(), so it is still called, unchanged, for every failure of a wrapped request. The conversion happens in the caller, _wrap_entry_point: when the handler returns the context worker's 4xx context_length_exceeded envelope for a streamed Responses request, the route answers with the failure stream instead of that 400. An override that answers the failure itself (raises, or returns anything else) keeps its answer. The snapshot names the model the client asked for, because the frontend does not know the served name.

Unchanged:

  • A non-streamed request keeps HTTP 400 with code: "context_length_exceeded", which is how OpenAI rejects a non-streamed over-length request.
  • Chat Completions and Completions keep their current behavior, streamed or not.
  • A prompt longer than max_num_tokens with chunked prefill disabled is a different rejection ("The prompt length ... should not exceed max_num_tokens"). It keeps its current form, as on the 400 path.

This was found while serving DeepSeek-V4.1-Flash to the Codex CLI. The change does not depend on the model.

Test Coverage

CPU-only tests, in files that l0_cpu.yml already lists:

  • tests/unittest/llmapi/apps/test_context_length_error.py
    • test_a_streamed_responses_request_fails_in_stream[in_worker|in_process] drives OpenAIServer.openai_responses with an executor that rejects the prompt in a worker (the rejection travels as an ErrorResponse through the real GenerationResult and error handler) or in-process (generate_async raises). It checks the four events, their numbering, the response id, the code and message, and that the body ends without raising.
    • test_a_non_streamed_responses_request_keeps_the_400[...] and test_other_rejections_of_a_streamed_request_are_unchanged[...].
    • test_disagg_route_fails_a_streamed_responses_request_in_stream and test_disagg_route_keeps_the_400_for_other_requests[...] (both drive _wrap_entry_point).
    • test_a_subclass_handler_is_called_for_an_ordinary_failure[worker_400|internal_error], test_a_subclass_handler_is_called_for_a_streamed_over_length_request, test_a_subclass_that_answers_the_over_length_failure_itself_keeps_its_answer and test_a_subclass_that_returns_its_own_answer_keeps_it[...]: a subclass overriding _handle_exception(self, exception) with one argument, as NeMo-RL does, is called for every failure, the streamed over-length request still fails in-stream, and an override's own answer is kept. With the earlier two-argument call these fail with TypeError.
  • tests/unittest/llmapi/apps/test_responses_streaming_events.py
    • the cause and detail of an over-length RequestError, and of a ValueError with the same text, which stays internal_error;
    • the failed code for each cause, and the frame of every other cause serialized like any other frame;
    • the guard ends an over-length stream normally and still re-raises other engine failures;
    • a rejection before generation streams the same frames as one raised while the stream runs.

On a build of current main, these two files and the two disaggregated Responses test files (tests/unittest/disaggregated/test_openai_server_stream_errors.py, tests/unittest/disaggregated/test_disagg_responses_api.py) pass: 147 tests.

Also checked outside the test suite: generated-input properties over the same functions (any chunking and number of frames already sent, wrapped rejection texts, request fields with floats and non-ASCII text), with planted bugs to confirm the checks can fail. An HTTP run under uvicorn with a reader that follows the Codex CLI's stream handling reports a full context window for both in-worker and in-process rejections. On main the same reader reports a stream error (incomplete chunked read) and an HTTP 400.

PR Checklist

  • PR description clearly explains what and why.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

🤖 Generated with Claude Code

Dev Engineer Review

Streamed /v1/responses requests rejected for context length now produce a response.failed event with error.code: "context_length_exceeded" and end normally. The change covers rejection before generation, during streaming, and in disaggregated serving. Other streamed failures retain their existing handling. Non-streamed over-length requests retain HTTP 400.

No current review findings or severity counts were supplied. The author reports that 113 CPU-only tests passed across the specified Responses test files, compared with 91 on main. The author also reports generated-input checks and an HTTP run; these were outside the test suite and were not independently verified. The supplied CI comments report mixed pipeline outcomes, including failures and unstable runs.

QA Engineer Review

Two test files were modified. test_context_length_error.py covers streamed and non-streamed requests, in-process and worker rejection paths, and disaggregated routing. test_responses_streaming_events.py covers error classification, event serialization, and stream termination. Both files are listed in tests/integration/test_lists/test-db/l0_cpu.yml. Neither appears in the searched manual-QA lists. Coverage verdict: sufficient.

Per-File QA Perspective

  • tensorrt_llm/serve/openai_disagg_server.py — Verify that a streamed Responses request rejected by the disaggregated context worker receives an in-stream failure. Verify that other failures retain their existing handling.
  • tensorrt_llm/serve/openai_server.py — Verify that a pre-generation context-length rejection returns a failure stream for streamed Responses requests. Verify that non-streamed requests and other errors retain their existing handling.
  • tensorrt_llm/serve/responses_utils.py — Verify the context_length_exceeded code, terminal event sequence, and normal stream termination. Verify that other stream failures retain their existing classification and propagation.
  • tests/unittest/llmapi/apps/test_context_length_error.py — Covers in-process, worker, and disaggregated rejection paths, non-streamed behavior, and other rejection causes. Listed in tests/integration/test_lists/test-db/l0_cpu.yml; no entry appeared in the searched manual-QA lists.
  • tests/unittest/llmapi/apps/test_responses_streaming_events.py — Covers error classification, event serialization, stream termination, and propagation of other failures. Listed in tests/integration/test_lists/test-db/l0_cpu.yml; no entry appeared in the searched manual-QA lists.

…am with context_length_exceeded

A streamed /v1/responses request whose prompt exceeds the context window
now gets the stream OpenAI sends for it: response.created,
response.in_progress, error, then response.failed with error.code
context_length_exceeded, after which the stream ends normally. Clients
act on that event: the Codex CLI compacts its history on it, while it
treats an HTTP 400 as an invalid request and an aborted stream as a
transport error that it retries.

Before, the answer depended on where the executor rejected the prompt:
- in a worker process (raised while the stream runs): response.failed
  carried server_error, and the re-raise then aborted the connection;
- in this process (raised by generate_async): HTTP 400;
- disaggregated serving (the context worker's 400): HTTP 400.

Non-streamed requests keep HTTP 400 with code context_length_exceeded.
Every other stream failure keeps server_error and the re-raise; its
response.failed frame is byte-identical to before.

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

Streamed Responses requests with context-length errors now receive an in-stream response.failed event. The standard and disaggregated server paths use rejection-stream helpers. Other errors and non-streamed requests retain their existing handling.

Changes

Responses stream rejection handling

Layer / File(s) Summary
Classify and serialize stream failures
tensorrt_llm/serve/responses_utils.py, tests/unittest/llmapi/apps/test_responses_streaming_events.py
The stream utilities classify context-length errors and emit a response.failed event with the context_length_exceeded code. The stream ends normally for this rejection. Other failures retain their existing behavior. Tests cover classification, serialization, and stream termination.
Standard server handling
tensorrt_llm/serve/openai_server.py, tests/unittest/llmapi/apps/test_context_length_error.py
The standard Responses entry point returns a rejected event stream for streamed context-length errors. Tests cover streamed and non-streamed requests and other rejection behavior.
Disaggregated server handling
tensorrt_llm/serve/openai_disagg_server.py, tests/unittest/llmapi/apps/test_context_length_error.py
The disaggregated route converts recognized context-length error responses into rejected event streams for streamed Responses requests. Tests cover the route and exception-handler response handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: juney-nvidia

Merge Risk: Minimal · up to 37139

No merge-blocking behavior has been established from the available evidence.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title follows the repository template: a [None] ticket tag, a [fix] type, and a concise summary. It names the main change, which makes streamed Responses requests that exceed the context window fa…
Description check Passed The description follows the repository template. It explains the problem and the fix, gives the full behavior for the three rejection paths, and lists the unchanged behavior. It also gives a detailed …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title follows the repository template: a [None] ticket tag, a [fix] type, and a concise summary. It names the main change, which makes streamed Responses requests that exceed the context window fail in-stream with the context_length_exceeded code. The changeset matches this: it classifies over-length RequestErrors, assigns the code in the failed event, and ends the stream normally.

Full details: Description check

Explanation

The description follows the repository template. It explains the problem and the fix, gives the full behavior for the three rejection paths, and lists the unchanged behavior. It also gives a detailed test coverage section and a checked PR checklist item. The template's 'Description' and 'Test Coverage' sections are present and filled in. The checklist contains only the first item, so the other template items are not addressed. These gaps are non-critical.

Full details: Docstring Coverage

Explanation

Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 18 functions across 5 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1131001 and 37139f9.

Files selected for processing: 2
  • tensorrt_llm/serve/openai_disagg_server.py
  • tests/unittest/llmapi/apps/test_context_length_error.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80799 [ run ] triggered by Bot. Commit: bcda071 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80799 [ run ] completed with state SUCCESS. Commit: bcda071
/LLM/main/L0_MergeRequest_PR pipeline #66536 completed with status: 'UNSTABLE'

CI Report

⚠️ Multi-GPU Label Required:
Multi-GPU tests require the ci: full pre-merge approved label on this PR. Either:

  • Wait for the PR to be fully approved — the label is added automatically once approval is complete. Having unresolved open comments is fine, or
  • If needed, ask a member of NVIDIA/trt-llm-ci-approvers to add the label manually.
    Then re-trigger CI with the same bot command (no rebase needed).

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

…ses-context-length-in-stream

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>

# Conflicts:
#	tensorrt_llm/serve/openai_server.py
#	tensorrt_llm/serve/responses_utils.py
#	tests/unittest/llmapi/apps/test_responses_streaming_events.py
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80939 [ run ] triggered by Bot. Commit: 3b477d3 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80939 [ run ] completed with state SUCCESS. Commit: 3b477d3
/LLM/main/L0_MergeRequest_PR pipeline #66662 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80999 [ run ] triggered by Bot. Commit: 3b477d3 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #80999 [ run ] completed with state FAILURE. Commit: 3b477d3
/LLM/main/L0_MergeRequest_PR pipeline #66719 completed with status: 'UNSTABLE'

CI Report

⚠️ Multi-GPU Label Required:
Multi-GPU tests require the ci: full pre-merge approved label on this PR. Either:

  • Wait for the PR to be fully approved — the label is added automatically once approval is complete. Having unresolved open comments is fine, or
  • If needed, ask a member of NVIDIA/trt-llm-ci-approvers to add the label manually.
    Then re-trigger CI with the same bot command (no rebase needed).

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

…ext-length-in-stream

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82259 [ run ] triggered by Bot. Commit: 67f5d53 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82259 [ run ] completed with state FAILURE. Commit: 67f5d53
/LLM/main/L0_MergeRequest_PR pipeline #67885 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

…ext-length-in-stream

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82299 [ run ] triggered by Bot. Commit: 1131001 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82299 [ run ] completed with state FAILURE. Commit: 1131001
/LLM/main/L0_MergeRequest_PR pipeline #67921 completed with status: 'UNSTABLE'

CI Report

⚠️ Multi-GPU Label Required:
Multi-GPU tests require the ci: full pre-merge approved label on this PR. Either:

  • Wait for the PR to be fully approved — the label is added automatically once approval is complete. Having unresolved open comments is fine, or
  • If needed, ask a member of NVIDIA/trt-llm-ci-approvers to add the label manually.
    Then re-trigger CI with the same bot command (no rebase needed).

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

…xception handler for subclasses

OpenAIDisaggServer._handle_exception(self, exception) keeps the signature
it has on main. Subclasses override it with that signature (NeMo-RL's
OpenAIDisaggServerAdaptor), so the extra request argument would have made
every failed request raise TypeError there.

The in-stream answer for a streamed Responses request that the context
worker rejected for its length moves to the caller, _wrap_entry_point.
The handler, base or override, rules on the failure first, as it does for
every failure; only when it returns the worker's context_length_exceeded
envelope is that envelope re-shaped into the stream. An override that
answers the failure itself keeps its answer.

Tests drive the wrapped route instead of the handler, and a subclass
with a one-argument handler is checked for an ordinary failure and for a
streamed over-length Responses request.

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
JunyiXu-nv added a commit to JunyiXu-nv/TensorRT-LLM that referenced this pull request Nov 4, 2026
…h a verdict

A run of the skill on a clean machine, from the skill alone, could not
reach a passing cell. This adds what it missed:
- The Codex default profile's echo is measured (exact: codex-cli 0.155.1,
  12 sessions against the fake server). Conformance was BLOCKED on it. The
  contract template now asks for echo.
- The capture changes are named by PR (NVIDIA#21605, NVIDIA#21602, NVIDIA#20936), with where to
  get a wheel that matches the base and which CPU suites to run.
- The wiring snippets use absolute paths, create their task, pass --build-id
  and --matrix, and state which claims a wiring run must pass.
- The real run has a full serve command with the recorder in front, and
  profiles/model/serving/ holds complete configs for GLM-5.3-NVFP4 and
  DeepSeek-V4.1-Flash as their cells were served.
- The matrix template proposes starting thresholds, lists the units each
  threshold needs, drops keys the verdict never reads, and names cell
  directories.
- templates/full-tier-report.md: the full tier's steps and the five files it
  writes.
- Smaller additions: restricting egress, a harness's own sandbox inside a
  container, Codex over-length on main (PR NVIDIA#21060), where the GLM goldens
  came from, test and gate hygiene, and the controls receipt command.

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82476 [ run ] triggered by Bot. Commit: 37139f9 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #82476 [ run ] completed with state SUCCESS. Commit: 37139f9
/LLM/main/L0_MergeRequest_PR pipeline #68090 completed with status: 'UNSTABLE'

CI Report

⚠️ Multi-GPU Label Required:
Multi-GPU tests require the ci: full pre-merge approved label on this PR. Either:

  • Wait for the PR to be fully approved — the label is added automatically once approval is complete. Having unresolved open comments is fine, or
  • If needed, ask a member of NVIDIA/trt-llm-ci-approvers to add the label manually.
    Then re-trigger CI with the same bot command (no rebase needed).

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

Link to invocation

This branch has not been deployed

No deployments
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