Skip to content

feat(node): Instrument stream responses for openai #17110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from

Conversation

RulaKhaled
Copy link
Member

@RulaKhaled RulaKhaled commented Jul 21, 2025

This adds support for OpenAI streaming responses in the Node.js SDK

What's new

  • Streaming Chat Completions: Support for stream: true in chat.completions.create()
  • Streaming Responses API: Support for streaming in the new OpenAI Responses API

// Streaming chat completions - automatically instrumented
const stream = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [...],
  stream: true
});

// Streaming responses API - automatically instrumented  
const responseStream = await openai.responses.create({
  model: 'gpt-4',
  input: 'Hello',
  stream: true
});

Copy link
Contributor

github-actions bot commented Jul 21, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 23.76 kB - -
@sentry/browser - with treeshaking flags 22.35 kB - -
@sentry/browser (incl. Tracing) 39.41 kB - -
@sentry/browser (incl. Tracing, Replay) 77.52 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 67.39 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 82.23 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 94.35 kB - -
@sentry/browser (incl. Feedback) 40.45 kB - -
@sentry/browser (incl. sendFeedback) 28.44 kB - -
@sentry/browser (incl. FeedbackAsync) 33.34 kB - -
@sentry/react 25.5 kB - -
@sentry/react (incl. Tracing) 41.38 kB - -
@sentry/vue 28.2 kB - -
@sentry/vue (incl. Tracing) 41.21 kB - -
@sentry/svelte 23.79 kB - -
CDN Bundle 25.28 kB - -
CDN Bundle (incl. Tracing) 39.28 kB - -
CDN Bundle (incl. Tracing, Replay) 75.39 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 80.82 kB - -
CDN Bundle - uncompressed 73.86 kB - -
CDN Bundle (incl. Tracing) - uncompressed 116.29 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 230.53 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 243.35 kB - -
@sentry/nextjs (client) 43.44 kB - -
@sentry/sveltekit (client) 39.84 kB - -
@sentry/node-core 47.48 kB -0.01% -1 B 🔽
@sentry/node 144.84 kB +0.5% +712 B 🔺
@sentry/node - without tracing 91.58 kB - -
@sentry/aws-serverless 103.03 kB - -

View base workflow run

@RulaKhaled RulaKhaled changed the title [WIP] Stream responses OpenAI feat(node): Instrument stream responses for openai Jul 23, 2025
This reverts commit 1607304.
@RulaKhaled RulaKhaled requested a review from andreiborza July 24, 2025 12:43
@RulaKhaled RulaKhaled marked this pull request as ready for review July 24, 2025 14:24
@mydea mydea requested a review from AbhiPrasad July 25, 2025 09:24
@@ -195,6 +143,9 @@ function addRequestAttributes(span: Span, params: Record<string, unknown>): void
if ('input' in params) {
span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) });
}
if ('stream' in params) {
span.setAttributes({ [OPENAI_RESPONSE_STREAM_ATTRIBUTE]: Boolean(params.stream) });
}
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect Attribute Assignment in Function

The addRequestAttributes function incorrectly sets the OPENAI_RESPONSE_STREAM_ATTRIBUTE (a response attribute) based on a request parameter. This function is intended for request attributes, and the stream request parameter is already correctly captured as GEN_AI_REQUEST_STREAM_ATTRIBUTE by extractRequestAttributes.

Locations (1)

Fix in CursorFix in Web

'openai.response.timestamp': '2023-03-01T06:31:50.000Z',
'openai.usage.completion_tokens': 0,
'openai.usage.prompt_tokens': 0,
},
Copy link

Choose a reason for hiding this comment

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

Bug: Inconsistent Metadata Across PII Configurations

The test expectations for OpenAI streaming responses are inconsistent between sendDefaultPii: false and sendDefaultPii: true configurations. When sendDefaultPii: false, the gen_ai.response.finish_reasons is expected as ["in_progress"] and token usage fields (gen_ai.usage.*_tokens) are expected to be 0. However, when sendDefaultPii: true, finish_reasons is ["in_progress","completed"] and token usage is correctly captured. These metadata fields should be consistently collected regardless of PII settings, as the mock streaming implementation emits all relevant data in both scenarios.

Locations (1)

Fix in CursorFix in Web

return;
}

const { response } = event as { response: OpenAIResponseObject };
Copy link

Choose a reason for hiding this comment

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

Bug: Event Handling Error: Missing Response Property

The processResponsesApiEvent function unsafely attempts to destructure a response property from the event object. Events of type response.output_text.delta (e.g., ResponseOutputTextDeltaEvent) do not contain a response property. While these events are typically handled by an early return, if recordOutputs is false or the delta property is missing, they will proceed to the destructuring, causing a runtime error. This also applies to other unknown event types that lack a response property. A check for the response property's existence is required before destructuring.

Locations (1)

Fix in CursorFix in Web

} catch (error) {
captureException(error);
span.end();
throw error;
Copy link

Choose a reason for hiding this comment

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

Bug: Streaming Error Reporting Inconsistency

OpenAI streaming calls that error before the stream begins incorrectly report status: 'ok'. This occurs because startSpanManual, used for streaming, does not automatically set the span status to an error on exceptions, unlike startSpan for non-streaming calls. This results in inconsistent error reporting and an incorrect test expectation.

Locations (2)

Fix in CursorFix in Web

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

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

I'd like us to add some more jsdoc explanations to packages/core/src/utils/openai/streaming.ts

return;
}
if (streamEvent instanceof Error) {
captureException(streamEvent);
Copy link
Member

Choose a reason for hiding this comment

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

m: when we capture an exception here, should we also set span status to errored?

also, I think we need to mark this as unhandled.

Copy link
Member Author

Choose a reason for hiding this comment

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

Potentially yeah. It's a series of events we're capturing in a single span, which makes things a bit vague. I guess if one fails it's fair to assume the span is in an error state, will update

setTokenUsageAttributes,
} from './utils';

interface StreamingState {
Copy link
Member

Choose a reason for hiding this comment

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

m: It would be nice to have some doc strings about this interface and it's fields.

l: I'm not the biggest fan of having a big interface with a bunch of nullable fields. Can we type this a bit stronger? For example if chunk.usage we should always have promptTokens, completionTokens, and totalTokens defined. Having some helpers that construct state from input (builder pattern?) might also be a useful abstraction.

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