fix(models): propagate the OpenAI request ID on the Chat Completions path - #4243
Merged
Merged
Conversation
seratch
approved these changes
Aug 6, 2026
seratch
left a comment
Member
There was a problem hiding this comment.
Confirmed this works well with real APIs
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
ModelResponse.request_idis alwaysNoneon the Chat Completions path, soOpenAIChatCompletionsModelusers cannot retrieve thex-request-idneeded 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_idAPI 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.pyreturns nothing: that file has never carried a request ID.The OpenAI SDK makes this available uniformly —
openai/_response.pycallsadd_request_id(parsed, self.request_id)for every parsed response, and theopenai._modelsdocstring 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
Runner.run(...)+OpenAIChatCompletionsModel→result.raw_responses[0].request_idNone"req_..."Runner.run_streamed(...)+OpenAIChatCompletionsModel→result.raw_responses[0].request_idNone"req_...""req_...""req_..."Root cause
Two independent omissions behind one cause — the adapter never propagates the request ID:
get_response()buildsModelResponse(output=..., usage=..., response_id=None)and never reads_request_idoff theChatCompletionthe SDK returned._fetch_response()rather than returned by the API, so it carries no HTTP metadata.Runneralready readsgetattr(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()passesrequest_id=getattr(response, "_request_id", None).stream_response()calls a new_attach_stream_request_id()that copiesx-request-idfrom the stream's underlying HTTP response onto the synthesizedResponse. The terminal event is amodel_copy()of that object and pydantic preserves the private attribute, soRunnerreads it back through the existing generic path.Why this is minimal: no public API, signature, or serialization change —
ModelResponse.request_idandRunStatepersistence (schema 1.4) already exist and are unchanged. No downstream file is touched. Both accessors degrade toNonefor custom clients and test doubles that expose no HTTP response, matching the compatibility approach #2552 took forwith_streaming_response.Regression tests
tests/models/test_openai_chatcompletions.pytest_get_response_propagates_request_id— request ID reachesModelResponse.request_id.test_get_response_request_id_is_none_when_absent— boundary: no_request_idstill yieldsNone.tests/models/test_openai_chatcompletions_stream.pytest_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_id→result.raw_responses[0].request_id, which is the linkage the streamed half of this fix feeds.Execution modes covered
Non-streamed
get_responseand streamedstream_response, which are the two modes this adapter exposes.Non-goals
LitellmModel/AnyLLMModelalso leaverequest_idunset. Their underlying clients are not OpenAI clients and do not expose thex-request-idcontract, so populating it there would be speculative rather than a fix.last_request_idAPI, 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:Before:
2 failed, 2 passed, 117 deselected. After:4 passed, 117 deselected.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
.agents/skills/code-change-verification/scripts/run.sh/reviewbefore submitting this PR