22import { trace , Span , SpanKind , Attributes } from "@opentelemetry/api" ;
33import { SpanAttributes } from "@traceloop/ai-semantic-conventions" ;
44import {
5- ATTR_GEN_AI_COMPLETION ,
6- ATTR_GEN_AI_PROMPT ,
75 ATTR_GEN_AI_REQUEST_MODEL ,
8- ATTR_GEN_AI_SYSTEM ,
9- ATTR_GEN_AI_USAGE_COMPLETION_TOKENS ,
10- ATTR_GEN_AI_USAGE_PROMPT_TOKENS ,
6+ ATTR_GEN_AI_PROVIDER_NAME ,
7+ ATTR_GEN_AI_INPUT_MESSAGES ,
8+ ATTR_GEN_AI_OUTPUT_MESSAGES ,
9+ ATTR_GEN_AI_USAGE_INPUT_TOKENS ,
10+ ATTR_GEN_AI_USAGE_OUTPUT_TOKENS ,
11+ ATTR_GEN_AI_OPERATION_NAME ,
12+ GEN_AI_PROVIDER_NAME_VALUE_OPENAI ,
1113} from "@opentelemetry/semantic-conventions/incubating" ;
1214import type { ImageUploadCallback } from "./types" ;
1315import type {
@@ -169,8 +171,9 @@ export function setImageGenerationRequestAttributes(
169171 }
170172
171173 if ( params . prompt ) {
172- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.content` ] = params . prompt ;
173- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.role` ] = "user" ;
174+ attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] = JSON . stringify ( [
175+ { role : "user" , parts : [ { type : "text" , content : params . prompt } ] } ,
176+ ] ) ;
174177 }
175178
176179 Object . entries ( attributes ) . forEach ( ( [ key , value ] ) => {
@@ -200,8 +203,9 @@ export async function setImageEditRequestAttributes(
200203 }
201204
202205 if ( params . prompt ) {
203- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.content` ] = params . prompt ;
204- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.role` ] = "user" ;
206+ attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] = JSON . stringify ( [
207+ { role : "user" , parts : [ { type : "text" , content : params . prompt } ] } ,
208+ ] ) ;
205209 }
206210
207211 // Process input image if upload callback is available
@@ -223,10 +227,24 @@ export async function setImageEditRequestAttributes(
223227 ) ;
224228
225229 if ( imageUrl ) {
226- attributes [ `${ ATTR_GEN_AI_PROMPT } .1.content` ] = JSON . stringify ( [
227- { type : "image_url" , image_url : { url : imageUrl } } ,
228- ] ) ;
229- attributes [ `${ ATTR_GEN_AI_PROMPT } .1.role` ] = "user" ;
230+ // Add the image as a part of the existing user message
231+ const existingMessages = attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] ;
232+ if ( existingMessages ) {
233+ const parsed = JSON . parse ( existingMessages as string ) ;
234+ parsed [ 0 ] . parts . push ( {
235+ type : "uri" ,
236+ modality : "image" ,
237+ uri : imageUrl ,
238+ } ) ;
239+ attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] = JSON . stringify ( parsed ) ;
240+ } else {
241+ attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] = JSON . stringify ( [
242+ {
243+ role : "user" ,
244+ parts : [ { type : "uri" , modality : "image" , uri : imageUrl } ] ,
245+ } ,
246+ ] ) ;
247+ }
230248 }
231249 }
232250
@@ -275,10 +293,12 @@ export async function setImageVariationRequestAttributes(
275293 ) ;
276294
277295 if ( imageUrl ) {
278- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.content` ] = JSON . stringify ( [
279- { type : "image_url" , image_url : { url : imageUrl } } ,
296+ attributes [ ATTR_GEN_AI_INPUT_MESSAGES ] = JSON . stringify ( [
297+ {
298+ role : "user" ,
299+ parts : [ { type : "uri" , modality : "image" , uri : imageUrl } ] ,
300+ } ,
280301 ] ) ;
281- attributes [ `${ ATTR_GEN_AI_PROMPT } .0.role` ] = "user" ;
282302 }
283303 }
284304
@@ -303,7 +323,7 @@ export async function setImageGenerationResponseAttributes(
303323 params ,
304324 response . data . length ,
305325 ) ;
306- attributes [ ATTR_GEN_AI_USAGE_COMPLETION_TOKENS ] = completionTokens ;
326+ attributes [ ATTR_GEN_AI_USAGE_OUTPUT_TOKENS ] = completionTokens ;
307327
308328 // Calculate prompt tokens if enrichTokens is enabled
309329 if ( instrumentationConfig ?. enrichTokens ) {
@@ -319,38 +339,34 @@ export async function setImageGenerationResponseAttributes(
319339 }
320340
321341 if ( estimatedPromptTokens > 0 ) {
322- attributes [ ATTR_GEN_AI_USAGE_PROMPT_TOKENS ] = estimatedPromptTokens ;
342+ attributes [ ATTR_GEN_AI_USAGE_INPUT_TOKENS ] = estimatedPromptTokens ;
323343 }
324344
325- attributes [ SpanAttributes . LLM_USAGE_TOTAL_TOKENS ] =
345+ attributes [ SpanAttributes . GEN_AI_USAGE_TOTAL_TOKENS ] =
326346 estimatedPromptTokens + completionTokens ;
327347 } catch {
328- attributes [ SpanAttributes . LLM_USAGE_TOTAL_TOKENS ] = completionTokens ;
348+ attributes [ SpanAttributes . GEN_AI_USAGE_TOTAL_TOKENS ] = completionTokens ;
329349 }
330350 } else {
331- attributes [ SpanAttributes . LLM_USAGE_TOTAL_TOKENS ] = completionTokens ;
351+ attributes [ SpanAttributes . GEN_AI_USAGE_TOTAL_TOKENS ] = completionTokens ;
332352 }
333353 }
334354
335355 if ( response . data && response . data . length > 0 ) {
336356 const firstImage = response . data [ 0 ] ;
357+ let imageOutputUrl : string | undefined ;
337358
338359 if ( firstImage . b64_json && uploadCallback ) {
339360 try {
340361 const traceId = span . spanContext ( ) . traceId ;
341362 const spanId = span . spanContext ( ) . spanId ;
342363
343- const imageUrl = await uploadCallback (
364+ imageOutputUrl = await uploadCallback (
344365 traceId ,
345366 spanId ,
346367 "generated_image.png" ,
347368 firstImage . b64_json ,
348369 ) ;
349-
350- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.content` ] = JSON . stringify ( [
351- { type : "image_url" , image_url : { url : imageUrl } } ,
352- ] ) ;
353- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.role` ] = "assistant" ;
354370 } catch ( error ) {
355371 console . error ( "Failed to upload generated image:" , error ) ;
356372 }
@@ -364,29 +380,28 @@ export async function setImageGenerationResponseAttributes(
364380 const buffer = Buffer . from ( arrayBuffer ) ;
365381 const base64Data = buffer . toString ( "base64" ) ;
366382
367- const uploadedUrl = await uploadCallback (
383+ imageOutputUrl = await uploadCallback (
368384 traceId ,
369385 spanId ,
370386 "generated_image.png" ,
371387 base64Data ,
372388 ) ;
373-
374- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.content` ] = JSON . stringify ( [
375- { type : "image_url" , image_url : { url : uploadedUrl } } ,
376- ] ) ;
377- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.role` ] = "assistant" ;
378389 } catch ( error ) {
379390 console . error ( "Failed to fetch and upload generated image:" , error ) ;
380- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.content` ] = JSON . stringify ( [
381- { type : "image_url" , image_url : { url : firstImage . url } } ,
382- ] ) ;
383- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.role` ] = "assistant" ;
391+ imageOutputUrl = firstImage . url ;
384392 }
385393 } else if ( firstImage . url ) {
386- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.content` ] = JSON . stringify ( [
387- { type : "image_url" , image_url : { url : firstImage . url } } ,
394+ imageOutputUrl = firstImage . url ;
395+ }
396+
397+ if ( imageOutputUrl ) {
398+ attributes [ ATTR_GEN_AI_OUTPUT_MESSAGES ] = JSON . stringify ( [
399+ {
400+ role : "assistant" ,
401+ finish_reason : "stop" ,
402+ parts : [ { type : "uri" , modality : "image" , uri : imageOutputUrl } ] ,
403+ } ,
388404 ] ) ;
389- attributes [ `${ ATTR_GEN_AI_COMPLETION } .0.role` ] = "assistant" ;
390405 }
391406
392407 if ( firstImage . revised_prompt ) {
@@ -410,10 +425,11 @@ export function wrapImageGeneration(
410425 return function ( this : any , ...args : any [ ] ) {
411426 const params = args [ 0 ] as ImageGenerateParams ;
412427
413- const span = tracer . startSpan ( "openai.images.generate" , {
428+ const span = tracer . startSpan ( `image_generation ${ params . model } ` , {
414429 kind : SpanKind . CLIENT ,
415430 attributes : {
416- [ ATTR_GEN_AI_SYSTEM ] : "OpenAI" ,
431+ [ ATTR_GEN_AI_PROVIDER_NAME ] : GEN_AI_PROVIDER_NAME_VALUE_OPENAI ,
432+ [ ATTR_GEN_AI_OPERATION_NAME ] : "image_generation" ,
417433 "gen_ai.request.type" : "image_generation" ,
418434 } ,
419435 } ) ;
@@ -469,10 +485,11 @@ export function wrapImageEdit(
469485 return function ( this : any , ...args : any [ ] ) {
470486 const params = args [ 0 ] as ImageEditParams ;
471487
472- const span = tracer . startSpan ( "openai.images.edit" , {
488+ const span = tracer . startSpan ( `image_edit ${ params . model } ` , {
473489 kind : SpanKind . CLIENT ,
474490 attributes : {
475- [ ATTR_GEN_AI_SYSTEM ] : "OpenAI" ,
491+ [ ATTR_GEN_AI_PROVIDER_NAME ] : GEN_AI_PROVIDER_NAME_VALUE_OPENAI ,
492+ [ ATTR_GEN_AI_OPERATION_NAME ] : "image_edit" ,
476493 "gen_ai.request.type" : "image_edit" ,
477494 } ,
478495 } ) ;
@@ -536,10 +553,11 @@ export function wrapImageVariation(
536553 return function ( this : any , ...args : any [ ] ) {
537554 const params = args [ 0 ] as ImageCreateVariationParams ;
538555
539- const span = tracer . startSpan ( "openai.images.createVariation" , {
556+ const span = tracer . startSpan ( `image_variation ${ params . model } ` , {
540557 kind : SpanKind . CLIENT ,
541558 attributes : {
542- [ ATTR_GEN_AI_SYSTEM ] : "OpenAI" ,
559+ [ ATTR_GEN_AI_PROVIDER_NAME ] : GEN_AI_PROVIDER_NAME_VALUE_OPENAI ,
560+ [ ATTR_GEN_AI_OPERATION_NAME ] : "image_variation" ,
543561 "gen_ai.request.type" : "image_variation" ,
544562 } ,
545563 } ) ;
0 commit comments