Skip to content

fix(models): propagate the OpenAI request ID on the Chat Completions path - #4243

Merged
seratch merged 1 commit into
openai:mainfrom
hsusul:fix/chatcompletions-request-id
Aug 6, 2026
Merged

fix(models): propagate the OpenAI request ID on the Chat Completions path#4243
seratch merged 1 commit into
openai:mainfrom
hsusul:fix/chatcompletions-request-id

Conversation

@hsusul

@hsusul hsusul commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

ModelResponse.request_id is always None on the Chat Completions path, so OpenAIChatCompletionsModel users cannot retrieve the x-request-id needed to debug a request with OpenAI — the exact gap #1121 reported.

#1121 asked for the request ID because it was already available on API errors but not on successful responses. #2552 closed that for the Responses path (non-streamed and HTTP streaming) and listed its intentional non-goals — a run-level last_request_id API and the websocket transport. Chat Completions was not among them; it simply was not covered. git log -S request_id -- src/agents/models/openai_chatcompletions.py returns nothing: that file has never carried a request ID.

The OpenAI SDK makes this available uniformly — openai/_response.py calls add_request_id(parsed, self.request_id) for every parsed response, and the openai._models docstring uses a chat completion (completion._request_id) as its example. The adapter simply never read it.

Affected component: src/agents/models/openai_chatcompletions.py (OpenAIChatCompletionsModel.get_response, .stream_response).

Current vs corrected behavior

before after
Runner.run(...) + OpenAIChatCompletionsModelresult.raw_responses[0].request_id None "req_..."
Runner.run_streamed(...) + OpenAIChatCompletionsModelresult.raw_responses[0].request_id None "req_..."
Responses path (unchanged) "req_..." "req_..."

Root cause

Two independent omissions behind one cause — the adapter never propagates the request ID:

  1. Non-streamed: get_response() builds ModelResponse(output=..., usage=..., response_id=None) and never reads _request_id off the ChatCompletion the SDK returned.
  2. Streamed: the terminal streamed response is synthesized locally in _fetch_response() rather than returned by the API, so it carries no HTTP metadata. Runner already reads getattr(terminal_response, "_request_id", None) generically (run_internal/run_loop.py), so nothing downstream needed changing — the header just never got attached.

Implementation

  • get_response() passes request_id=getattr(response, "_request_id", None).
  • stream_response() calls a new _attach_stream_request_id() that copies x-request-id from the stream's underlying HTTP response onto the synthesized Response. The terminal event is a model_copy() of that object and pydantic preserves the private attribute, so Runner reads it back through the existing generic path.

Why this is minimal: no public API, signature, or serialization change — ModelResponse.request_id and RunState persistence (schema 1.4) already exist and are unchanged. No downstream file is touched. Both accessors degrade to None for custom clients and test doubles that expose no HTTP response, matching the compatibility approach #2552 took for with_streaming_response.

Regression tests

tests/models/test_openai_chatcompletions.py

  • test_get_response_propagates_request_id — request ID reaches ModelResponse.request_id.
  • test_get_response_request_id_is_none_when_absent — boundary: no _request_id still yields None.

tests/models/test_openai_chatcompletions_stream.py

  • test_stream_response_propagates_request_id — terminal streamed response carries _request_id.
  • test_stream_response_without_http_response_has_no_request_id — boundary: a bare async iterator (the shape existing tests and custom clients use) still streams, with no request ID.

The two propagation tests fail on main (assert None == 'req_nonstreamed_123' / assert None == 'req_streamed_456') and pass with this change; the two boundary tests pass both before and after, pinning the no-op case.

End-to-end coverage already exists and is unchanged: test_streamed_run_exposes_request_id_on_raw_responses (added by #2552) asserts terminal _request_idresult.raw_responses[0].request_id, which is the linkage the streamed half of this fix feeds.

Execution modes covered

Non-streamed get_response and streamed stream_response, which are the two modes this adapter exposes.

Non-goals

  • LitellmModel / AnyLLMModel also leave request_id unset. Their underlying clients are not OpenAI clients and do not expose the x-request-id contract, so populating it there would be speculative rather than a fix.
  • No run-level last_request_id API, and no change to the websocket transport — both were declared out of scope by fix: #1121 expose model request IDs on raw responses #2552 and remain so.

Test plan

Run from the repository root on b47a0e4:

uv run pytest tests/models/test_openai_chatcompletions.py tests/models/test_openai_chatcompletions_stream.py -k request_id -q

Before: 2 failed, 2 passed, 117 deselected. After: 4 passed, 117 deselected.

uv run pytest tests/models/test_openai_chatcompletions.py tests/models/test_openai_chatcompletions_stream.py -q   # 121 passed
make format      # 862 files left unchanged; All checks passed!
make lint        # All checks passed!
make typecheck   # 0 errors, 0 warnings, 0 informations (849 source files)
make tests       # 6669 passed, 29 skipped; serial stage 38 passed, 5 skipped
bash .agents/skills/code-change-verification/scripts/run.sh
git diff --check # clean

No OpenAI API key, network access, or paid model call is needed for any of the above; the tests patch _fetch_response.

Issue number

Follow-up to #1121, which #2552 fixed for the Responses path only.

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

@seratch seratch added this to the 0.20.x milestone Aug 6, 2026

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Confirmed this works well with real APIs

@seratch
seratch merged commit 19f6bde into openai:main Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants