azure-ai-evaluation 1.8.0__py3-none-any.whl → 1.10.0__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 azure-ai-evaluation might be problematic. Click here for more details.

Files changed (142) hide show
  1. azure/ai/evaluation/__init__.py +51 -6
  2. azure/ai/evaluation/_aoai/__init__.py +1 -1
  3. azure/ai/evaluation/_aoai/aoai_grader.py +21 -11
  4. azure/ai/evaluation/_aoai/label_grader.py +3 -2
  5. azure/ai/evaluation/_aoai/python_grader.py +84 -0
  6. azure/ai/evaluation/_aoai/score_model_grader.py +91 -0
  7. azure/ai/evaluation/_aoai/string_check_grader.py +3 -2
  8. azure/ai/evaluation/_aoai/text_similarity_grader.py +3 -2
  9. azure/ai/evaluation/_azure/_envs.py +9 -10
  10. azure/ai/evaluation/_azure/_token_manager.py +7 -1
  11. azure/ai/evaluation/_common/constants.py +11 -2
  12. azure/ai/evaluation/_common/evaluation_onedp_client.py +32 -26
  13. azure/ai/evaluation/_common/onedp/__init__.py +32 -32
  14. azure/ai/evaluation/_common/onedp/_client.py +136 -139
  15. azure/ai/evaluation/_common/onedp/_configuration.py +70 -73
  16. azure/ai/evaluation/_common/onedp/_patch.py +21 -21
  17. azure/ai/evaluation/_common/onedp/_utils/__init__.py +6 -0
  18. azure/ai/evaluation/_common/onedp/_utils/model_base.py +1232 -0
  19. azure/ai/evaluation/_common/onedp/_utils/serialization.py +2032 -0
  20. azure/ai/evaluation/_common/onedp/_validation.py +50 -50
  21. azure/ai/evaluation/_common/onedp/_version.py +9 -9
  22. azure/ai/evaluation/_common/onedp/aio/__init__.py +29 -29
  23. azure/ai/evaluation/_common/onedp/aio/_client.py +138 -143
  24. azure/ai/evaluation/_common/onedp/aio/_configuration.py +70 -75
  25. azure/ai/evaluation/_common/onedp/aio/_patch.py +21 -21
  26. azure/ai/evaluation/_common/onedp/aio/operations/__init__.py +37 -39
  27. azure/ai/evaluation/_common/onedp/aio/operations/_operations.py +4832 -4494
  28. azure/ai/evaluation/_common/onedp/aio/operations/_patch.py +21 -21
  29. azure/ai/evaluation/_common/onedp/models/__init__.py +168 -142
  30. azure/ai/evaluation/_common/onedp/models/_enums.py +230 -162
  31. azure/ai/evaluation/_common/onedp/models/_models.py +2685 -2228
  32. azure/ai/evaluation/_common/onedp/models/_patch.py +21 -21
  33. azure/ai/evaluation/_common/onedp/operations/__init__.py +37 -39
  34. azure/ai/evaluation/_common/onedp/operations/_operations.py +6106 -5657
  35. azure/ai/evaluation/_common/onedp/operations/_patch.py +21 -21
  36. azure/ai/evaluation/_common/rai_service.py +88 -52
  37. azure/ai/evaluation/_common/raiclient/__init__.py +1 -1
  38. azure/ai/evaluation/_common/raiclient/operations/_operations.py +14 -1
  39. azure/ai/evaluation/_common/utils.py +188 -10
  40. azure/ai/evaluation/_constants.py +2 -1
  41. azure/ai/evaluation/_converters/__init__.py +1 -1
  42. azure/ai/evaluation/_converters/_ai_services.py +9 -8
  43. azure/ai/evaluation/_converters/_models.py +46 -0
  44. azure/ai/evaluation/_converters/_sk_services.py +495 -0
  45. azure/ai/evaluation/_eval_mapping.py +2 -2
  46. azure/ai/evaluation/_evaluate/_batch_run/_run_submitter_client.py +73 -25
  47. azure/ai/evaluation/_evaluate/_batch_run/eval_run_context.py +2 -2
  48. azure/ai/evaluation/_evaluate/_evaluate.py +210 -94
  49. azure/ai/evaluation/_evaluate/_evaluate_aoai.py +132 -89
  50. azure/ai/evaluation/_evaluate/_telemetry/__init__.py +0 -1
  51. azure/ai/evaluation/_evaluate/_utils.py +25 -17
  52. azure/ai/evaluation/_evaluators/_bleu/_bleu.py +4 -4
  53. azure/ai/evaluation/_evaluators/_code_vulnerability/_code_vulnerability.py +20 -12
  54. azure/ai/evaluation/_evaluators/_coherence/_coherence.py +6 -6
  55. azure/ai/evaluation/_evaluators/_common/_base_eval.py +45 -11
  56. azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +24 -9
  57. azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +24 -9
  58. azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +28 -18
  59. azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +11 -8
  60. azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +11 -8
  61. azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +12 -9
  62. azure/ai/evaluation/_evaluators/_content_safety/_violence.py +10 -7
  63. azure/ai/evaluation/_evaluators/_document_retrieval/__init__.py +1 -5
  64. azure/ai/evaluation/_evaluators/_document_retrieval/_document_retrieval.py +37 -64
  65. azure/ai/evaluation/_evaluators/_eci/_eci.py +6 -3
  66. azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +5 -5
  67. azure/ai/evaluation/_evaluators/_fluency/_fluency.py +3 -3
  68. azure/ai/evaluation/_evaluators/_gleu/_gleu.py +4 -4
  69. azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +12 -8
  70. azure/ai/evaluation/_evaluators/_intent_resolution/_intent_resolution.py +31 -26
  71. azure/ai/evaluation/_evaluators/_intent_resolution/intent_resolution.prompty +210 -96
  72. azure/ai/evaluation/_evaluators/_meteor/_meteor.py +3 -4
  73. azure/ai/evaluation/_evaluators/_protected_material/_protected_material.py +14 -7
  74. azure/ai/evaluation/_evaluators/_qa/_qa.py +5 -5
  75. azure/ai/evaluation/_evaluators/_relevance/_relevance.py +62 -15
  76. azure/ai/evaluation/_evaluators/_relevance/relevance.prompty +140 -59
  77. azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.py +21 -26
  78. azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py +5 -5
  79. azure/ai/evaluation/_evaluators/_rouge/_rouge.py +22 -22
  80. azure/ai/evaluation/_evaluators/_service_groundedness/_service_groundedness.py +7 -6
  81. azure/ai/evaluation/_evaluators/_similarity/_similarity.py +4 -4
  82. azure/ai/evaluation/_evaluators/_task_adherence/_task_adherence.py +27 -24
  83. azure/ai/evaluation/_evaluators/_task_adherence/task_adherence.prompty +354 -66
  84. azure/ai/evaluation/_evaluators/_tool_call_accuracy/_tool_call_accuracy.py +175 -183
  85. azure/ai/evaluation/_evaluators/_tool_call_accuracy/tool_call_accuracy.prompty +99 -21
  86. azure/ai/evaluation/_evaluators/_ungrounded_attributes/_ungrounded_attributes.py +20 -12
  87. azure/ai/evaluation/_evaluators/_xpia/xpia.py +10 -7
  88. azure/ai/evaluation/_exceptions.py +10 -0
  89. azure/ai/evaluation/_http_utils.py +3 -3
  90. azure/ai/evaluation/_legacy/_batch_engine/_config.py +6 -3
  91. azure/ai/evaluation/_legacy/_batch_engine/_engine.py +117 -32
  92. azure/ai/evaluation/_legacy/_batch_engine/_openai_injector.py +5 -2
  93. azure/ai/evaluation/_legacy/_batch_engine/_result.py +2 -0
  94. azure/ai/evaluation/_legacy/_batch_engine/_run.py +2 -2
  95. azure/ai/evaluation/_legacy/_batch_engine/_run_submitter.py +33 -41
  96. azure/ai/evaluation/_legacy/_batch_engine/_utils.py +1 -4
  97. azure/ai/evaluation/_legacy/_common/_async_token_provider.py +12 -19
  98. azure/ai/evaluation/_legacy/_common/_thread_pool_executor_with_context.py +2 -0
  99. azure/ai/evaluation/_legacy/prompty/_prompty.py +11 -5
  100. azure/ai/evaluation/_safety_evaluation/__init__.py +1 -1
  101. azure/ai/evaluation/_safety_evaluation/_safety_evaluation.py +195 -111
  102. azure/ai/evaluation/_user_agent.py +32 -1
  103. azure/ai/evaluation/_version.py +1 -1
  104. azure/ai/evaluation/red_team/__init__.py +3 -1
  105. azure/ai/evaluation/red_team/_agent/__init__.py +1 -1
  106. azure/ai/evaluation/red_team/_agent/_agent_functions.py +68 -71
  107. azure/ai/evaluation/red_team/_agent/_agent_tools.py +103 -145
  108. azure/ai/evaluation/red_team/_agent/_agent_utils.py +26 -6
  109. azure/ai/evaluation/red_team/_agent/_semantic_kernel_plugin.py +62 -71
  110. azure/ai/evaluation/red_team/_attack_objective_generator.py +94 -52
  111. azure/ai/evaluation/red_team/_attack_strategy.py +2 -1
  112. azure/ai/evaluation/red_team/_callback_chat_target.py +4 -9
  113. azure/ai/evaluation/red_team/_default_converter.py +1 -1
  114. azure/ai/evaluation/red_team/_red_team.py +1947 -1040
  115. azure/ai/evaluation/red_team/_red_team_result.py +49 -38
  116. azure/ai/evaluation/red_team/_utils/__init__.py +1 -1
  117. azure/ai/evaluation/red_team/_utils/_rai_service_eval_chat_target.py +39 -34
  118. azure/ai/evaluation/red_team/_utils/_rai_service_target.py +163 -138
  119. azure/ai/evaluation/red_team/_utils/_rai_service_true_false_scorer.py +14 -14
  120. azure/ai/evaluation/red_team/_utils/constants.py +1 -13
  121. azure/ai/evaluation/red_team/_utils/formatting_utils.py +41 -44
  122. azure/ai/evaluation/red_team/_utils/logging_utils.py +17 -17
  123. azure/ai/evaluation/red_team/_utils/metric_mapping.py +31 -4
  124. azure/ai/evaluation/red_team/_utils/strategy_utils.py +33 -25
  125. azure/ai/evaluation/simulator/_adversarial_scenario.py +2 -0
  126. azure/ai/evaluation/simulator/_adversarial_simulator.py +31 -17
  127. azure/ai/evaluation/simulator/_conversation/__init__.py +2 -2
  128. azure/ai/evaluation/simulator/_direct_attack_simulator.py +8 -8
  129. azure/ai/evaluation/simulator/_indirect_attack_simulator.py +18 -6
  130. azure/ai/evaluation/simulator/_model_tools/_generated_rai_client.py +54 -24
  131. azure/ai/evaluation/simulator/_model_tools/_identity_manager.py +7 -1
  132. azure/ai/evaluation/simulator/_model_tools/_proxy_completion_model.py +30 -10
  133. azure/ai/evaluation/simulator/_model_tools/_rai_client.py +19 -31
  134. azure/ai/evaluation/simulator/_model_tools/_template_handler.py +20 -6
  135. azure/ai/evaluation/simulator/_model_tools/models.py +1 -1
  136. azure/ai/evaluation/simulator/_simulator.py +21 -8
  137. {azure_ai_evaluation-1.8.0.dist-info → azure_ai_evaluation-1.10.0.dist-info}/METADATA +46 -3
  138. {azure_ai_evaluation-1.8.0.dist-info → azure_ai_evaluation-1.10.0.dist-info}/RECORD +141 -136
  139. azure/ai/evaluation/_common/onedp/aio/_vendor.py +0 -40
  140. {azure_ai_evaluation-1.8.0.dist-info → azure_ai_evaluation-1.10.0.dist-info}/NOTICE.txt +0 -0
  141. {azure_ai_evaluation-1.8.0.dist-info → azure_ai_evaluation-1.10.0.dist-info}/WHEEL +0 -0
  142. {azure_ai_evaluation-1.8.0.dist-info → azure_ai_evaluation-1.10.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@
5
5
  import dataclasses
6
6
  import inspect
7
7
  import sys
8
+ import traceback
8
9
 
9
10
  from concurrent.futures import Executor
10
11
  from datetime import datetime, timezone
@@ -46,11 +47,6 @@ class RunSubmitter:
46
47
  **kwargs,
47
48
  ) -> Run:
48
49
 
49
- # if the column mappings are not provided, generate them based on the arguments to the
50
- # flow function.
51
- if column_mapping is None:
52
- column_mapping = self._generate_column_mapping(dynamic_callable)
53
-
54
50
  # The old code always spun up two threads here using a ThreadPoolExecutor:
55
51
  # 1. One thread essentially did nothing of value (since tracing was disabled, and we
56
52
  # don't care about checking for the latest PromptFlow version number now)
@@ -84,7 +80,7 @@ class RunSubmitter:
84
80
  # unnecessary Flow loading code was removed here. Instead do direct calls to _submit_bulk_run
85
81
  await self._submit_bulk_run(run=run, local_storage=local_storage, **kwargs)
86
82
 
87
- self.stream_run(run=run, storage=local_storage, raise_on_error=True)
83
+ self.stream_run(run=run, storage=local_storage, raise_on_error=self._config.raise_on_error)
88
84
  return run
89
85
 
90
86
  async def _submit_bulk_run(self, run: Run, local_storage: AbstractRunStorage, **kwargs) -> None:
@@ -108,16 +104,13 @@ class RunSubmitter:
108
104
  f"Referenced run {previous.name} has {len(previous.outputs)} outputs, "
109
105
  f"but {len(run.inputs)} inputs are provided."
110
106
  )
111
-
107
+
112
108
  # load in the previous run's outputs and inputs into the list of dictionaries to allow for
113
109
  # the previous run's outputs to be used as inputs for the current run
114
110
  run.inputs = [
115
- {
116
- "run.outputs": previous.outputs[i],
117
- "run.inputs": previous.inputs[i],
118
- **run.inputs[i]
119
- }
120
- for i in range(len(run.inputs))]
111
+ {"run.outputs": previous.outputs[i], "run.inputs": previous.inputs[i], **run.inputs[i]}
112
+ for i in range(len(run.inputs))
113
+ ]
121
114
 
122
115
  self._validate_column_mapping(run.column_mapping)
123
116
 
@@ -128,10 +121,8 @@ class RunSubmitter:
128
121
  try:
129
122
  batch_engine = BatchEngine(
130
123
  run.dynamic_callable,
124
+ config=self._config,
131
125
  storage=local_storage,
132
- batch_timeout_sec=self._config.batch_timeout_seconds,
133
- line_timeout_sec=self._config.run_timeout_seconds,
134
- max_worker_count=self._config.max_concurrency,
135
126
  executor=self._executor,
136
127
  )
137
128
 
@@ -163,10 +154,10 @@ class RunSubmitter:
163
154
  # system metrics
164
155
  system_metrics = {}
165
156
  if batch_result:
166
- system_metrics.update(dataclasses.asdict(batch_result.tokens)) # token related
157
+ # system_metrics.update(dataclasses.asdict(batch_result.tokens)) # token related
167
158
  system_metrics.update(
168
159
  {
169
- "duration": batch_result.duration.total_seconds(),
160
+ # "duration": batch_result.duration.total_seconds(),
170
161
  # "__pf__.lines.completed": batch_result.total_lines - batch_result.failed_lines,
171
162
  # "__pf__.lines.failed": batch_result.failed_lines,
172
163
  }
@@ -176,33 +167,16 @@ class RunSubmitter:
176
167
  run.metrics = system_metrics
177
168
  run.result = batch_result
178
169
 
179
- @staticmethod
180
- def _generate_column_mapping(function: Callable) -> Mapping[str, Any]:
181
- args = inspect.signature(function).parameters
182
- default_values: Dict[str, Any] = {}
183
- mapping: Dict[str, Any] = {}
184
- for key, value in args.items():
185
- if key in ["self", "cls"] or value.kind in [value.VAR_POSITIONAL, value.VAR_KEYWORD]:
186
- continue
187
-
188
- mapping[key] = f"${{data.{key}}}"
189
- if value.default != inspect.Parameter.empty:
190
- default_values[key] = value.default
191
-
192
- return {
193
- **mapping,
194
- DEFAULTS_KEY: default_values,
195
- }
196
-
197
170
  @staticmethod
198
171
  def _validate_inputs(run: Run):
199
172
  if not run.inputs and not run.previous_run:
200
- raise BatchEngineValidationError(
201
- "Either data, or a previous run must be specified for the evaluation run."
202
- )
173
+ raise BatchEngineValidationError("Either data, or a previous run must be specified for the evaluation run.")
203
174
 
204
175
  @staticmethod
205
- def _validate_column_mapping(column_mapping: Mapping[str, str]):
176
+ def _validate_column_mapping(column_mapping: Optional[Mapping[str, str]]):
177
+ if not column_mapping:
178
+ return
179
+
206
180
  if not isinstance(column_mapping, Mapping):
207
181
  raise BatchEngineValidationError(f"Column mapping must be a dict, got {type(column_mapping)}.")
208
182
 
@@ -226,6 +200,7 @@ class RunSubmitter:
226
200
  return
227
201
 
228
202
  file_handler = sys.stdout
203
+ error_message: Optional[str] = None
229
204
  try:
230
205
  printed = 0
231
206
  available_logs = storage.logger.get_logs()
@@ -237,7 +212,24 @@ class RunSubmitter:
237
212
 
238
213
  if run.status == RunStatus.FAILED or run.status == RunStatus.CANCELED:
239
214
  if run.status == RunStatus.FAILED:
240
- error_message = storage.load_exception().get("message", "Run fails with unknown error.")
215
+ # Get the first error message from the results, or use a default one
216
+ if run.result and run.result.error:
217
+ error_message = "".join(
218
+ traceback.format_exception(
219
+ type(run.result.error), run.result.error, run.result.error.__traceback__
220
+ )
221
+ )
222
+ elif run.result and run.result.details:
223
+ err = next((r.error for r in run.result.details if r.error), None)
224
+ if err and err.exception:
225
+ error_message = "".join(
226
+ traceback.format_exception(type(err.exception), err.exception, err.exception.__traceback__)
227
+ )
228
+ elif err and err.details:
229
+ error_message = err.details
230
+
231
+ if not error_message:
232
+ error_message = "Run fails with unknown error."
241
233
  else:
242
234
  error_message = "Run is canceled."
243
235
  if raise_on_error:
@@ -94,7 +94,4 @@ def is_async_callable(obj: Any) -> bool:
94
94
  :return: True if the object is an async callable.
95
95
  :rtype: bool
96
96
  """
97
- return (
98
- inspect.iscoroutinefunction(obj)
99
- or inspect.iscoroutinefunction(getattr(obj, "__call__", None))
100
- )
97
+ return inspect.iscoroutinefunction(obj) or inspect.iscoroutinefunction(getattr(obj, "__call__", None))
@@ -11,21 +11,15 @@ from azure.identity import AzureCliCredential, DefaultAzureCredential, ManagedId
11
11
  from azure.ai.evaluation._exceptions import EvaluationException, ErrorBlame, ErrorCategory, ErrorTarget
12
12
  from azure.ai.evaluation._azure._envs import AzureEnvironmentClient
13
13
 
14
+
14
15
  class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
15
16
  """Asynchronous token provider for Azure services that supports non-default Azure clouds
16
17
  (e.g. Azure China, Azure US Government, etc.)."""
17
18
 
18
- def __init__(
19
- self,
20
- *,
21
- base_url: Optional[str] = None,
22
- **kwargs: Any
23
- ) -> None:
19
+ def __init__(self, *, base_url: Optional[str] = None, **kwargs: Any) -> None:
24
20
  """Initialize the AsyncAzureTokenProvider."""
25
21
  self._credential: Optional[TokenCredential] = None
26
- self._env_client: Optional[AzureEnvironmentClient] = AzureEnvironmentClient(
27
- base_url=base_url,
28
- **kwargs)
22
+ self._env_client: Optional[AzureEnvironmentClient] = AzureEnvironmentClient(base_url=base_url, **kwargs)
29
23
 
30
24
  async def close(self) -> None:
31
25
  if self._env_client:
@@ -50,14 +44,10 @@ class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
50
44
  f"{self.__class__.__name__} could not determine the credential to use.",
51
45
  target=ErrorTarget.UNKNOWN,
52
46
  category=ErrorCategory.INVALID_VALUE,
53
- blame=ErrorBlame.SYSTEM_ERROR)
47
+ blame=ErrorBlame.SYSTEM_ERROR,
48
+ )
54
49
 
55
- return self._credential.get_token(
56
- *scopes,
57
- claims=claims,
58
- tenant_id=tenant_id,
59
- enable_cae=enable_cae,
60
- **kwargs)
50
+ return self._credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, enable_cae=enable_cae, **kwargs)
61
51
 
62
52
  async def __aenter__(self) -> "AsyncAzureTokenProvider":
63
53
  self._credential = await self._initialize_async(self._env_client)
@@ -67,7 +57,7 @@ class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
67
57
  self,
68
58
  exc_type: Optional[type] = None,
69
59
  exc_value: Optional[BaseException] = None,
70
- traceback: Optional[Any] = None
60
+ traceback: Optional[Any] = None,
71
61
  ) -> None:
72
62
  await self.close()
73
63
 
@@ -80,7 +70,8 @@ class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
80
70
  f"{AsyncAzureTokenProvider.__name__} instance has already been closed.",
81
71
  target=ErrorTarget.UNKNOWN,
82
72
  category=ErrorCategory.INVALID_VALUE,
83
- blame=ErrorBlame.USER_ERROR)
73
+ blame=ErrorBlame.USER_ERROR,
74
+ )
84
75
 
85
76
  cloud_name: str = await client.get_default_cloud_name_async()
86
77
  if cloud_name != client.DEFAULT_AZURE_CLOUD_NAME:
@@ -92,7 +83,8 @@ class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
92
83
  f"Failed to get metadata for cloud '{cloud_name}'.",
93
84
  target=ErrorTarget.UNKNOWN,
94
85
  category=ErrorCategory.INVALID_VALUE,
95
- blame=ErrorBlame.USER_ERROR)
86
+ blame=ErrorBlame.USER_ERROR,
87
+ )
96
88
 
97
89
  authority = metadata.get("active_directory_endpoint")
98
90
  return DefaultAzureCredential(authority=authority, exclude_shared_token_cache_credential=True)
@@ -100,6 +92,7 @@ class AsyncAzureTokenProvider(AsyncContextManager["AsyncAzureTokenProvider"]):
100
92
  # using Azure on behalf of credentials requires the use of the azure-ai-ml package
101
93
  try:
102
94
  from azure.ai.ml.identity import AzureMLOnBehalfOfCredential
95
+
103
96
  return AzureMLOnBehalfOfCredential() # type: ignore
104
97
  except (ModuleNotFoundError, ImportError):
105
98
  raise EvaluationException( # pylint: disable=raise-missing-from
@@ -7,8 +7,10 @@ from concurrent.futures import ThreadPoolExecutor
7
7
  from functools import partial
8
8
  from typing_extensions import override
9
9
 
10
+
10
11
  class ThreadPoolExecutorWithContext(ThreadPoolExecutor):
11
12
  """ThreadPoolExecutor that preserves context variables across threads."""
13
+
12
14
  @override
13
15
  def submit(self, fn, *args, **kwargs):
14
16
  context = contextvars.copy_context()
@@ -40,7 +40,7 @@ from azure.ai.evaluation._legacy.prompty._utils import (
40
40
  from azure.ai.evaluation._constants import DEFAULT_MAX_COMPLETION_TOKENS_REASONING_MODELS
41
41
  from azure.ai.evaluation._legacy._common._logging import get_logger
42
42
  from azure.ai.evaluation._legacy._common._async_token_provider import AsyncAzureTokenProvider
43
-
43
+ from azure.ai.evaluation._user_agent import UserAgentSingleton
44
44
 
45
45
  PROMPTY_EXTENSION: Final[str] = ".prompty"
46
46
 
@@ -168,8 +168,9 @@ class AsyncPrompty:
168
168
  self._outputs: Dict[str, Any] = configs.get("outputs", {})
169
169
  self._name: str = configs.get("name", path.stem)
170
170
  self._logger = logger or get_logger(__name__)
171
- self._token_credential: Union[TokenCredential, AsyncTokenCredential] = \
171
+ self._token_credential: Union[TokenCredential, AsyncTokenCredential] = (
172
172
  token_credential or AsyncAzureTokenProvider()
173
+ )
173
174
 
174
175
  @property
175
176
  def path(self) -> Path:
@@ -290,6 +291,8 @@ class AsyncPrompty:
290
291
  # for better debugging and real-time status updates.
291
292
  max_retries = 0
292
293
 
294
+ default_headers = {"User-Agent": UserAgentSingleton().value}
295
+
293
296
  api_client: Union[AsyncAzureOpenAI, AsyncOpenAI]
294
297
  if isinstance(connection, AzureOpenAIConnection):
295
298
  api_client = AsyncAzureOpenAI(
@@ -298,9 +301,10 @@ class AsyncPrompty:
298
301
  azure_deployment=connection.azure_deployment,
299
302
  api_version=connection.api_version,
300
303
  max_retries=max_retries,
301
- azure_ad_token_provider=(self.get_token_provider(self._token_credential)
302
- if not connection.api_key
303
- else None),
304
+ azure_ad_token_provider=(
305
+ self.get_token_provider(self._token_credential) if not connection.api_key else None
306
+ ),
307
+ default_headers=default_headers,
304
308
  )
305
309
  elif isinstance(connection, OpenAIConnection):
306
310
  api_client = AsyncOpenAI(
@@ -308,6 +312,7 @@ class AsyncPrompty:
308
312
  api_key=connection.api_key,
309
313
  organization=connection.organization,
310
314
  max_retries=max_retries,
315
+ default_headers=default_headers,
311
316
  )
312
317
  else:
313
318
  raise NotSupportedError(
@@ -414,6 +419,7 @@ class AsyncPrompty:
414
419
  :return: The token provider if a credential is provided, otherwise None.
415
420
  :rtype: Optional[AsyncAzureADTokenProvider]
416
421
  """
422
+
417
423
  async def _wrapper() -> str:
418
424
  token = cred.get_token(TokenScope.COGNITIVE_SERVICES_MANAGEMENT)
419
425
  if isinstance(token, Awaitable):
@@ -1,3 +1,3 @@
1
1
  # ---------------------------------------------------------
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
- # ---------------------------------------------------------
3
+ # ---------------------------------------------------------