deepeval 3.5.1__py3-none-any.whl → 3.5.3__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.
Files changed (39) hide show
  1. deepeval/_version.py +1 -1
  2. deepeval/config/settings.py +94 -2
  3. deepeval/config/utils.py +54 -1
  4. deepeval/constants.py +27 -0
  5. deepeval/integrations/langchain/__init__.py +2 -3
  6. deepeval/integrations/langchain/callback.py +126 -301
  7. deepeval/integrations/langchain/patch.py +24 -13
  8. deepeval/integrations/langchain/utils.py +203 -1
  9. deepeval/integrations/pydantic_ai/patcher.py +220 -185
  10. deepeval/integrations/pydantic_ai/utils.py +86 -0
  11. deepeval/metrics/conversational_g_eval/conversational_g_eval.py +1 -0
  12. deepeval/metrics/pii_leakage/pii_leakage.py +1 -1
  13. deepeval/models/embedding_models/azure_embedding_model.py +40 -9
  14. deepeval/models/embedding_models/local_embedding_model.py +54 -11
  15. deepeval/models/embedding_models/ollama_embedding_model.py +25 -7
  16. deepeval/models/embedding_models/openai_embedding_model.py +47 -5
  17. deepeval/models/llms/amazon_bedrock_model.py +31 -4
  18. deepeval/models/llms/anthropic_model.py +39 -13
  19. deepeval/models/llms/azure_model.py +37 -38
  20. deepeval/models/llms/deepseek_model.py +36 -7
  21. deepeval/models/llms/gemini_model.py +10 -0
  22. deepeval/models/llms/grok_model.py +50 -3
  23. deepeval/models/llms/kimi_model.py +37 -7
  24. deepeval/models/llms/local_model.py +38 -12
  25. deepeval/models/llms/ollama_model.py +15 -3
  26. deepeval/models/llms/openai_model.py +37 -44
  27. deepeval/models/mlllms/gemini_model.py +21 -3
  28. deepeval/models/mlllms/ollama_model.py +38 -13
  29. deepeval/models/mlllms/openai_model.py +18 -42
  30. deepeval/models/retry_policy.py +548 -64
  31. deepeval/prompt/api.py +13 -9
  32. deepeval/prompt/prompt.py +19 -9
  33. deepeval/tracing/tracing.py +87 -0
  34. deepeval/utils.py +12 -0
  35. {deepeval-3.5.1.dist-info → deepeval-3.5.3.dist-info}/METADATA +1 -1
  36. {deepeval-3.5.1.dist-info → deepeval-3.5.3.dist-info}/RECORD +39 -38
  37. {deepeval-3.5.1.dist-info → deepeval-3.5.3.dist-info}/LICENSE.md +0 -0
  38. {deepeval-3.5.1.dist-info → deepeval-3.5.3.dist-info}/WHEEL +0 -0
  39. {deepeval-3.5.1.dist-info → deepeval-3.5.3.dist-info}/entry_points.txt +0 -0
deepeval/_version.py CHANGED
@@ -1 +1 @@
1
- __version__: str = "3.5.1"
1
+ __version__: str = "3.5.3"
@@ -9,6 +9,7 @@ Central config for DeepEval.
9
9
  type coercion.
10
10
  """
11
11
 
12
+ import logging
12
13
  import os
13
14
  import re
14
15
 
@@ -16,11 +17,17 @@ from dotenv import dotenv_values
16
17
  from pathlib import Path
17
18
  from pydantic import AnyUrl, SecretStr, field_validator, confloat
18
19
  from pydantic_settings import BaseSettings, SettingsConfigDict
19
- from typing import Any, Dict, Optional, NamedTuple
20
+ from typing import Any, Dict, List, Optional, NamedTuple
20
21
 
21
- from deepeval.config.utils import parse_bool
22
+ from deepeval.config.utils import (
23
+ parse_bool,
24
+ coerce_to_list,
25
+ dedupe_preserve_order,
26
+ )
27
+ from deepeval.constants import SUPPORTED_PROVIDER_SLUGS, slugify
22
28
 
23
29
 
30
+ logger = logging.getLogger(__name__)
24
31
  _SAVE_RE = re.compile(r"^(?P<scheme>dotenv)(?::(?P<path>.+))?$")
25
32
 
26
33
 
@@ -264,6 +271,13 @@ class Settings(BaseSettings):
264
271
  LOCAL_EMBEDDING_MODEL_NAME: Optional[str] = None
265
272
  LOCAL_EMBEDDING_BASE_URL: Optional[AnyUrl] = None
266
273
 
274
+ #
275
+ # Retry Policy
276
+ #
277
+ DEEPEVAL_SDK_RETRY_PROVIDERS: Optional[List[str]] = None
278
+ DEEPEVAL_RETRY_BEFORE_LOG_LEVEL: Optional[int] = None # default -> INFO
279
+ DEEPEVAL_RETRY_AFTER_LOG_LEVEL: Optional[int] = None # default -> ERROR
280
+
267
281
  #
268
282
  # Telemetry and Debug
269
283
  #
@@ -283,6 +297,12 @@ class Settings(BaseSettings):
283
297
  CONFIDENT_SAMPLE_RATE: Optional[float] = 1.0
284
298
  OTEL_EXPORTER_OTLP_ENDPOINT: Optional[AnyUrl] = None
285
299
 
300
+ #
301
+ # Network
302
+ #
303
+ MEDIA_IMAGE_CONNECT_TIMEOUT_SECONDS: float = 3.05
304
+ MEDIA_IMAGE_READ_TIMEOUT_SECONDS: float = 10.0
305
+
286
306
  ##############
287
307
  # Validators #
288
308
  ##############
@@ -401,6 +421,78 @@ class Settings(BaseSettings):
401
421
  return None
402
422
  return s.upper()
403
423
 
424
+ @field_validator("DEEPEVAL_SDK_RETRY_PROVIDERS", mode="before")
425
+ @classmethod
426
+ def _coerce_to_list(cls, v):
427
+ # works with JSON list, comma/space/semicolon separated, or real lists
428
+ return coerce_to_list(v, lower=True)
429
+
430
+ @field_validator("DEEPEVAL_SDK_RETRY_PROVIDERS", mode="after")
431
+ @classmethod
432
+ def _validate_sdk_provider_list(cls, v):
433
+ if v is None:
434
+ return None
435
+
436
+ normalized: list[str] = []
437
+ star = False
438
+
439
+ for item in v:
440
+ s = str(item).strip()
441
+ if not s:
442
+ continue
443
+ if s == "*":
444
+ star = True
445
+ continue
446
+ s = slugify(s)
447
+ if s in SUPPORTED_PROVIDER_SLUGS:
448
+ normalized.append(s)
449
+ else:
450
+ if cls.DEEPEVAL_VERBOSE_MODE:
451
+ logger.warning("Unknown provider slug %r dropped", item)
452
+
453
+ if star:
454
+ return ["*"]
455
+
456
+ # It is important to dedup after normalization to catch variants
457
+ normalized = dedupe_preserve_order(normalized)
458
+ return normalized or None
459
+
460
+ @field_validator(
461
+ "DEEPEVAL_RETRY_BEFORE_LOG_LEVEL",
462
+ "DEEPEVAL_RETRY_AFTER_LOG_LEVEL",
463
+ mode="before",
464
+ )
465
+ @classmethod
466
+ def _coerce_log_level(cls, v):
467
+ if v is None:
468
+ return None
469
+ if isinstance(v, (int, float)):
470
+ return int(v)
471
+
472
+ s = str(v).strip().upper()
473
+ if not s:
474
+ return None
475
+
476
+ import logging
477
+
478
+ # Accept standard names or numeric strings
479
+ name_to_level = {
480
+ "CRITICAL": logging.CRITICAL,
481
+ "ERROR": logging.ERROR,
482
+ "WARNING": logging.WARNING,
483
+ "INFO": logging.INFO,
484
+ "DEBUG": logging.DEBUG,
485
+ "NOTSET": logging.NOTSET,
486
+ }
487
+ if s.isdigit() or (s.startswith("-") and s[1:].isdigit()):
488
+ return int(s)
489
+ if s in name_to_level:
490
+ return name_to_level[s]
491
+ raise ValueError(
492
+ "Retry log level must be one of DEBUG, INFO, WARNING, ERROR, "
493
+ "CRITICAL, NOTSET, or a numeric logging level."
494
+ )
495
+
404
496
  #######################
405
497
  # Persistence support #
406
498
  #######################
deepeval/config/utils.py CHANGED
@@ -1,8 +1,13 @@
1
+ import json
1
2
  import os
2
- from typing import Any, Optional
3
+ import re
4
+
5
+ from typing import Any, Iterable, List, Optional
6
+
3
7
 
4
8
  _TRUTHY = frozenset({"1", "true", "t", "yes", "y", "on", "enable", "enabled"})
5
9
  _FALSY = frozenset({"0", "false", "f", "no", "n", "off", "disable", "disabled"})
10
+ _LIST_SEP_RE = re.compile(r"[,\s;]+")
6
11
 
7
12
 
8
13
  def parse_bool(value: Any, default: bool = False) -> bool:
@@ -84,3 +89,51 @@ def set_env_bool(key: str, value: Optional[bool] = False) -> None:
84
89
  - Use `get_env_bool` to read back and parse the value safely.
85
90
  """
86
91
  os.environ[key] = bool_to_env_str(bool(value))
92
+
93
+
94
+ def coerce_to_list(
95
+ v,
96
+ *,
97
+ lower: bool = False,
98
+ allow_json: bool = True,
99
+ sep_re: re.Pattern = _LIST_SEP_RE,
100
+ ) -> Optional[List[str]]:
101
+ """
102
+ Coerce None / str / list / tuple / set into a clean List[str].
103
+ - Accepts JSON arrays ("[...]"") or delimited strings (comma/space/semicolon).
104
+ - Strips whitespace, drops empties, optionally lowercases.
105
+ """
106
+ if v is None:
107
+ return None
108
+ if isinstance(v, (list, tuple, set)):
109
+ items = list(v)
110
+ else:
111
+ s = str(v).strip()
112
+ if not s:
113
+ return None
114
+ if allow_json and s.startswith("[") and s.endswith("]"):
115
+ try:
116
+ parsed = json.loads(s)
117
+ items = parsed if isinstance(parsed, list) else [s]
118
+ except Exception:
119
+ items = sep_re.split(s)
120
+ else:
121
+ items = sep_re.split(s)
122
+
123
+ out: List[str] = []
124
+ for item in items:
125
+ s = str(item).strip()
126
+ if not s:
127
+ continue
128
+ out.append(s.lower() if lower else s)
129
+ return out or None
130
+
131
+
132
+ def dedupe_preserve_order(items: Iterable[str]) -> List[str]:
133
+ seen = set()
134
+ out: List[str] = []
135
+ for x in items:
136
+ if x not in seen:
137
+ seen.add(x)
138
+ out.append(x)
139
+ return out
deepeval/constants.py CHANGED
@@ -1,3 +1,5 @@
1
+ from enum import Enum
2
+
1
3
  KEY_FILE: str = ".deepeval"
2
4
  HIDDEN_DIR: str = ".deepeval"
3
5
  PYTEST_RUN_TEST_NAME: str = "CONFIDENT_AI_RUN_TEST_NAME"
@@ -11,3 +13,28 @@ CONFIDENT_TRACE_ENVIRONMENT = "CONFIDENT_TRACE_ENVIRONMENT"
11
13
  CONFIDENT_TRACING_ENABLED = "CONFIDENT_TRACING_ENABLED"
12
14
  CONFIDENT_OPEN_BROWSER = "CONFIDENT_OPEN_BROWSER"
13
15
  CONFIDENT_TEST_CASE_BATCH_SIZE = "CONFIDENT_TEST_CASE_BATCH_SIZE"
16
+
17
+
18
+ class ProviderSlug(str, Enum):
19
+ OPENAI = "openai"
20
+ AZURE = "azure"
21
+ ANTHROPIC = "anthropic"
22
+ BEDROCK = "bedrock"
23
+ DEEPSEEK = "deepseek"
24
+ GOOGLE = "google"
25
+ GROK = "grok"
26
+ KIMI = "kimi"
27
+ LITELLM = "litellm"
28
+ LOCAL = "local"
29
+ OLLAMA = "ollama"
30
+
31
+
32
+ def slugify(value: str | ProviderSlug) -> str:
33
+ return (
34
+ value.value
35
+ if isinstance(value, ProviderSlug)
36
+ else str(value).strip().lower()
37
+ )
38
+
39
+
40
+ SUPPORTED_PROVIDER_SLUGS = frozenset(s.value for s in ProviderSlug)
@@ -1,5 +1,4 @@
1
- from .callback import CallbackHandler
2
- from .patch import tool
1
+ from .callback import CallbackHandler, tool
3
2
 
4
3
 
5
- __all__ = ["CallbackHandler"]
4
+ __all__ = ["CallbackHandler", "tool"]