judgeval 0.16.6__py3-none-any.whl → 0.16.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of judgeval might be problematic. Click here for more details.

Files changed (43) hide show
  1. judgeval/api/api_types.py +1 -2
  2. judgeval/data/judgment_types.py +1 -2
  3. judgeval/tracer/__init__.py +7 -52
  4. judgeval/tracer/llm/config.py +12 -44
  5. judgeval/tracer/llm/constants.py +0 -1
  6. judgeval/tracer/llm/llm_anthropic/config.py +3 -17
  7. judgeval/tracer/llm/llm_anthropic/messages.py +440 -0
  8. judgeval/tracer/llm/llm_anthropic/messages_stream.py +322 -0
  9. judgeval/tracer/llm/llm_anthropic/wrapper.py +40 -621
  10. judgeval/tracer/llm/llm_google/__init__.py +3 -0
  11. judgeval/tracer/llm/llm_google/config.py +3 -21
  12. judgeval/tracer/llm/llm_google/generate_content.py +125 -0
  13. judgeval/tracer/llm/llm_google/wrapper.py +19 -454
  14. judgeval/tracer/llm/llm_openai/beta_chat_completions.py +192 -0
  15. judgeval/tracer/llm/llm_openai/chat_completions.py +437 -0
  16. judgeval/tracer/llm/llm_openai/config.py +3 -29
  17. judgeval/tracer/llm/llm_openai/responses.py +444 -0
  18. judgeval/tracer/llm/llm_openai/wrapper.py +43 -641
  19. judgeval/tracer/llm/llm_together/__init__.py +3 -0
  20. judgeval/tracer/llm/llm_together/chat_completions.py +398 -0
  21. judgeval/tracer/llm/llm_together/config.py +3 -20
  22. judgeval/tracer/llm/llm_together/wrapper.py +34 -485
  23. judgeval/tracer/llm/providers.py +4 -48
  24. judgeval/utils/decorators/dont_throw.py +30 -14
  25. judgeval/utils/wrappers/README.md +3 -0
  26. judgeval/utils/wrappers/__init__.py +15 -0
  27. judgeval/utils/wrappers/immutable_wrap_async.py +74 -0
  28. judgeval/utils/wrappers/immutable_wrap_async_iterator.py +84 -0
  29. judgeval/utils/wrappers/immutable_wrap_sync.py +66 -0
  30. judgeval/utils/wrappers/immutable_wrap_sync_iterator.py +84 -0
  31. judgeval/utils/wrappers/mutable_wrap_async.py +67 -0
  32. judgeval/utils/wrappers/mutable_wrap_sync.py +67 -0
  33. judgeval/utils/wrappers/utils.py +35 -0
  34. judgeval/version.py +1 -1
  35. {judgeval-0.16.6.dist-info → judgeval-0.16.8.dist-info}/METADATA +1 -1
  36. {judgeval-0.16.6.dist-info → judgeval-0.16.8.dist-info}/RECORD +40 -27
  37. judgeval/tracer/llm/llm_groq/config.py +0 -23
  38. judgeval/tracer/llm/llm_groq/wrapper.py +0 -498
  39. judgeval/tracer/local_eval_queue.py +0 -199
  40. /judgeval/{tracer/llm/llm_groq/__init__.py → utils/wrappers/py.typed} +0 -0
  41. {judgeval-0.16.6.dist-info → judgeval-0.16.8.dist-info}/WHEEL +0 -0
  42. {judgeval-0.16.6.dist-info → judgeval-0.16.8.dist-info}/entry_points.txt +0 -0
  43. {judgeval-0.16.6.dist-info → judgeval-0.16.8.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,24 +1,6 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING
2
+ import importlib.util
3
3
 
4
- if TYPE_CHECKING:
5
- from google.genai import Client
6
- from google.genai.client import AsyncClient
4
+ HAS_GOOGLE_GENAI = importlib.util.find_spec("google.genai") is not None
7
5
 
8
- try:
9
- from google.genai import Client
10
- from google.genai.client import AsyncClient
11
-
12
- HAS_GOOGLE_GENAI = True
13
- except ImportError:
14
- HAS_GOOGLE_GENAI = False
15
- Client = AsyncClient = None # type: ignore[misc,assignment]
16
-
17
- google_genai_Client = Client
18
- google_genai_AsyncClient = AsyncClient
19
-
20
- __all__ = [
21
- "HAS_GOOGLE_GENAI",
22
- "google_genai_Client",
23
- "google_genai_AsyncClient",
24
- ]
6
+ __all__ = ["HAS_GOOGLE_GENAI"]
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+ from typing import (
3
+ TYPE_CHECKING,
4
+ Any,
5
+ Dict,
6
+ Optional,
7
+ Tuple,
8
+ )
9
+
10
+ from judgeval.tracer.keys import AttributeKeys
11
+ from judgeval.tracer.utils import set_span_attribute
12
+ from judgeval.utils.serialize import safe_serialize
13
+ from judgeval.utils.wrappers import immutable_wrap_sync
14
+
15
+ if TYPE_CHECKING:
16
+ from judgeval.tracer import Tracer
17
+ from google.genai import Client
18
+ from google.genai.types import (
19
+ GenerateContentResponse,
20
+ GenerateContentResponseUsageMetadata,
21
+ )
22
+
23
+
24
+ def _extract_google_tokens(
25
+ usage: GenerateContentResponseUsageMetadata,
26
+ ) -> Tuple[int, int, int, int]:
27
+ prompt_tokens = (
28
+ usage.prompt_token_count if usage.prompt_token_count is not None else 0
29
+ )
30
+ completion_tokens = (
31
+ usage.candidates_token_count if usage.candidates_token_count is not None else 0
32
+ )
33
+ cache_read_input_tokens = (
34
+ usage.cached_content_token_count
35
+ if usage.cached_content_token_count is not None
36
+ else 0
37
+ )
38
+ cache_creation_input_tokens = 0
39
+ return (
40
+ prompt_tokens,
41
+ completion_tokens,
42
+ cache_read_input_tokens,
43
+ cache_creation_input_tokens,
44
+ )
45
+
46
+
47
+ def _format_google_output(
48
+ response: GenerateContentResponse,
49
+ ) -> Tuple[Optional[str], Optional[GenerateContentResponseUsageMetadata]]:
50
+ return response.text, response.usage_metadata
51
+
52
+
53
+ def wrap_generate_content_sync(tracer: Tracer, client: Client) -> None:
54
+ original_func = client.models.generate_content
55
+
56
+ def pre_hook(ctx: Dict[str, Any], *args: Any, **kwargs: Any) -> None:
57
+ ctx["span"] = tracer.get_tracer().start_span(
58
+ "GOOGLE_API_CALL", attributes={AttributeKeys.JUDGMENT_SPAN_KIND: "llm"}
59
+ )
60
+ tracer.add_agent_attributes_to_span(ctx["span"])
61
+ set_span_attribute(
62
+ ctx["span"], AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)
63
+ )
64
+ ctx["model_name"] = kwargs.get("model", "")
65
+ set_span_attribute(
66
+ ctx["span"], AttributeKeys.GEN_AI_REQUEST_MODEL, ctx["model_name"]
67
+ )
68
+
69
+ def post_hook(ctx: Dict[str, Any], result: GenerateContentResponse) -> None:
70
+ span = ctx.get("span")
71
+ if not span:
72
+ return
73
+
74
+ output, usage_data = _format_google_output(result)
75
+ set_span_attribute(span, AttributeKeys.GEN_AI_COMPLETION, output)
76
+
77
+ if usage_data:
78
+ prompt_tokens, completion_tokens, cache_read, cache_creation = (
79
+ _extract_google_tokens(usage_data)
80
+ )
81
+ set_span_attribute(
82
+ span, AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens
83
+ )
84
+ set_span_attribute(
85
+ span, AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS, completion_tokens
86
+ )
87
+ set_span_attribute(
88
+ span, AttributeKeys.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, cache_read
89
+ )
90
+ set_span_attribute(
91
+ span,
92
+ AttributeKeys.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
93
+ cache_creation,
94
+ )
95
+ set_span_attribute(
96
+ span,
97
+ AttributeKeys.JUDGMENT_USAGE_METADATA,
98
+ safe_serialize(usage_data),
99
+ )
100
+
101
+ set_span_attribute(
102
+ span,
103
+ AttributeKeys.GEN_AI_RESPONSE_MODEL,
104
+ result.model_version if result.model_version else ctx["model_name"],
105
+ )
106
+
107
+ def error_hook(ctx: Dict[str, Any], error: Exception) -> None:
108
+ span = ctx.get("span")
109
+ if span:
110
+ span.record_exception(error)
111
+
112
+ def finally_hook(ctx: Dict[str, Any]) -> None:
113
+ span = ctx.get("span")
114
+ if span:
115
+ span.end()
116
+
117
+ wrapped = immutable_wrap_sync(
118
+ original_func,
119
+ pre_hook=pre_hook,
120
+ post_hook=post_hook,
121
+ error_hook=error_hook,
122
+ finally_hook=finally_hook,
123
+ )
124
+
125
+ setattr(client.models, "generate_content", wrapped)
@@ -1,465 +1,30 @@
1
1
  from __future__ import annotations
2
- import functools
3
- from typing import (
4
- TYPE_CHECKING,
5
- Callable,
6
- Optional,
7
- Protocol,
8
- Tuple,
9
- Union,
10
- Iterator,
11
- AsyncIterator,
12
- Sequence,
13
- runtime_checkable,
14
- )
2
+ from typing import TYPE_CHECKING
15
3
 
16
- from judgeval.tracer.llm.llm_google.config import (
17
- google_genai_Client,
18
- google_genai_AsyncClient,
4
+ from judgeval.tracer.llm.llm_google.generate_content import (
5
+ wrap_generate_content_sync,
19
6
  )
20
- from judgeval.tracer.managers import sync_span_context, async_span_context
21
- from judgeval.logger import judgeval_logger
22
- from judgeval.tracer.keys import AttributeKeys
23
- from judgeval.tracer.utils import set_span_attribute
24
- from judgeval.utils.serialize import safe_serialize
25
7
 
26
8
  if TYPE_CHECKING:
27
9
  from judgeval.tracer import Tracer
28
- from opentelemetry.trace import Span
29
-
30
- # Keep the original client type for runtime compatibility
31
- GoogleClientType = Union[google_genai_Client, google_genai_AsyncClient]
32
-
33
-
34
- # Usage protocols
35
- @runtime_checkable
36
- class GoogleUsageMetadata(Protocol):
37
- prompt_token_count: Optional[int]
38
- candidates_token_count: Optional[int]
39
- total_token_count: Optional[int]
40
- cached_content_token_count: Optional[int]
41
-
42
-
43
- # Content protocols
44
- @runtime_checkable
45
- class GooglePart(Protocol):
46
- text: str
47
-
48
-
49
- @runtime_checkable
50
- class GoogleContent(Protocol):
51
- parts: Sequence[GooglePart]
52
-
53
-
54
- @runtime_checkable
55
- class GoogleCandidate(Protocol):
56
- content: GoogleContent
57
- finish_reason: Optional[str]
58
-
59
-
60
- @runtime_checkable
61
- class GoogleGenerateContentResponse(Protocol):
62
- candidates: Sequence[GoogleCandidate]
63
- usage_metadata: Optional[GoogleUsageMetadata]
64
- model_version: Optional[str]
65
-
66
-
67
- # Stream protocols
68
- @runtime_checkable
69
- class GoogleStreamChunk(Protocol):
70
- candidates: Sequence[GoogleCandidate]
71
- usage_metadata: Optional[GoogleUsageMetadata]
72
-
73
-
74
- # Client protocols
75
- @runtime_checkable
76
- class GoogleClient(Protocol):
77
- pass
78
-
79
-
80
- @runtime_checkable
81
- class GoogleAsyncClient(Protocol):
82
- pass
83
-
84
-
85
- # Union types
86
- GoogleResponseType = GoogleGenerateContentResponse
87
- GoogleStreamType = Union[Iterator[GoogleStreamChunk], AsyncIterator[GoogleStreamChunk]]
88
-
89
-
90
- def _extract_google_content(chunk: GoogleStreamChunk) -> str:
91
- if chunk.candidates and len(chunk.candidates) > 0:
92
- candidate = chunk.candidates[0]
93
- if (
94
- candidate.content
95
- and candidate.content.parts
96
- and len(candidate.content.parts) > 0
97
- ):
98
- return candidate.content.parts[0].text or ""
99
- return ""
100
-
10
+ from google.genai import Client
101
11
 
102
- def _extract_google_tokens(
103
- usage_data: GoogleUsageMetadata,
104
- ) -> Tuple[int, int, int, int]:
105
- prompt_tokens = usage_data.prompt_token_count or 0
106
- completion_tokens = usage_data.candidates_token_count or 0
107
- cache_read_input_tokens = usage_data.cached_content_token_count or 0
108
- cache_creation_input_tokens = 0 # Google GenAI doesn't have cache creation tokens
109
- return (
110
- prompt_tokens,
111
- completion_tokens,
112
- cache_read_input_tokens,
113
- cache_creation_input_tokens,
114
- )
115
12
 
13
+ def wrap_google_client(tracer: Tracer, client: Client) -> Client:
14
+ from judgeval.tracer.llm.llm_google.config import HAS_GOOGLE_GENAI
15
+ from judgeval.logger import judgeval_logger
116
16
 
117
- def _format_google_output(
118
- response: GoogleGenerateContentResponse,
119
- ) -> Tuple[Optional[str], Optional[GoogleUsageMetadata]]:
120
- message_content: Optional[str] = None
121
- usage_data: Optional[GoogleUsageMetadata] = None
122
-
123
- try:
124
- if isinstance(response, GoogleGenerateContentResponse):
125
- usage_data = response.usage_metadata
126
- if response.candidates and len(response.candidates) > 0:
127
- candidate = response.candidates[0]
128
- if (
129
- candidate.content
130
- and candidate.content.parts
131
- and len(candidate.content.parts) > 0
132
- ):
133
- message_content = candidate.content.parts[0].text
134
- except (AttributeError, IndexError, TypeError):
135
- pass
136
-
137
- return message_content, usage_data
138
-
139
-
140
- class TracedGoogleGenerator:
141
- def __init__(
142
- self,
143
- tracer: Tracer,
144
- generator: Iterator[GoogleStreamChunk],
145
- client: GoogleClientType,
146
- span: Span,
147
- model_name: str,
148
- ):
149
- self.tracer = tracer
150
- self.generator = generator
151
- self.client = client
152
- self.span = span
153
- self.model_name = model_name
154
- self.accumulated_content = ""
155
-
156
- def __iter__(self) -> Iterator[GoogleStreamChunk]:
157
- return self
158
-
159
- def __next__(self) -> GoogleStreamChunk:
160
- try:
161
- chunk = next(self.generator)
162
- content = _extract_google_content(chunk)
163
- if content:
164
- self.accumulated_content += content
165
- if chunk.usage_metadata:
166
- prompt_tokens, completion_tokens, cache_read, cache_creation = (
167
- _extract_google_tokens(chunk.usage_metadata)
168
- )
169
- set_span_attribute(
170
- self.span, AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens
171
- )
172
- set_span_attribute(
173
- self.span,
174
- AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS,
175
- completion_tokens,
176
- )
177
- set_span_attribute(
178
- self.span,
179
- AttributeKeys.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
180
- cache_read,
181
- )
182
- set_span_attribute(
183
- self.span,
184
- AttributeKeys.JUDGMENT_USAGE_METADATA,
185
- safe_serialize(chunk.usage_metadata),
186
- )
187
- return chunk
188
- except StopIteration:
189
- set_span_attribute(
190
- self.span, AttributeKeys.GEN_AI_COMPLETION, self.accumulated_content
191
- )
192
- self.span.end()
193
- raise
194
- except Exception as e:
195
- if self.span:
196
- self.span.record_exception(e)
197
- self.span.end()
198
- raise
199
-
200
-
201
- class TracedGoogleAsyncGenerator:
202
- def __init__(
203
- self,
204
- tracer: Tracer,
205
- async_generator: AsyncIterator[GoogleStreamChunk],
206
- client: GoogleClientType,
207
- span: Span,
208
- model_name: str,
209
- ):
210
- self.tracer = tracer
211
- self.async_generator = async_generator
212
- self.client = client
213
- self.span = span
214
- self.model_name = model_name
215
- self.accumulated_content = ""
216
-
217
- def __aiter__(self) -> AsyncIterator[GoogleStreamChunk]:
218
- return self
219
-
220
- async def __anext__(self) -> GoogleStreamChunk:
221
- try:
222
- chunk = await self.async_generator.__anext__()
223
- content = _extract_google_content(chunk)
224
- if content:
225
- self.accumulated_content += content
226
- if chunk.usage_metadata:
227
- prompt_tokens, completion_tokens, cache_read, cache_creation = (
228
- _extract_google_tokens(chunk.usage_metadata)
229
- )
230
- set_span_attribute(
231
- self.span, AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens
232
- )
233
- set_span_attribute(
234
- self.span,
235
- AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS,
236
- completion_tokens,
237
- )
238
- set_span_attribute(
239
- self.span,
240
- AttributeKeys.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
241
- cache_read,
242
- )
243
- set_span_attribute(
244
- self.span,
245
- AttributeKeys.JUDGMENT_USAGE_METADATA,
246
- safe_serialize(chunk.usage_metadata),
247
- )
248
- return chunk
249
- except StopAsyncIteration:
250
- set_span_attribute(
251
- self.span, AttributeKeys.GEN_AI_COMPLETION, self.accumulated_content
252
- )
253
- self.span.end()
254
- raise
255
- except Exception as e:
256
- if self.span:
257
- self.span.record_exception(e)
258
- self.span.end()
259
- raise
260
-
261
-
262
- def wrap_google_client(tracer: Tracer, client: GoogleClientType) -> GoogleClientType:
263
- def wrapped(function: Callable, span_name: str):
264
- @functools.wraps(function)
265
- def wrapper(*args, **kwargs):
266
- if kwargs.get("stream", False):
267
- try:
268
- span = tracer.get_tracer().start_span(
269
- span_name, attributes={AttributeKeys.JUDGMENT_SPAN_KIND: "llm"}
270
- )
271
- tracer.add_agent_attributes_to_span(span)
272
- set_span_attribute(
273
- span, AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)
274
- )
275
- model_name = kwargs.get("model", "")
276
- set_span_attribute(
277
- span, AttributeKeys.GEN_AI_REQUEST_MODEL, model_name
278
- )
279
- except Exception as e:
280
- judgeval_logger.error(
281
- f"[google wrapped] Error adding span metadata: {e}"
282
- )
283
- stream_response = function(*args, **kwargs)
284
- return TracedGoogleGenerator(
285
- tracer, stream_response, client, span, model_name
286
- )
287
- else:
288
- with sync_span_context(
289
- tracer, span_name, {AttributeKeys.JUDGMENT_SPAN_KIND: "llm"}
290
- ) as span:
291
- try:
292
- tracer.add_agent_attributes_to_span(span)
293
- set_span_attribute(
294
- span, AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)
295
- )
296
- model_name = kwargs.get("model", "")
297
- set_span_attribute(
298
- span, AttributeKeys.GEN_AI_REQUEST_MODEL, model_name
299
- )
300
- except Exception as e:
301
- judgeval_logger.error(
302
- f"[google wrapped] Error adding span metadata: {e}"
303
- )
304
-
305
- response = function(*args, **kwargs)
306
-
307
- try:
308
- if isinstance(response, GoogleGenerateContentResponse):
309
- output, usage_data = _format_google_output(response)
310
- set_span_attribute(
311
- span, AttributeKeys.GEN_AI_COMPLETION, output
312
- )
313
- if usage_data:
314
- (
315
- prompt_tokens,
316
- completion_tokens,
317
- cache_read,
318
- cache_creation,
319
- ) = _extract_google_tokens(usage_data)
320
- set_span_attribute(
321
- span,
322
- AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS,
323
- prompt_tokens,
324
- )
325
- set_span_attribute(
326
- span,
327
- AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS,
328
- completion_tokens,
329
- )
330
- set_span_attribute(
331
- span,
332
- AttributeKeys.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
333
- cache_read,
334
- )
335
- set_span_attribute(
336
- span,
337
- AttributeKeys.JUDGMENT_USAGE_METADATA,
338
- safe_serialize(usage_data),
339
- )
340
- set_span_attribute(
341
- span,
342
- AttributeKeys.GEN_AI_RESPONSE_MODEL,
343
- getattr(response, "model_version", model_name),
344
- )
345
- except Exception as e:
346
- judgeval_logger.error(
347
- f"[google wrapped] Error adding span metadata: {e}"
348
- )
349
- finally:
350
- return response
351
-
352
- return wrapper
353
-
354
- def wrapped_async(function: Callable, span_name: str):
355
- @functools.wraps(function)
356
- async def wrapper(*args, **kwargs):
357
- if kwargs.get("stream", False):
358
- try:
359
- span = tracer.get_tracer().start_span(
360
- span_name, attributes={AttributeKeys.JUDGMENT_SPAN_KIND: "llm"}
361
- )
362
- tracer.add_agent_attributes_to_span(span)
363
- set_span_attribute(
364
- span, AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)
365
- )
366
- model_name = kwargs.get("model", "")
367
- set_span_attribute(
368
- span, AttributeKeys.GEN_AI_REQUEST_MODEL, model_name
369
- )
370
- except Exception as e:
371
- judgeval_logger.error(
372
- f"[google wrapped_async] Error adding span metadata: {e}"
373
- )
374
- stream_response = await function(*args, **kwargs)
375
- return TracedGoogleAsyncGenerator(
376
- tracer, stream_response, client, span, model_name
377
- )
378
- else:
379
- async with async_span_context(
380
- tracer, span_name, {AttributeKeys.JUDGMENT_SPAN_KIND: "llm"}
381
- ) as span:
382
- try:
383
- tracer.add_agent_attributes_to_span(span)
384
- set_span_attribute(
385
- span, AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)
386
- )
387
- model_name = kwargs.get("model", "")
388
- set_span_attribute(
389
- span, AttributeKeys.GEN_AI_REQUEST_MODEL, model_name
390
- )
391
- except Exception as e:
392
- judgeval_logger.error(
393
- f"[google wrapped_async] Error adding span metadata: {e}"
394
- )
395
-
396
- response = await function(*args, **kwargs)
397
-
398
- try:
399
- if isinstance(response, GoogleGenerateContentResponse):
400
- output, usage_data = _format_google_output(response)
401
- set_span_attribute(
402
- span, AttributeKeys.GEN_AI_COMPLETION, output
403
- )
404
- if usage_data:
405
- (
406
- prompt_tokens,
407
- completion_tokens,
408
- cache_read,
409
- cache_creation,
410
- ) = _extract_google_tokens(usage_data)
411
- set_span_attribute(
412
- span,
413
- AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS,
414
- prompt_tokens,
415
- )
416
- set_span_attribute(
417
- span,
418
- AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS,
419
- completion_tokens,
420
- )
421
- set_span_attribute(
422
- span,
423
- AttributeKeys.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
424
- cache_read,
425
- )
426
- set_span_attribute(
427
- span,
428
- AttributeKeys.JUDGMENT_USAGE_METADATA,
429
- safe_serialize(usage_data),
430
- )
431
- set_span_attribute(
432
- span,
433
- AttributeKeys.GEN_AI_RESPONSE_MODEL,
434
- getattr(response, "model_version", model_name),
435
- )
436
- except Exception as e:
437
- judgeval_logger.error(
438
- f"[google wrapped_async] Error adding span metadata: {e}"
439
- )
440
- finally:
441
- return response
442
-
443
- return wrapper
444
-
445
- span_name = "GOOGLE_API_CALL"
446
- if google_genai_Client is not None and isinstance(client, google_genai_Client):
447
- # Type narrowing for mypy
448
- google_client = client # type: ignore[assignment]
449
- setattr(
450
- google_client.models,
451
- "generate_content",
452
- wrapped(google_client.models.generate_content, span_name),
453
- )
454
- elif google_genai_AsyncClient is not None and isinstance(
455
- client, google_genai_AsyncClient
456
- ):
457
- # Type narrowing for mypy
458
- async_google_client = client # type: ignore[assignment]
459
- setattr(
460
- async_google_client.models,
461
- "generate_content",
462
- wrapped_async(async_google_client.models.generate_content, span_name),
17
+ if not HAS_GOOGLE_GENAI:
18
+ judgeval_logger.error(
19
+ "Cannot wrap Google GenAI client: 'google-genai' library not installed. "
20
+ "Install it with: pip install google-genai"
463
21
  )
22
+ return client
23
+
24
+ from google.genai import Client
464
25
 
465
- return client
26
+ if isinstance(client, Client):
27
+ wrap_generate_content_sync(tracer, client)
28
+ return client
29
+ else:
30
+ raise TypeError(f"Invalid client type: {type(client)}")