@@ -19,6 +19,7 @@ import {
1919 accumulateTokensForParent ,
2020 applyAccumulatedTokens ,
2121 convertAvailableToolsToJsonString ,
22+ getSpanOpFromName ,
2223 requestMessagesFromPrompt ,
2324} from './utils' ;
2425import type { ProviderMetadata } from './vercel-ai-attributes' ;
@@ -64,8 +65,17 @@ function onVercelAiSpanStart(span: Span): void {
6465 return ;
6566 }
6667
67- // The AI model ID must be defined for generate, stream, and embed spans.
68- // The provider is optional and may not always be present.
68+ // Check if this is a Vercel AI span by name pattern.
69+ // We set origin even if model ID is missing, so processEndedVercelAiSpan
70+ // can still process the span when attributes are set late.
71+ if ( ! name . startsWith ( 'ai.' ) ) {
72+ return ;
73+ }
74+
75+ addOriginToSpan ( span , 'auto.vercelai.otel' ) ;
76+
77+ // The AI model ID must be defined for full generate span processing.
78+ // If it's not available at span start, processEndedVercelAiSpan will set the op.
6979 const aiModelId = attributes [ AI_MODEL_ID_ATTRIBUTE ] ;
7080 if ( typeof aiModelId !== 'string' || ! aiModelId ) {
7181 return ;
@@ -109,12 +119,21 @@ function vercelAiEventProcessor(event: Event): Event {
109119 * Post-process spans emitted by the Vercel AI SDK.
110120 */
111121function processEndedVercelAiSpan ( span : SpanJSON ) : void {
112- const { data : attributes , origin } = span ;
122+ const { data : attributes , origin, description : name } = span ;
113123
114124 if ( origin !== 'auto.vercelai.otel' ) {
115125 return ;
116126 }
117127
128+ // Set span.op if it wasn't already set during span start
129+ // This can happen when the model attribute is set too late
130+ // Check for both undefined (OTel spans without op) and 'default'
131+ if ( ( ! span . op || span . op === 'default' ) && name ) {
132+ const op = getSpanOpFromName ( name ) ;
133+ span . op = op ;
134+ attributes [ 'sentry.op' ] = op ;
135+ }
136+
118137 renameAttributeKey ( attributes , AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE , GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ) ;
119138 renameAttributeKey ( attributes , AI_USAGE_PROMPT_TOKENS_ATTRIBUTE , GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ) ;
120139 renameAttributeKey ( attributes , AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE , GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE ) ;
@@ -204,8 +223,6 @@ function processToolCallSpan(span: Span, attributes: SpanAttributes): void {
204223}
205224
206225function processGenerateSpan ( span : Span , name : string , attributes : SpanAttributes ) : void {
207- addOriginToSpan ( span , 'auto.vercelai.otel' ) ;
208-
209226 const nameWthoutAi = name . replace ( 'ai.' , '' ) ;
210227 span . setAttribute ( 'ai.pipeline.name' , nameWthoutAi ) ;
211228 span . updateName ( nameWthoutAi ) ;
@@ -225,76 +242,33 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute
225242 }
226243 span . setAttribute ( 'ai.streaming' , name . includes ( 'stream' ) ) ;
227244
228- // Generate Spans
229- if ( name === 'ai.generateText' ) {
230- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
231- return ;
232- }
233-
234- if ( name === 'ai.generateText.doGenerate' ) {
235- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.generate_text' ) ;
236- span . updateName ( `generate_text ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
237- return ;
238- }
239-
240- if ( name === 'ai.streamText' ) {
241- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
242- return ;
243- }
244-
245- if ( name === 'ai.streamText.doStream' ) {
246- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.stream_text' ) ;
247- span . updateName ( `stream_text ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
248- return ;
249- }
250-
251- if ( name === 'ai.generateObject' ) {
252- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
253- return ;
254- }
255-
256- if ( name === 'ai.generateObject.doGenerate' ) {
257- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.generate_object' ) ;
258- span . updateName ( `generate_object ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
259- return ;
260- }
261-
262- if ( name === 'ai.streamObject' ) {
263- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
264- return ;
265- }
266-
267- if ( name === 'ai.streamObject.doStream' ) {
268- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.stream_object' ) ;
269- span . updateName ( `stream_object ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
270- return ;
271- }
272-
273- if ( name === 'ai.embed' ) {
274- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
275- return ;
276- }
277-
278- if ( name === 'ai.embed.doEmbed' ) {
279- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.embed' ) ;
280- span . updateName ( `embed ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
281- return ;
245+ // Set the op based on the span name
246+ const op = getSpanOpFromName ( name ) ;
247+ if ( op ) {
248+ span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , op ) ;
282249 }
283250
284- if ( name === 'ai.embedMany' ) {
285- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.invoke_agent' ) ;
286- return ;
287- }
288-
289- if ( name === 'ai.embedMany.doEmbed' ) {
290- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'gen_ai.embed_many' ) ;
291- span . updateName ( `embed_many ${ attributes [ AI_MODEL_ID_ATTRIBUTE ] } ` ) ;
292- return ;
293- }
294-
295- if ( name . startsWith ( 'ai.stream' ) ) {
296- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_OP , 'ai.run' ) ;
297- return ;
251+ // Update span names for .do* spans to include the model ID
252+ const modelId = attributes [ AI_MODEL_ID_ATTRIBUTE ] ;
253+ switch ( name ) {
254+ case 'ai.generateText.doGenerate' :
255+ span . updateName ( `generate_text ${ modelId } ` ) ;
256+ break ;
257+ case 'ai.streamText.doStream' :
258+ span . updateName ( `stream_text ${ modelId } ` ) ;
259+ break ;
260+ case 'ai.generateObject.doGenerate' :
261+ span . updateName ( `generate_object ${ modelId } ` ) ;
262+ break ;
263+ case 'ai.streamObject.doStream' :
264+ span . updateName ( `stream_object ${ modelId } ` ) ;
265+ break ;
266+ case 'ai.embed.doEmbed' :
267+ span . updateName ( `embed ${ modelId } ` ) ;
268+ break ;
269+ case 'ai.embedMany.doEmbed' :
270+ span . updateName ( `embed_many ${ modelId } ` ) ;
271+ break ;
298272 }
299273}
300274
0 commit comments