13
13
# limitations under the License.
14
14
15
15
import json
16
+ from typing import Optional , Union
16
17
from opentelemetry import trace
17
18
from opentelemetry .trace import SpanKind , Span
18
19
from opentelemetry .trace .status import Status , StatusCode
19
20
from opentelemetry .trace .propagation import set_span_in_context
20
- from openai ._types import NOT_GIVEN
21
- from span_attributes import SpanAttributes , LLMSpanAttributes , Event
22
- from utils import (
23
- estimate_tokens ,
24
- silently_fail ,
25
- extract_content ,
26
- calculate_prompt_tokens ,
27
- )
21
+ from openai import NOT_GIVEN
22
+ from .span_attributes import LLMSpanAttributes , SpanAttributes
28
23
24
+ from .utils import silently_fail , extract_content
25
+ from opentelemetry .trace import Tracer
29
26
30
- def chat_completions_create (original_method , version , tracer ):
27
+
28
+ def chat_completions_create (original_method , version , tracer : Tracer ):
31
29
"""Wrap the `create` method of the `ChatCompletion` class to trace it."""
32
30
33
31
def traced_method (wrapped , instance , args , kwargs ):
@@ -69,8 +67,10 @@ def traced_method(wrapped, instance, args, kwargs):
69
67
70
68
attributes = LLMSpanAttributes (** span_attributes )
71
69
70
+ span_name = f"{ attributes .gen_ai_operation_name } { attributes .gen_ai_request_model } "
71
+
72
72
span = tracer .start_span (
73
- "openai.completion" ,
73
+ name = span_name ,
74
74
kind = SpanKind .CLIENT ,
75
75
context = set_span_in_context (trace .get_current_span ()),
76
76
)
@@ -79,36 +79,18 @@ def traced_method(wrapped, instance, args, kwargs):
79
79
try :
80
80
result = wrapped (* args , ** kwargs )
81
81
if is_streaming (kwargs ):
82
- prompt_tokens = 0
83
- for message in kwargs .get ("messages" , {}):
84
- prompt_tokens += calculate_prompt_tokens (
85
- json .dumps (str (message )), kwargs .get ("model" )
86
- )
87
-
88
- if (
89
- kwargs .get ("functions" ) is not None
90
- and kwargs .get ("functions" ) != NOT_GIVEN
91
- ):
92
- for function in kwargs .get ("functions" ):
93
- prompt_tokens += calculate_prompt_tokens (
94
- json .dumps (function ), kwargs .get ("model" )
95
- )
96
-
97
82
return StreamWrapper (
98
83
result ,
99
84
span ,
100
- prompt_tokens ,
101
85
function_call = kwargs .get ("functions" ) is not None ,
102
86
tool_calls = kwargs .get ("tools" ) is not None ,
103
87
)
104
88
else :
105
89
_set_response_attributes (span , kwargs , result )
106
- span .set_status (StatusCode .OK )
107
90
span .end ()
108
91
return result
109
92
110
93
except Exception as error :
111
- span .record_exception (error )
112
94
span .set_status (Status (StatusCode .ERROR , str (error )))
113
95
span .end ()
114
96
raise
@@ -118,21 +100,14 @@ def traced_method(wrapped, instance, args, kwargs):
118
100
119
101
def get_tool_calls (item ):
120
102
if isinstance (item , dict ):
121
- if "tool_calls" in item and item ["tool_calls" ] is not None :
122
- return item ["tool_calls" ]
123
- return None
124
-
103
+ return item .get ("tool_calls" )
125
104
else :
126
- if hasattr (item , "tool_calls" ) and item .tool_calls is not None :
127
- return item .tool_calls
128
- return None
105
+ return getattr (item , "tool_calls" , None )
129
106
130
107
131
108
@silently_fail
132
- def _set_input_attributes (span , kwargs , attributes ):
109
+ def _set_input_attributes (span , kwargs , attributes : LLMSpanAttributes ):
133
110
tools = []
134
- for field , value in attributes .model_dump (by_alias = True ).items ():
135
- set_span_attribute (span , field , value )
136
111
137
112
if (
138
113
kwargs .get ("functions" ) is not None
@@ -149,6 +124,9 @@ def _set_input_attributes(span, kwargs, attributes):
149
124
if tools :
150
125
set_span_attribute (span , SpanAttributes .LLM_TOOLS , json .dumps (tools ))
151
126
127
+ for field , value in attributes .model_dump (by_alias = True ).items ():
128
+ set_span_attribute (span , field , value )
129
+
152
130
153
131
@silently_fail
154
132
def _set_response_attributes (span , kwargs , result ):
@@ -230,15 +208,6 @@ def set_event_completion(span: Span, result_content):
230
208
)
231
209
232
210
233
- def set_event_completion_chunk (span : Span , chunk ):
234
- span .add_event (
235
- name = SpanAttributes .LLM_CONTENT_COMPLETION_CHUNK ,
236
- attributes = {
237
- SpanAttributes .LLM_CONTENT_COMPLETION_CHUNK : json .dumps (chunk ),
238
- },
239
- )
240
-
241
-
242
211
def set_span_attribute (span : Span , name , value ):
243
212
if value is not None :
244
213
if value != "" or value != NOT_GIVEN :
@@ -250,33 +219,33 @@ def set_span_attribute(span: Span, name, value):
250
219
251
220
252
221
def is_streaming (kwargs ):
253
- return not (
254
- kwargs . get ( "stream" ) is False
255
- or kwargs . get ( "stream" ) is None
256
- or kwargs . get ( "stream" ) == NOT_GIVEN
257
- )
222
+ return non_numerical_value_is_set ( kwargs . get ( "stream" ))
223
+
224
+
225
+ def non_numerical_value_is_set ( value : Optional [ Union [ bool , str ]]):
226
+ return bool ( value ) and value != NOT_GIVEN
258
227
259
228
260
229
def get_llm_request_attributes (
261
230
kwargs , prompts = None , model = None , operation_name = "chat"
262
231
):
263
232
264
- user = kwargs .get ("user" , None )
233
+ user = kwargs .get ("user" )
265
234
if prompts is None :
266
235
prompts = (
267
236
[{"role" : user or "user" , "content" : kwargs .get ("prompt" )}]
268
237
if "prompt" in kwargs
269
238
else None
270
239
)
271
240
top_k = (
272
- kwargs .get ("n" , None )
273
- or kwargs .get ("k" , None )
274
- or kwargs .get ("top_k" , None )
275
- or kwargs .get ("top_n" , None )
241
+ kwargs .get ("n" )
242
+ or kwargs .get ("k" )
243
+ or kwargs .get ("top_k" )
244
+ or kwargs .get ("top_n" )
276
245
)
277
246
278
- top_p = kwargs .get ("p" , None ) or kwargs .get ("top_p" , None )
279
- tools = kwargs .get ("tools" , None )
247
+ top_p = kwargs .get ("p" ) or kwargs .get ("top_p" )
248
+ tools = kwargs .get ("tools" )
280
249
return {
281
250
SpanAttributes .LLM_OPERATION_NAME : operation_name ,
282
251
SpanAttributes .LLM_REQUEST_MODEL : model or kwargs .get ("model" ),
@@ -308,7 +277,7 @@ def __init__(
308
277
self ,
309
278
stream ,
310
279
span ,
311
- prompt_tokens ,
280
+ prompt_tokens = None ,
312
281
function_call = False ,
313
282
tool_calls = False ,
314
283
):
@@ -324,12 +293,10 @@ def __init__(
324
293
325
294
def setup (self ):
326
295
if not self ._span_started :
327
- self .span .add_event (Event .STREAM_START .value )
328
296
self ._span_started = True
329
297
330
298
def cleanup (self ):
331
299
if self ._span_started :
332
- self .span .add_event (Event .STREAM_END .value )
333
300
set_span_attribute (
334
301
self .span ,
335
302
SpanAttributes .LLM_USAGE_PROMPT_TOKENS ,
@@ -391,8 +358,6 @@ def process_chunk(self, chunk):
391
358
if not self .function_call and not self .tool_calls :
392
359
for choice in chunk .choices :
393
360
if choice .delta and choice .delta .content is not None :
394
- token_counts = estimate_tokens (choice .delta .content )
395
- self .completion_tokens += token_counts
396
361
content = [choice .delta .content ]
397
362
elif self .function_call :
398
363
for choice in chunk .choices :
@@ -401,10 +366,6 @@ def process_chunk(self, chunk):
401
366
and choice .delta .function_call is not None
402
367
and choice .delta .function_call .arguments is not None
403
368
):
404
- token_counts = estimate_tokens (
405
- choice .delta .function_call .arguments
406
- )
407
- self .completion_tokens += token_counts
408
369
content = [choice .delta .function_call .arguments ]
409
370
elif self .tool_calls :
410
371
for choice in chunk .choices :
@@ -417,40 +378,17 @@ def process_chunk(self, chunk):
417
378
and tool_call .function is not None
418
379
and tool_call .function .arguments is not None
419
380
):
420
- token_counts = estimate_tokens (
421
- tool_call .function .arguments
422
- )
423
- self .completion_tokens += token_counts
424
381
content .append (tool_call .function .arguments )
425
- set_event_completion_chunk (
426
- self .span ,
427
- (
428
- "" .join (content )
429
- if len (content ) > 0 and content [0 ] is not None
430
- else ""
431
- ),
432
- )
382
+
433
383
if content :
434
384
self .result_content .append (content [0 ])
435
385
436
386
if hasattr (chunk , "text" ):
437
- token_counts = estimate_tokens (chunk .text )
438
- self .completion_tokens += token_counts
439
387
content = [chunk .text ]
440
- set_event_completion_chunk (
441
- self .span ,
442
- (
443
- "" .join (content )
444
- if len (content ) > 0 and content [0 ] is not None
445
- else ""
446
- ),
447
- )
448
388
449
389
if content :
450
390
self .result_content .append (content [0 ])
451
391
452
- if hasattr (chunk , "usage_metadata" ):
453
- self .completion_tokens = (
454
- chunk .usage_metadata .candidates_token_count
455
- )
456
- self .prompt_tokens = chunk .usage_metadata .prompt_token_count
392
+ if getattr (chunk , "usage" ):
393
+ self .completion_tokens = chunk .usage .completion_tokens
394
+ self .prompt_tokens = chunk .usage .prompt_tokens
0 commit comments