@@ -45,6 +45,7 @@ import type {
45
45
} from "openai/resources" ;
46
46
import type { Stream } from "openai/streaming" ;
47
47
import { version } from "../package.json" ;
48
+ import { encoding_for_model , TiktokenModel , Tiktoken } from "tiktoken" ;
48
49
49
50
export class OpenAIInstrumentation extends InstrumentationBase < any > {
50
51
protected declare _config : OpenAIInstrumentationConfig ;
@@ -198,6 +199,7 @@ export class OpenAIInstrumentation extends InstrumentationBase<any> {
198
199
plugin . _streamingWrapPromise ( {
199
200
span,
200
201
type,
202
+ params : args [ 0 ] as any ,
201
203
promise : execPromise ,
202
204
} ) ,
203
205
) ;
@@ -296,15 +298,18 @@ export class OpenAIInstrumentation extends InstrumentationBase<any> {
296
298
private async * _streamingWrapPromise ( {
297
299
span,
298
300
type,
301
+ params,
299
302
promise,
300
303
} :
301
304
| {
302
305
span : Span ;
303
306
type : "chat" ;
307
+ params : ChatCompletionCreateParamsStreaming ;
304
308
promise : Promise < Stream < ChatCompletionChunk > > ;
305
309
}
306
310
| {
307
311
span : Span ;
312
+ params : CompletionCreateParamsStreaming ;
308
313
type : "completion" ;
309
314
promise : Promise < Stream < Completion > > ;
310
315
} ) {
@@ -356,6 +361,29 @@ export class OpenAIInstrumentation extends InstrumentationBase<any> {
356
361
this . _addLogProbsEvent ( span , result . choices [ 0 ] . logprobs ) ;
357
362
}
358
363
364
+ if ( this . _config . enrichTokens ) {
365
+ let promptTokens = 0 ;
366
+ for ( const message of params . messages ) {
367
+ promptTokens +=
368
+ this . tokenCountFromString (
369
+ message . content as string ,
370
+ result . model ,
371
+ ) ?? 0 ;
372
+ }
373
+
374
+ const completionTokens = this . tokenCountFromString (
375
+ result . choices [ 0 ] . message . content ?? "" ,
376
+ result . model ,
377
+ ) ;
378
+ if ( completionTokens ) {
379
+ result . usage = {
380
+ prompt_tokens : promptTokens ,
381
+ completion_tokens : completionTokens ,
382
+ total_tokens : promptTokens + completionTokens ,
383
+ } ;
384
+ }
385
+ }
386
+
359
387
this . _endSpan ( { span, type, result } ) ;
360
388
} else {
361
389
const result : Completion = {
@@ -394,6 +422,23 @@ export class OpenAIInstrumentation extends InstrumentationBase<any> {
394
422
this . _addLogProbsEvent ( span , result . choices [ 0 ] . logprobs ) ;
395
423
}
396
424
425
+ if ( this . _config . enrichTokens ) {
426
+ const promptTokens =
427
+ this . tokenCountFromString ( params . prompt as string , result . model ) ?? 0 ;
428
+
429
+ const completionTokens = this . tokenCountFromString (
430
+ result . choices [ 0 ] . text ?? "" ,
431
+ result . model ,
432
+ ) ;
433
+ if ( completionTokens ) {
434
+ result . usage = {
435
+ prompt_tokens : promptTokens ,
436
+ completion_tokens : completionTokens ,
437
+ total_tokens : promptTokens + completionTokens ,
438
+ } ;
439
+ }
440
+ }
441
+
397
442
this . _endSpan ( { span, type, result } ) ;
398
443
}
399
444
}
@@ -588,4 +633,23 @@ export class OpenAIInstrumentation extends InstrumentationBase<any> {
588
633
589
634
span . addEvent ( "logprobs" , { logprobs : JSON . stringify ( result ) } ) ;
590
635
}
636
+
637
+ private _encodingCache = new Map < string , Tiktoken > ( ) ;
638
+
639
+ private tokenCountFromString ( text : string , model : string ) {
640
+ if ( ! this . _encodingCache . has ( model ) ) {
641
+ try {
642
+ const encoding = encoding_for_model ( model as TiktokenModel ) ;
643
+ this . _encodingCache . set ( model , encoding ) ;
644
+ } catch ( e ) {
645
+ this . _diag . warn (
646
+ `Failed to get tiktoken encoding for model_name: ${ model } , error: ${ e } ` ,
647
+ ) ;
648
+ return ;
649
+ }
650
+ }
651
+
652
+ const encoding = this . _encodingCache . get ( model ) ;
653
+ return encoding ! . encode ( text ) . length ;
654
+ }
591
655
}
0 commit comments