Skip to content

[openai] No side effects when streaming; stay resilient to edge cases with parse(custom_type)#278

Open
lmolkova wants to merge 7 commits into
open-telemetry:mainfrom
lmolkova:no-side-effects-when-streaming
Open

[openai] No side effects when streaming; stay resilient to edge cases with parse(custom_type)#278
lmolkova wants to merge 7 commits into
open-telemetry:mainfrom
lmolkova:no-side-effects-when-streaming

Conversation

@lmolkova

@lmolkova lmolkova commented Jul 16, 2026

Copy link
Copy Markdown
Member

Replaces #147

Today we call parse on legacy streams introducing side-effects (changing stream type for end users). This leads to issues like #46 (not having methods / properties that stream has without instrumentation being applied).

This PR uses different approach:

  • Wrap with_raw_response streaming transparently - parse deferred until/if user calls it
  • Finalize the span from an httpx close/aclose fallback for callers that never parse. We won't record content / response attributes, but still end the span.
  • Tolerate caller-defined chunk/event types: skip chunks with unknown type

Merges the two +-prefixed openai changelog fragments into a single
278.fixed fragment so the changelog CI check passes (it requires a
fragment named <PR_NUMBER>.<type> and rejects the + orphan names).

Assisted-by: Claude Opus 4.8
@lmolkova
lmolkova force-pushed the no-side-effects-when-streaming branch from f7b478a to 17f8d86 Compare July 16, 2026 06:44
@lmolkova lmolkova changed the title No side effects when streaming; stay resilient to edge cases with parse(custom_type) [openai] No side effects when streaming; stay resilient to edge cases with parse(custom_type) Jul 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the OpenAI GenAI instrumentation’s handling of with_raw_response.create(stream=True) by avoiding eager parse() side effects, keeping raw-response metadata accessible (e.g., headers, request_id), and ensuring spans are finalized even when callers drain the underlying http_response without parsing.

Changes:

  • Introduces a transparent raw-response streaming proxy that defers parse() and finalizes spans via http_response.close/aclose fallback when needed.
  • Updates chat and Responses API wrappers/patching to avoid stream-type side effects and to tolerate caller-defined parse(to=...) types without breaking iteration.
  • Adds unit + VCR-backed tests for sync/async raw-response streaming, custom parse targets, and “read without parse” span finalization.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_responses.py Adds coverage for Responses API with_raw_response streaming (metadata access, unknown event type, span closure).
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_proxy.py Adds focused unit tests for RawResponseStreamProxy behavior (memoization, non-stream passthrough, close fallback).
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_chat_completions.py Adds tests for chat raw-response parse(to=...) custom types and streaming custom/unknown chunk types + read-without-parse.
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_async_responses.py Async equivalents for Responses API with_raw_response streaming tests (including unknown event type).
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_async_chat_completions.py Async equivalents for chat raw-response custom parse + unknown chunk + read-without-parse.
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/response_wrappers.py Makes Responses stream telemetry resilient to unknown event shapes; integrates shared wrap_result path.
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/response_extractors.py Adjusts extraction to handle raw responses by parsing on non-streaming paths.
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/patch.py Stops eagerly parsing streaming raw responses; routes streaming through wrap_result.
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/patch_responses.py Aligns Responses patching with lazy/raw-response-safe streaming behavior and avoids double-invocation under Responses.stream().
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/chat_wrappers.py Makes chat stream telemetry resilient to unknown chunk shapes; integrates shared wrap_result path.
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/_raw_response.py Adds RawResponseStreamProxy and StreamResultFactory to proxy raw streaming results and finalize spans on close.
instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/+openai-raw-response-streaming-close.fixed Changelog fragment for span finalization when draining via underlying http_response without parse().
instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/+openai-raw-response-streaming-attrs.fixed Changelog fragment for preserving raw-response metadata and custom parse(to=...) resilience.
AGENTS.md Documents new guidance about preserving SDK return contract / avoiding side effects.
.github/instructions/instrumentation.instructions.md Adds reviewer guidance to flag SDK-side-effect and return-contract changes.

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Jul 16, 2026

Copy link
Copy Markdown

Pull request dashboard status

  • Waiting on: Reviewers
  • Next step: Review the latest changes.

lmolkova added 2 commits July 15, 2026 23:56
When a raw-response stream is parsed into a caller-defined type, the
unknown-shape debug log fired once per chunk/event, spamming logs on long
streams. Guard it with an explicitly-initialized per-stream flag so it
logs at most once per stream instance (chat chunks and response events).

Addresses Copilot review feedback on PR open-telemetry#278.

Assisted-by: Claude Opus 4.8
A wrapt.ObjectProxy preserves isinstance and __class__ (only type()
identity differs), so it satisfies the 'don't change the returned type'
rule and reconciles it with the 'keep the wrapper transparent' bullet.
Keeps the parallel reviewer instructions in sync.

Assisted-by: Claude Opus 4.8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants