Skip to content

feat(core): Emit low-cardinality gen_ai inference span names when streaming - #23573

Open
RulaKhaled wants to merge 3 commits into
developfrom
rolaabuhasna/js-3410-emit-low-cardinality-gen-ai-inference-span-names
Open

feat(core): Emit low-cardinality gen_ai inference span names when streaming#23573
RulaKhaled wants to merge 3 commits into
developfrom
rolaabuhasna/js-3410-emit-low-cardinality-gen-ai-inference-span-names

Conversation

@RulaKhaled

@RulaKhaled RulaKhaled commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

When span streaming is enabled, gen_ai inference span names follow the inference templates: {operation} {model} when a model is known, {operation} when it is not. Instrumented methods always have an operation, so the convention fallback Generative AI model operation is not emitted today.

traceLifecycle: 'static' keeps the previous names (chat unknown). Known models stay in the name in both lifecycles (chat gpt-4).

When streaming, gen_ai.execute_tool is named execute_tool; the tool name stays on gen_ai.tool.name. Agent ops (gen_ai.invoke_agent, including LangChain chain {name} spans) remain out of scope (JS-3409).

Fixes #23525

@linear-code

linear-code Bot commented Aug 25, 2026

Copy link
Copy Markdown

JS-3410

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.57 kB - -
@sentry/browser - with treeshaking flags 26.92 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 26.82 kB - -
@sentry/browser (incl. Tracing) 48.55 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.57 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.47 kB - -
@sentry/browser (incl. Tracing, Replay) 88 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.44 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 92.69 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 105.41 kB - -
@sentry/browser (incl. Feedback) 45.81 kB - -
@sentry/browser (incl. sendFeedback) 33.36 kB - -
@sentry/browser (incl. FeedbackAsync) 38.47 kB - -
@sentry/browser (incl. Metrics) 29.52 kB - -
@sentry/browser (incl. Logs) 29.8 kB - -
@sentry/browser (incl. Metrics & Logs) 30.45 kB - -
@sentry/react 30.33 kB - -
@sentry/react (incl. Tracing) 50.76 kB - -
@sentry/vue 35.64 kB - -
@sentry/vue (incl. Tracing) 50.78 kB - -
@sentry/svelte 28.6 kB - -
CDN Bundle 30.32 kB - -
CDN Bundle (incl. Tracing) 49.07 kB - -
CDN Bundle (incl. Logs, Metrics) 32.54 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 50.94 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 72.96 kB - -
CDN Bundle (incl. Tracing, Replay) 86.57 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.44 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 92.33 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94.27 kB - -
CDN Bundle - uncompressed 89.94 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.74 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 96.23 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 152.43 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 225.3 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 266.14 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 271.82 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.84 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 285.51 kB - -
@sentry/nextjs (client) 53.27 kB - -
@sentry/sveltekit (client) 48.97 kB - -
@sentry/core/server 65.13 kB - -
@sentry/core/browser 52.27 kB - -
@sentry/node 121.92 kB +0.24% +281 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 85.18 kB - -
@sentry/node - without tracing 87.52 kB +0.03% +24 B 🔺
@sentry/aws-serverless 95.93 kB +0.03% +25 B 🔺
@sentry/cloudflare (withSentry) - minified 199.39 kB +0.06% +111 B 🔺
@sentry/cloudflare (withSentry) 495.88 kB +0.1% +454 B 🔺

View base workflow run

…when streaming

When span streaming is on, inference spans use `{operation} {model}` or
`{operation}` if the model is missing, and execute_tool drops the tool
name from the span name. Static lifecycle names are unchanged.

Co-Authored-By: Cursor Grok 4.6 <cursoragent@cursor.com>
@RulaKhaled
RulaKhaled force-pushed the rolaabuhasna/js-3410-emit-low-cardinality-gen-ai-inference-span-names branch from dac5b71 to 314f52f Compare August 25, 2026 13:50
MIGRATION.md should not list ops that are not part of the published
v11 span-name set yet.

Co-Authored-By: Cursor Grok 4.6 <cursoragent@cursor.com>
@RulaKhaled
RulaKhaled marked this pull request as ready for review August 25, 2026 14:15
@RulaKhaled
RulaKhaled requested review from a team as code owners August 25, 2026 14:15
@RulaKhaled
RulaKhaled requested review from JPeer264 and isaacs and removed request for a team August 25, 2026 14:15
@RulaKhaled RulaKhaled self-assigned this Aug 25, 2026

@isaacs isaacs 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.

There are some opportunities for a bit of factoring to reduce the overhead here and prune some dead fallback branches, but that can also be done in a subsequent followup.

Comment on lines +197 to +203
// With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality.
name:
(typeof model === 'string' && model !== 'unknown') || !(client && hasSpanStreamingEnabled(client))
? `${operationName} ${model}`
: operationName !== 'unknown'
? operationName
: GEN_AI_INFERENCE_SPAN_NAME_FALLBACK,

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.

low: This nearly-identical ternary is repeated quite a lot in this patch (eg, this file again on line 310, packages/server-utils/src/ai/google-genai/index.ts line 277, etc). Seems like a good opportunity for factoring out. That could reduce bundle size a bit, but more importantly, would make the code more readable and unlikely to drift.

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.

Also, I think the operationName !== 'unknown' ? operationName : GEN_AI_INFERENCE_SPAN_NAME_FALLBACK branch is unreachable. Every entry in packages/server-utils/src/ai/anthropic-ai/constants.ts has an operation name, so the || 'unknown' never gets executed. Probably we could tighten up the types and make the fallback unnecessary. (Doesn't have to be in this PR, but could be something to throw a clanker at, see if it can let typescript inference prove that the fallback is unnecessary.)

@@ -189,8 +192,15 @@ function handleStreamingRequest<T extends unknown[], R>(
isStreamingMethod: boolean,
): R | Promise<R> {
const model = requestAttributes[GEN_AI_REQUEST_MODEL] ?? 'unknown';

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.

Is it possible for this to be an empty string? If so, we could have a span name like 'chat ' (with a trailing space). I think if this is || instead, it dodges the issue.

Suggested change
const model = requestAttributes[GEN_AI_REQUEST_MODEL] ?? 'unknown';
const model = requestAttributes[GEN_AI_REQUEST_MODEL] || 'unknown';

name: `${operationName} ${model}`,
// With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality.
name:
(typeof model === 'string' && model !== 'unknown') || !(client && hasSpanStreamingEnabled(client))

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.

Is the typeof model === 'string' doing any work here? It seems like it's guarded by line 146 above, right?

name: `${operationName} ${modelName}`,
// With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality.
name:
(typeof modelName === 'string' && modelName !== 'unknown') || !(client && hasSpanStreamingEnabled(client))

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.

Same here, modelName must be a string at this point, so the typeof seems unnecessary.

@@ -296,7 +307,13 @@ function instrumentMethod<T extends unknown[], R>(

const instrumentedPromise = startSpan(
{

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.

This object creation is identical to the one on line 196, so we could probably use a helper function to do both in one place.

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.

Emit low cardinality gen_ai inference span names

2 participants