Uh oh!
There was an error while loading. Please reload this page.
Python: fix streaming when GenAI tracing replaces the raw response (#7461) - #7705
Python: fix streaming when GenAI tracing replaces the raw response (#7461)#7705madanmishra1223 wants to merge 4 commits into
Conversation
…ft#7461) Setting AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true made every streaming request fail with: AttributeError: 'AsyncStreamWrapper' object has no attribute 'parse' The Azure GenAI instrumentor replaces the SDK's raw-response wrapper with an object that *is* the event stream and exposes neither .parse() nor .headers. Both streaming paths already read .headers defensively via getattr, with a comment explaining that instrumentors wrap the response -- but then called .parse() unconditionally on that same object. Read .parse defensively too: _open_event_stream() uses .parse() when present and otherwise iterates the object directly, letting the instrumentor own the stream's lifetime. Behavior is unchanged for the normal SDK wrapper. The non-streaming .parse() call sites are left alone: the instrumentor's wrapper is stream-specific and those paths were not reported as failing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a defensive streaming path so chat streaming continues to work when telemetry/tracing replaces the SDK raw-response wrapper (removing .parse() / .headers), and introduces a regression test for that scenario.
Changes:
- Added
_open_event_stream()async context manager to safely obtain an event stream with/without.parse(). - Updated streaming code paths to use
_open_event_stream()instead of unconditionally calling.parse(). - Added a regression test that simulates an instrumented bare async stream without
.parse()/.headers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/packages/openai/agent_framework_openai/_chat_client.py | Adds _open_event_stream() and switches streaming to use it to avoid .parse() AttributeError under instrumentation. |
| python/packages/openai/tests/openai/test_openai_chat_client.py | Adds regression coverage for streaming when the raw response is replaced by a bare async stream. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Review feedback: yielding the telemetry wrapper as-is only moved the AttributeError. Verified against azure-ai-projects==2.3.0 with openai==2.53.0: with_raw_response.create() routes through the instrumented AsyncResponses.create, so AsyncStreamWrapper.stream_async_iter is the still-unparsed LegacyAPIResponse, which is not an async iterator. Iterating the wrapper fails on the first __anext__ and traced streaming stays broken. Parse that inner raw response and hand it back to the wrapper instead, so the wrapper stays in the iteration path and keeps recording telemetry while real events flow through it. The previous test patched with_raw_response.create, i.e. above the layer that does the wrapping, so it could not catch this. The test now models the observed object graph -- a wrapper with no parse/headers whose stream_async_iter is an unparsed raw response -- and fails against the previous fix. A second test covers a bare event stream with nothing to parse. Also guard with callable() rather than an is-None check, per review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
madanmishra1223
commented
Aug 18, 2026
Thanks both — Evan Mattson (@moonbox3) you were right, and the PR is updated. Verified the failure you describedI reproduced it against the real instrumentor ( With tracing genuinely active: Exactly as you said: You were also right about why the test missed it: it patched What changed
inner=getattr(raw_response, "stream_async_iter", None)
inner_parse=getattr(inner, "parse", None)
ifcallable(inner_parse):
asyncwithcast("Any", inner_parse()) asstream:
raw_response.stream_async_iter=streamyieldraw_responsereturnEnd-to-end against the real instrumentor now: The test now models that observed object graph — a wrapper with no I kept the fake rather than adding On the two Copilot comments
Checks
|
Evan Mattson (moonbox3)
commented
Aug 18, 2026
madanmishra1223 please resolve all open comments if they've been addressed |
Python Test Coverage Report •
Python Unit Test Overview
| ||||||||||||||||||||||||||||||
Test Typing Checks caught two problems in the new test helpers: - ty: the wrapper's stream_async_iter was annotated `object`, so delegating to __aiter__/__anext__ was an attribute error. Annotate it `Any`, which also makes the two `type: ignore` comments unnecessary. - zuban: `_BareEventStream` was declared inside the test function, and its own forward-referenced return annotation does not resolve there. Move it to module scope alongside the other stream fakes. Verified with the task CI runs, `poe test-typing -P openai`: mypy, pyrefly, ty, zuban and pyright all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tao Chen (TaoChenOSU)
commented
Aug 19, 2026
Hi madanmishra1223, Thanks for the contributions! Could you also create an issue in the Azure SDKs repo and link it here? |
madanmishra1223
commented
Aug 19, 2026
Thanks Tao Chen (@TaoChenOSU) — filed upstream: Azure/azure-sdk-for-python#48646 It has a standalone repro (
I called out there that reproducing needs both an OTel I also linked this PR from that issue and noted the workaround here can be dropped once it's fixed upstream. One process note: this branch is still showing |
Tao Chen (TaoChenOSU)
commented
Aug 20, 2026
Hi madanmishra1223, Thanks! Please also resolve all open comments when you can. |
madanmishra1223
commented
Aug 20, 2026
Tao Chen (@TaoChenOSU) all three review threads are resolved. Since resolving collapses them, here is what each one says, so it is readable without expanding: 1. Copilot — guard parse=getattr(raw_response, "parse", None)
ifcallable(parse): ...
inner_parse=getattr(inner, "parse", None)
ifcallable(inner_parse): ...2. Copilot — use Baseline for the package is 0 errors; 3. Evan Mattson (@moonbox3) — exercise it through the real Your point about the test was right too — it patched
The test now models that object graph and fails against my previous commit. Status: upstream issue filed and linked (Azure/azure-sdk-for-python#48646), branch up to date with |
Fixes#7461
Problem
With
AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true, every streaming request fails:The Azure GenAI instrumentor replaces the SDK's raw-response wrapper with an object that is the event stream and exposes neither
.parse()nor.headers.Both streaming paths in
_chat_client.pyalready anticipate this for headers, and say so in a comment:.headersis read throughgetattr, then.parse()is called unconditionally on that same object. So the defense is half-applied: the attribute that only degrades a nice-to-have is guarded, and the attribute that breaks the entire call is not.Fix
Read
.parsedefensively too._open_event_stream()uses.parse()when present — preserving theasync withso the socket still closes deterministically — and otherwise iterates the object directly, letting the instrumentor own the stream's lifetime.Behavior is unchanged for the normal SDK wrapper; only the instrumented case changes, from raising to streaming.
Both affected streaming call sites are updated (the
retrievecontinuation path and thecreatepath).The two non-streaming
.parse()sites are deliberately left alone: the instrumentor's wrapper is stream-specific (AsyncStreamWrapper), and non-streaming was not reported as failing. Happy to extend if maintainers prefer symmetry.Verification
Repro before the fix, using a stand-in wrapper that is async-iterable with no
.parse()/.headers:After:
Added
test_streaming_survives_instrumented_response_without_parse, which fails onmainwith the exact reportedAttributeErrorand passes with this change.Checks run locally:
pytest packages/openai/testspytest packages/core/testspyright(openai package)mypy(test file)ruff format --check/ruff checkNote
The issue is assigned to Tao Chen (@TaoChenOSU). There was no linked PR after ~2.5 weeks, so I picked it up — happy to close this if it is already in progress.