1
1
from __future__ import annotations
2
2
import functools
3
- import sys
4
3
from typing import Callable , Tuple , Optional , Any , TYPE_CHECKING
5
4
from functools import wraps
6
5
from judgeval .data .trace import TraceUsage
@@ -55,6 +54,9 @@ def wrapper(*args, **kwargs):
55
54
with sync_span_context (
56
55
tracer , span_name , {AttributeKeys .SPAN_TYPE : "llm" }
57
56
) as span :
57
+ tracer .add_agent_attributes_to_span (
58
+ span , {AttributeKeys .SPAN_TYPE : "llm" }
59
+ )
58
60
span .set_attribute (AttributeKeys .GEN_AI_PROMPT , safe_serialize (kwargs ))
59
61
try :
60
62
response = function (* args , ** kwargs )
@@ -76,6 +78,18 @@ def wrapper(*args, **kwargs):
76
78
AttributeKeys .GEN_AI_USAGE_COMPLETION_TOKENS ,
77
79
usage .completion_tokens ,
78
80
)
81
+ if usage .total_tokens :
82
+ span .set_attribute (
83
+ AttributeKeys .GEN_AI_USAGE_TOTAL_TOKENS ,
84
+ usage .total_tokens ,
85
+ )
86
+ if usage .total_cost_usd :
87
+ span .set_attribute (
88
+ AttributeKeys .GEN_AI_USAGE_TOTAL_COST ,
89
+ usage .total_cost_usd ,
90
+ )
91
+ # Add cost to cumulative context tracking
92
+ tracer .add_cost_to_current_context (usage .total_cost_usd )
79
93
return response
80
94
except Exception as e :
81
95
span .record_exception (e )
@@ -89,6 +103,9 @@ async def wrapper(*args, **kwargs):
89
103
async with async_span_context (
90
104
tracer , span_name , {AttributeKeys .SPAN_TYPE : "llm" }
91
105
) as span :
106
+ tracer .add_agent_attributes_to_span (
107
+ span , {AttributeKeys .SPAN_TYPE : "llm" }
108
+ )
92
109
span .set_attribute (AttributeKeys .GEN_AI_PROMPT , safe_serialize (kwargs ))
93
110
try :
94
111
response = await function (* args , ** kwargs )
@@ -110,6 +127,17 @@ async def wrapper(*args, **kwargs):
110
127
AttributeKeys .GEN_AI_USAGE_COMPLETION_TOKENS ,
111
128
usage .completion_tokens ,
112
129
)
130
+ if usage .total_tokens :
131
+ span .set_attribute (
132
+ AttributeKeys .GEN_AI_USAGE_TOTAL_TOKENS ,
133
+ usage .total_tokens ,
134
+ )
135
+ if usage .total_cost_usd :
136
+ span .set_attribute (
137
+ AttributeKeys .GEN_AI_USAGE_TOTAL_COST ,
138
+ usage .total_cost_usd ,
139
+ )
140
+ tracer .add_cost_to_current_context (usage .total_cost_usd )
113
141
return response
114
142
except Exception as e :
115
143
span .record_exception (e )
@@ -160,9 +188,9 @@ async def wrapper(*args, **kwargs):
160
188
)
161
189
162
190
assert google_genai_Client is not None , "Google GenAI client not found"
163
- assert (
164
- google_genai_AsyncClient is not None
165
- ), "Google GenAI async client not found"
191
+ assert google_genai_AsyncClient is not None , (
192
+ "Google GenAI async client not found"
193
+ )
166
194
if isinstance (client , google_genai_Client ):
167
195
setattr (client .models , "generate_content" , wrapped (original_create ))
168
196
elif isinstance (client , google_genai_AsyncClient ):
@@ -225,9 +253,9 @@ def _get_client_config(client: ApiClient) -> tuple[str, Callable]:
225
253
)
226
254
227
255
assert google_genai_Client is not None , "Google GenAI client not found"
228
- assert (
229
- google_genai_AsyncClient is not None
230
- ), "Google GenAI async client not found"
256
+ assert google_genai_AsyncClient is not None , (
257
+ "Google GenAI async client not found"
258
+ )
231
259
if isinstance (client , google_genai_Client ):
232
260
return "GOOGLE_API_CALL" , client .models .generate_content
233
261
elif isinstance (client , google_genai_AsyncClient ):
@@ -269,9 +297,9 @@ def _format_output_data(
269
297
assert openai_AsyncOpenAI is not None , "OpenAI async client not found"
270
298
assert openai_ChatCompletion is not None , "OpenAI chat completion not found"
271
299
assert openai_Response is not None , "OpenAI response not found"
272
- assert (
273
- openai_ParsedChatCompletion is not None
274
- ), "OpenAI parsed chat completion not found"
300
+ assert openai_ParsedChatCompletion is not None , (
301
+ "OpenAI parsed chat completion not found"
302
+ )
275
303
276
304
if isinstance (client , openai_OpenAI ) or isinstance (client , openai_AsyncOpenAI ):
277
305
if isinstance (response , openai_ChatCompletion ):
@@ -318,7 +346,11 @@ def _format_output_data(
318
346
else 0
319
347
)
320
348
output0 = response .output [0 ]
321
- if hasattr (output0 , "content" ) and output0 .content and hasattr (output0 .content , "__iter__" ): # type: ignore[attr-defined]
349
+ if (
350
+ hasattr (output0 , "content" )
351
+ and output0 .content
352
+ and hasattr (output0 .content , "__iter__" )
353
+ ): # type: ignore[attr-defined]
322
354
message_content = "" .join (
323
355
seg .text # type: ignore[attr-defined]
324
356
for seg in output0 .content # type: ignore[attr-defined]
@@ -346,9 +378,23 @@ def _format_output_data(
346
378
client , together_AsyncTogether
347
379
):
348
380
model_name = (response .model or "" ) if hasattr (response , "model" ) else ""
349
- prompt_tokens = response .usage .prompt_tokens if hasattr (response .usage , "prompt_tokens" ) and response .usage .prompt_tokens is not None else 0 # type: ignore[attr-defined]
350
- completion_tokens = response .usage .completion_tokens if hasattr (response .usage , "completion_tokens" ) and response .usage .completion_tokens is not None else 0 # type: ignore[attr-defined]
351
- message_content = response .choices [0 ].message .content if hasattr (response , "choices" ) else None # type: ignore[attr-defined]
381
+ prompt_tokens = (
382
+ response .usage .prompt_tokens
383
+ if hasattr (response .usage , "prompt_tokens" )
384
+ and response .usage .prompt_tokens is not None
385
+ else 0
386
+ ) # type: ignore[attr-defined]
387
+ completion_tokens = (
388
+ response .usage .completion_tokens
389
+ if hasattr (response .usage , "completion_tokens" )
390
+ and response .usage .completion_tokens is not None
391
+ else 0
392
+ ) # type: ignore[attr-defined]
393
+ message_content = (
394
+ response .choices [0 ].message .content
395
+ if hasattr (response , "choices" )
396
+ else None
397
+ ) # type: ignore[attr-defined]
352
398
353
399
if model_name :
354
400
return message_content , _create_usage (
@@ -366,9 +412,9 @@ def _format_output_data(
366
412
)
367
413
368
414
assert google_genai_Client is not None , "Google GenAI client not found"
369
- assert (
370
- google_genai_AsyncClient is not None
371
- ), "Google GenAI async client not found"
415
+ assert google_genai_AsyncClient is not None , (
416
+ "Google GenAI async client not found"
417
+ )
372
418
if isinstance (client , google_genai_Client ) or isinstance (
373
419
client , google_genai_AsyncClient
374
420
):
@@ -467,9 +513,23 @@ def _format_output_data(
467
513
assert groq_AsyncGroq is not None , "Groq async client not found"
468
514
if isinstance (client , groq_Groq ) or isinstance (client , groq_AsyncGroq ):
469
515
model_name = (response .model or "" ) if hasattr (response , "model" ) else ""
470
- prompt_tokens = response .usage .prompt_tokens if hasattr (response .usage , "prompt_tokens" ) and response .usage .prompt_tokens is not None else 0 # type: ignore[attr-defined]
471
- completion_tokens = response .usage .completion_tokens if hasattr (response .usage , "completion_tokens" ) and response .usage .completion_tokens is not None else 0 # type: ignore[attr-defined]
472
- message_content = response .choices [0 ].message .content if hasattr (response , "choices" ) else None # type: ignore[attr-defined]
516
+ prompt_tokens = (
517
+ response .usage .prompt_tokens
518
+ if hasattr (response .usage , "prompt_tokens" )
519
+ and response .usage .prompt_tokens is not None
520
+ else 0
521
+ ) # type: ignore[attr-defined]
522
+ completion_tokens = (
523
+ response .usage .completion_tokens
524
+ if hasattr (response .usage , "completion_tokens" )
525
+ and response .usage .completion_tokens is not None
526
+ else 0
527
+ ) # type: ignore[attr-defined]
528
+ message_content = (
529
+ response .choices [0 ].message .content
530
+ if hasattr (response , "choices" )
531
+ else None
532
+ ) # type: ignore[attr-defined]
473
533
474
534
if model_name :
475
535
return message_content , _create_usage (
0 commit comments