4
4
import os
5
5
import time
6
6
import types
7
- from typing import Collection , Optional
7
+ from typing import Collection , Optional , Union
8
8
9
9
from opentelemetry import context as context_api
10
- from opentelemetry ._events import get_event_logger
10
+ from opentelemetry ._events import EventLogger , get_event_logger
11
11
from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
12
12
from opentelemetry .instrumentation .utils import (
13
13
_SUPPRESS_INSTRUMENTATION_KEY ,
14
14
unwrap ,
15
15
)
16
16
from opentelemetry .instrumentation .watsonx .config import Config
17
- from opentelemetry .instrumentation .watsonx .event_handler import (
18
- ChoiceEvent ,
19
- MessageEvent ,
17
+ from opentelemetry .instrumentation .watsonx .event_emitter import (
20
18
emit_event ,
21
19
)
22
- from opentelemetry .instrumentation .watsonx .utils import dont_throw
20
+ from opentelemetry .instrumentation .watsonx .event_models import ChoiceEvent , MessageEvent
21
+ from opentelemetry .instrumentation .watsonx .utils import (
22
+ dont_throw ,
23
+ should_emit_events ,
24
+ should_send_prompts ,
25
+ )
23
26
from opentelemetry .instrumentation .watsonx .version import __version__
24
27
from opentelemetry .metrics import Counter , Histogram , get_meter
25
- from opentelemetry .semconv ._incubating .attributes import (
26
- gen_ai_attributes as GenAIAttributes ,
27
- )
28
28
from opentelemetry .semconv_ai import (
29
29
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY ,
30
30
LLMRequestTypeValues ,
@@ -107,6 +107,8 @@ def _set_span_attribute(span, name, value):
107
107
108
108
109
109
def _set_api_attributes (span ):
110
+ if not span .is_recording ():
111
+ return
110
112
_set_span_attribute (
111
113
span ,
112
114
WatsonxSpanAttributes .WATSONX_API_BASE ,
@@ -115,20 +117,15 @@ def _set_api_attributes(span):
115
117
_set_span_attribute (span , WatsonxSpanAttributes .WATSONX_API_TYPE , "watsonx.ai" )
116
118
_set_span_attribute (span , WatsonxSpanAttributes .WATSONX_API_VERSION , "1.0" )
117
119
118
- return
119
-
120
-
121
- def should_send_prompts ():
122
- return (
123
- os .getenv ("TRACELOOP_TRACE_CONTENT" ) or "true"
124
- ).lower () == "true" or context_api .get_value ("override_enable_content_tracing" )
125
-
126
120
127
121
def is_metrics_enabled () -> bool :
128
122
return (os .getenv ("TRACELOOP_METRICS_ENABLED" ) or "true" ).lower () == "true"
129
123
130
124
131
125
def _set_input_attributes (span , instance , kwargs ):
126
+ if not span .is_recording ():
127
+ return
128
+
132
129
if should_send_prompts () and kwargs is not None and len (kwargs ) > 0 :
133
130
prompt = kwargs .get ("prompt" )
134
131
if isinstance (prompt , list ):
@@ -145,6 +142,11 @@ def _set_input_attributes(span, instance, kwargs):
145
142
prompt ,
146
143
)
147
144
145
+
146
+ def set_model_input_attributes (span , instance ):
147
+ if not span .is_recording ():
148
+ return
149
+
148
150
_set_span_attribute (span , SpanAttributes .LLM_REQUEST_MODEL , instance .model_id )
149
151
# Set other attributes
150
152
modelParameters = instance .params
@@ -186,10 +188,20 @@ def _set_input_attributes(span, instance, kwargs):
186
188
span , SpanAttributes .LLM_REQUEST_TOP_P , modelParameters .get ("top_p" , None )
187
189
)
188
190
189
- return
190
-
191
191
192
192
def _set_stream_response_attributes (span , stream_response ):
193
+ if not span .is_recording ():
194
+ return
195
+ _set_span_attribute (
196
+ span ,
197
+ f"{ SpanAttributes .LLM_COMPLETIONS } .0.content" ,
198
+ stream_response .get ("generated_text" ),
199
+ )
200
+
201
+
202
+ def _set_model_stream_response_attributes (span , stream_response ):
203
+ if not span .is_recording ():
204
+ return
193
205
_set_span_attribute (
194
206
span , SpanAttributes .LLM_RESPONSE_MODEL , stream_response .get ("model_id" )
195
207
)
@@ -211,11 +223,6 @@ def _set_stream_response_attributes(span, stream_response):
211
223
SpanAttributes .LLM_USAGE_TOTAL_TOKENS ,
212
224
total_token ,
213
225
)
214
- _set_span_attribute (
215
- span ,
216
- f"{ SpanAttributes .LLM_COMPLETIONS } .0.content" ,
217
- stream_response .get ("generated_text" ),
218
- )
219
226
220
227
221
228
def _set_completion_content_attributes (
@@ -263,7 +270,7 @@ def _token_usage_count(responses):
263
270
def _set_response_attributes (
264
271
span , responses , token_histogram , response_counter , duration_histogram , duration
265
272
):
266
- if not isinstance (responses , (list , dict )):
273
+ if not isinstance (responses , (list , dict )) or not span . is_recording () :
267
274
return
268
275
269
276
if isinstance (responses , list ):
@@ -283,6 +290,32 @@ def _set_response_attributes(
283
290
return
284
291
_set_span_attribute (span , SpanAttributes .LLM_RESPONSE_MODEL , model_id )
285
292
293
+ shared_attributes = _metric_shared_attributes (response_model = model_id )
294
+
295
+ prompt_token , completion_token = _token_usage_count (responses )
296
+
297
+ if token_histogram :
298
+ attributes_with_token_type = {
299
+ ** shared_attributes ,
300
+ SpanAttributes .LLM_TOKEN_TYPE : "output" ,
301
+ }
302
+ token_histogram .record (completion_token , attributes = attributes_with_token_type )
303
+ attributes_with_token_type = {
304
+ ** shared_attributes ,
305
+ SpanAttributes .LLM_TOKEN_TYPE : "input" ,
306
+ }
307
+ token_histogram .record (prompt_token , attributes = attributes_with_token_type )
308
+
309
+ if duration and isinstance (duration , (float , int )) and duration_histogram :
310
+ duration_histogram .record (duration , attributes = shared_attributes )
311
+
312
+
313
+ def set_model_response_attributes (
314
+ span , responses , token_histogram , duration_histogram , duration
315
+ ):
316
+ if not span .is_recording ():
317
+ return
318
+
286
319
prompt_token , completion_token = _token_usage_count (responses )
287
320
if (prompt_token + completion_token ) != 0 :
288
321
_set_span_attribute (
@@ -301,35 +334,16 @@ def _set_response_attributes(
301
334
prompt_token + completion_token ,
302
335
)
303
336
304
- shared_attributes = _metric_shared_attributes (response_model = model_id )
305
337
306
- if token_histogram :
307
- attributes_with_token_type = {
308
- ** shared_attributes ,
309
- SpanAttributes .LLM_TOKEN_TYPE : "output" ,
310
- }
311
- token_histogram .record (
312
- completion_token , attributes = attributes_with_token_type
313
- )
314
- attributes_with_token_type = {
315
- ** shared_attributes ,
316
- SpanAttributes .LLM_TOKEN_TYPE : "input" ,
317
- }
318
- token_histogram .record (prompt_token , attributes = attributes_with_token_type )
319
-
320
- if duration and isinstance (duration , (float , int )) and duration_histogram :
321
- duration_histogram .record (duration , attributes = shared_attributes )
322
-
323
-
324
- def _emit_input_events (args , kwargs ):
338
+ def _emit_input_events (args , kwargs , event_logger ):
325
339
prompt = kwargs .get ("prompt" ) or args [0 ]
326
340
327
341
if isinstance (prompt , list ):
328
342
for message in prompt :
329
- emit_event (MessageEvent (content = message , role = "user" ))
343
+ emit_event (MessageEvent (content = message , role = "user" ), event_logger )
330
344
331
345
elif isinstance (prompt , str ):
332
- emit_event (MessageEvent (content = prompt , role = "user" ))
346
+ emit_event (MessageEvent (content = prompt , role = "user" ), event_logger )
333
347
334
348
335
349
def _emit_response_events (response : dict ):
@@ -345,6 +359,7 @@ def _emit_response_events(response: dict):
345
359
346
360
def _build_and_set_stream_response (
347
361
span ,
362
+ event_logger ,
348
363
response ,
349
364
raw_flag ,
350
365
token_histogram ,
@@ -378,7 +393,9 @@ def _build_and_set_stream_response(
378
393
"generated_token_count" : stream_generated_token_count ,
379
394
"input_token_count" : stream_input_token_count ,
380
395
}
381
- _set_stream_response_attributes (span , stream_response )
396
+ _handle_stream_response (
397
+ span , event_logger , stream_response , stream_generated_text , stream_stop_reason
398
+ )
382
399
# response counter
383
400
if response_counter :
384
401
attributes_with_reason = {
@@ -412,16 +429,6 @@ def _build_and_set_stream_response(
412
429
if duration and isinstance (duration , (float , int )) and duration_histogram :
413
430
duration_histogram .record (duration , attributes = shared_attributes )
414
431
415
- _emit_response_events (
416
- {
417
- "results" : [
418
- {
419
- "stop_reason" : stream_stop_reason ,
420
- "generated_text" : stream_generated_text ,
421
- }
422
- ]
423
- },
424
- )
425
432
span .set_status (Status (StatusCode .OK ))
426
433
span .end ()
427
434
@@ -444,6 +451,7 @@ def _with_tracer(
444
451
response_counter ,
445
452
duration_histogram ,
446
453
exception_counter ,
454
+ event_logger ,
447
455
):
448
456
def wrapper (wrapped , instance , args , kwargs ):
449
457
return func (
@@ -453,6 +461,7 @@ def wrapper(wrapped, instance, args, kwargs):
453
461
response_counter ,
454
462
duration_histogram ,
455
463
exception_counter ,
464
+ event_logger ,
456
465
wrapped ,
457
466
instance ,
458
467
args ,
@@ -464,6 +473,67 @@ def wrapper(wrapped, instance, args, kwargs):
464
473
return _with_tracer
465
474
466
475
476
+ @dont_throw
477
+ def _handle_input (span , event_logger , name , instance , response_counter , args , kwargs ):
478
+ _set_api_attributes (span )
479
+
480
+ if "generate" in name :
481
+ set_model_input_attributes (span , instance )
482
+
483
+ if should_emit_events () and event_logger :
484
+ _emit_input_events (args , kwargs , event_logger )
485
+ elif "generate" in name :
486
+ _set_input_attributes (span , instance , kwargs )
487
+
488
+
489
+ @dont_throw
490
+ def _handle_response (
491
+ span ,
492
+ event_logger ,
493
+ responses ,
494
+ response_counter ,
495
+ token_histogram ,
496
+ duration_histogram ,
497
+ duration ,
498
+ ):
499
+ set_model_response_attributes (
500
+ span , responses , token_histogram , duration_histogram , duration
501
+ )
502
+
503
+ if should_emit_events () and event_logger :
504
+ _emit_response_events (responses , event_logger )
505
+ else :
506
+ _set_response_attributes (
507
+ span ,
508
+ responses ,
509
+ token_histogram ,
510
+ response_counter ,
511
+ duration_histogram ,
512
+ duration ,
513
+ )
514
+
515
+
516
+ @dont_throw
517
+ def _handle_stream_response (
518
+ span , event_logger , stream_response , stream_generated_text , stream_stop_reason
519
+ ):
520
+ _set_model_stream_response_attributes (span , stream_response )
521
+
522
+ if should_emit_events () and event_logger :
523
+ _emit_response_events (
524
+ {
525
+ "results" : [
526
+ {
527
+ "stop_reason" : stream_stop_reason ,
528
+ "generated_text" : stream_generated_text ,
529
+ }
530
+ ]
531
+ },
532
+ )
533
+ else :
534
+ _set_stream_response_attributes (span , stream_response )
535
+
536
+
467
537
@_with_tracer_wrapper
468
538
def _wrap (
469
539
tracer ,
@@ -472,6 +542,7 @@ def _wrap(
472
542
response_counter : Counter ,
473
543
duration_histogram : Histogram ,
474
544
exception_counter : Counter ,
545
+ event_logger : Union [EventLogger , None ],
475
546
wrapped ,
476
547
instance ,
477
548
args ,
@@ -494,17 +565,15 @@ def _wrap(
494
565
},
495
566
)
496
567
497
- _set_api_attributes (span )
568
+ _handle_input (span , event_logger , name , instance , args , kwargs )
569
+
498
570
if "generate" in name :
499
- _set_input_attributes (span , instance , kwargs )
500
571
if to_wrap .get ("method" ) == "generate_text_stream" :
501
572
if (raw_flag := kwargs .get ("raw_response" , None )) is None :
502
573
kwargs = {** kwargs , "raw_response" : True }
503
574
elif raw_flag is False :
504
575
kwargs ["raw_response" ] = True
505
576
506
- _emit_input_events (args , kwargs )
507
-
508
577
try :
509
578
start_time = time .time ()
510
579
response = wrapped (* args , ** kwargs )
@@ -528,6 +597,7 @@ def _wrap(
528
597
if isinstance (response , types .GeneratorType ):
529
598
return _build_and_set_stream_response (
530
599
span ,
600
+ event_logger ,
531
601
response ,
532
602
raw_flag ,
533
603
token_histogram ,
@@ -537,17 +607,15 @@ def _wrap(
537
607
)
538
608
else :
539
609
duration = end_time - start_time
540
- _set_response_attributes (
610
+ _handle_response (
541
611
span ,
612
+ event_logger ,
542
613
response ,
543
- token_histogram ,
544
614
response_counter ,
615
+ token_histogram ,
545
616
duration_histogram ,
546
617
duration ,
547
618
)
548
-
549
- _emit_response_events (response )
550
-
551
619
span .end ()
552
620
return response
553
621
@@ -613,9 +681,11 @@ def _instrument(self, **kwargs):
613
681
None ,
614
682
)
615
683
684
+ event_logger = None
685
+
616
686
if not Config .use_legacy_attributes :
617
687
event_logger_provider = kwargs .get ("event_logger_provider" )
618
- Config . event_logger = get_event_logger (
688
+ event_logger = get_event_logger (
619
689
__name__ , __version__ , event_logger_provider = event_logger_provider
620
690
)
621
691
@@ -634,6 +704,7 @@ def _instrument(self, **kwargs):
634
704
response_counter ,
635
705
duration_histogram ,
636
706
exception_counter ,
707
+ event_logger ,
637
708
),
638
709
)
639
710
0 commit comments