azure-ai-evaluation 1.7.0__py3-none-any.whl → 1.9.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.
Files changed (136) hide show
  1. azure/ai/evaluation/__init__.py +13 -2
  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/score_model_grader.py +90 -0
  6. azure/ai/evaluation/_aoai/string_check_grader.py +3 -2
  7. azure/ai/evaluation/_aoai/text_similarity_grader.py +3 -2
  8. azure/ai/evaluation/_azure/_envs.py +9 -10
  9. azure/ai/evaluation/_azure/_token_manager.py +7 -1
  10. azure/ai/evaluation/_common/constants.py +11 -2
  11. azure/ai/evaluation/_common/evaluation_onedp_client.py +32 -26
  12. azure/ai/evaluation/_common/onedp/__init__.py +32 -32
  13. azure/ai/evaluation/_common/onedp/_client.py +136 -139
  14. azure/ai/evaluation/_common/onedp/_configuration.py +70 -73
  15. azure/ai/evaluation/_common/onedp/_patch.py +21 -21
  16. azure/ai/evaluation/_common/onedp/_utils/__init__.py +6 -0
  17. azure/ai/evaluation/_common/onedp/_utils/model_base.py +1232 -0
  18. azure/ai/evaluation/_common/onedp/_utils/serialization.py +2032 -0
  19. azure/ai/evaluation/_common/onedp/_validation.py +50 -50
  20. azure/ai/evaluation/_common/onedp/_version.py +9 -9
  21. azure/ai/evaluation/_common/onedp/aio/__init__.py +29 -29
  22. azure/ai/evaluation/_common/onedp/aio/_client.py +138 -143
  23. azure/ai/evaluation/_common/onedp/aio/_configuration.py +70 -75
  24. azure/ai/evaluation/_common/onedp/aio/_patch.py +21 -21
  25. azure/ai/evaluation/_common/onedp/aio/operations/__init__.py +37 -39
  26. azure/ai/evaluation/_common/onedp/aio/operations/_operations.py +4832 -4494
  27. azure/ai/evaluation/_common/onedp/aio/operations/_patch.py +21 -21
  28. azure/ai/evaluation/_common/onedp/models/__init__.py +168 -142
  29. azure/ai/evaluation/_common/onedp/models/_enums.py +230 -162
  30. azure/ai/evaluation/_common/onedp/models/_models.py +2685 -2228
  31. azure/ai/evaluation/_common/onedp/models/_patch.py +21 -21
  32. azure/ai/evaluation/_common/onedp/operations/__init__.py +37 -39
  33. azure/ai/evaluation/_common/onedp/operations/_operations.py +6106 -5655
  34. azure/ai/evaluation/_common/onedp/operations/_patch.py +21 -21
  35. azure/ai/evaluation/_common/rai_service.py +86 -50
  36. azure/ai/evaluation/_common/raiclient/__init__.py +1 -1
  37. azure/ai/evaluation/_common/raiclient/operations/_operations.py +14 -1
  38. azure/ai/evaluation/_common/utils.py +124 -3
  39. azure/ai/evaluation/_constants.py +2 -1
  40. azure/ai/evaluation/_converters/__init__.py +1 -1
  41. azure/ai/evaluation/_converters/_ai_services.py +9 -8
  42. azure/ai/evaluation/_converters/_models.py +46 -0
  43. azure/ai/evaluation/_converters/_sk_services.py +495 -0
  44. azure/ai/evaluation/_eval_mapping.py +2 -2
  45. azure/ai/evaluation/_evaluate/_batch_run/_run_submitter_client.py +4 -4
  46. azure/ai/evaluation/_evaluate/_batch_run/eval_run_context.py +2 -2
  47. azure/ai/evaluation/_evaluate/_evaluate.py +64 -58
  48. azure/ai/evaluation/_evaluate/_evaluate_aoai.py +130 -89
  49. azure/ai/evaluation/_evaluate/_telemetry/__init__.py +0 -1
  50. azure/ai/evaluation/_evaluate/_utils.py +24 -15
  51. azure/ai/evaluation/_evaluators/_bleu/_bleu.py +3 -3
  52. azure/ai/evaluation/_evaluators/_code_vulnerability/_code_vulnerability.py +12 -11
  53. azure/ai/evaluation/_evaluators/_coherence/_coherence.py +5 -5
  54. azure/ai/evaluation/_evaluators/_common/_base_eval.py +15 -5
  55. azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +24 -9
  56. azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +6 -1
  57. azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +13 -13
  58. azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +7 -7
  59. azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +7 -7
  60. azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +7 -7
  61. azure/ai/evaluation/_evaluators/_content_safety/_violence.py +6 -6
  62. azure/ai/evaluation/_evaluators/_document_retrieval/__init__.py +1 -5
  63. azure/ai/evaluation/_evaluators/_document_retrieval/_document_retrieval.py +34 -64
  64. azure/ai/evaluation/_evaluators/_eci/_eci.py +3 -3
  65. azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +4 -4
  66. azure/ai/evaluation/_evaluators/_fluency/_fluency.py +2 -2
  67. azure/ai/evaluation/_evaluators/_gleu/_gleu.py +3 -3
  68. azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +11 -7
  69. azure/ai/evaluation/_evaluators/_intent_resolution/_intent_resolution.py +30 -25
  70. azure/ai/evaluation/_evaluators/_intent_resolution/intent_resolution.prompty +210 -96
  71. azure/ai/evaluation/_evaluators/_meteor/_meteor.py +2 -3
  72. azure/ai/evaluation/_evaluators/_protected_material/_protected_material.py +6 -6
  73. azure/ai/evaluation/_evaluators/_qa/_qa.py +4 -4
  74. azure/ai/evaluation/_evaluators/_relevance/_relevance.py +8 -13
  75. azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.py +20 -25
  76. azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py +4 -4
  77. azure/ai/evaluation/_evaluators/_rouge/_rouge.py +25 -25
  78. azure/ai/evaluation/_evaluators/_service_groundedness/_service_groundedness.py +5 -5
  79. azure/ai/evaluation/_evaluators/_similarity/_similarity.py +3 -3
  80. azure/ai/evaluation/_evaluators/_task_adherence/_task_adherence.py +11 -14
  81. azure/ai/evaluation/_evaluators/_tool_call_accuracy/_tool_call_accuracy.py +43 -34
  82. azure/ai/evaluation/_evaluators/_tool_call_accuracy/tool_call_accuracy.prompty +3 -3
  83. azure/ai/evaluation/_evaluators/_ungrounded_attributes/_ungrounded_attributes.py +12 -11
  84. azure/ai/evaluation/_evaluators/_xpia/xpia.py +6 -6
  85. azure/ai/evaluation/_exceptions.py +10 -0
  86. azure/ai/evaluation/_http_utils.py +3 -3
  87. azure/ai/evaluation/_legacy/_batch_engine/_engine.py +3 -3
  88. azure/ai/evaluation/_legacy/_batch_engine/_openai_injector.py +5 -2
  89. azure/ai/evaluation/_legacy/_batch_engine/_run_submitter.py +5 -10
  90. azure/ai/evaluation/_legacy/_batch_engine/_utils.py +1 -4
  91. azure/ai/evaluation/_legacy/_common/_async_token_provider.py +12 -19
  92. azure/ai/evaluation/_legacy/_common/_thread_pool_executor_with_context.py +2 -0
  93. azure/ai/evaluation/_legacy/prompty/_prompty.py +11 -5
  94. azure/ai/evaluation/_safety_evaluation/__init__.py +1 -1
  95. azure/ai/evaluation/_safety_evaluation/_safety_evaluation.py +193 -111
  96. azure/ai/evaluation/_user_agent.py +32 -1
  97. azure/ai/evaluation/_version.py +1 -1
  98. azure/ai/evaluation/red_team/__init__.py +3 -1
  99. azure/ai/evaluation/red_team/_agent/__init__.py +3 -0
  100. azure/ai/evaluation/red_team/_agent/_agent_functions.py +261 -0
  101. azure/ai/evaluation/red_team/_agent/_agent_tools.py +461 -0
  102. azure/ai/evaluation/red_team/_agent/_agent_utils.py +89 -0
  103. azure/ai/evaluation/red_team/_agent/_semantic_kernel_plugin.py +228 -0
  104. azure/ai/evaluation/red_team/_attack_objective_generator.py +94 -52
  105. azure/ai/evaluation/red_team/_attack_strategy.py +4 -1
  106. azure/ai/evaluation/red_team/_callback_chat_target.py +4 -9
  107. azure/ai/evaluation/red_team/_default_converter.py +1 -1
  108. azure/ai/evaluation/red_team/_red_team.py +1622 -765
  109. azure/ai/evaluation/red_team/_red_team_result.py +43 -38
  110. azure/ai/evaluation/red_team/_utils/__init__.py +1 -1
  111. azure/ai/evaluation/red_team/_utils/_rai_service_eval_chat_target.py +121 -0
  112. azure/ai/evaluation/red_team/_utils/_rai_service_target.py +595 -0
  113. azure/ai/evaluation/red_team/_utils/_rai_service_true_false_scorer.py +108 -0
  114. azure/ai/evaluation/red_team/_utils/constants.py +6 -12
  115. azure/ai/evaluation/red_team/_utils/formatting_utils.py +41 -44
  116. azure/ai/evaluation/red_team/_utils/logging_utils.py +17 -17
  117. azure/ai/evaluation/red_team/_utils/metric_mapping.py +33 -6
  118. azure/ai/evaluation/red_team/_utils/strategy_utils.py +35 -25
  119. azure/ai/evaluation/simulator/_adversarial_scenario.py +2 -0
  120. azure/ai/evaluation/simulator/_adversarial_simulator.py +34 -16
  121. azure/ai/evaluation/simulator/_conversation/__init__.py +2 -2
  122. azure/ai/evaluation/simulator/_direct_attack_simulator.py +8 -8
  123. azure/ai/evaluation/simulator/_indirect_attack_simulator.py +5 -5
  124. azure/ai/evaluation/simulator/_model_tools/_generated_rai_client.py +54 -23
  125. azure/ai/evaluation/simulator/_model_tools/_identity_manager.py +7 -1
  126. azure/ai/evaluation/simulator/_model_tools/_proxy_completion_model.py +25 -15
  127. azure/ai/evaluation/simulator/_model_tools/_rai_client.py +19 -31
  128. azure/ai/evaluation/simulator/_model_tools/_template_handler.py +20 -6
  129. azure/ai/evaluation/simulator/_model_tools/models.py +1 -1
  130. azure/ai/evaluation/simulator/_simulator.py +9 -8
  131. {azure_ai_evaluation-1.7.0.dist-info → azure_ai_evaluation-1.9.0.dist-info}/METADATA +24 -1
  132. {azure_ai_evaluation-1.7.0.dist-info → azure_ai_evaluation-1.9.0.dist-info}/RECORD +135 -123
  133. azure/ai/evaluation/_common/onedp/aio/_vendor.py +0 -40
  134. {azure_ai_evaluation-1.7.0.dist-info → azure_ai_evaluation-1.9.0.dist-info}/NOTICE.txt +0 -0
  135. {azure_ai_evaluation-1.7.0.dist-info → azure_ai_evaluation-1.9.0.dist-info}/WHEEL +0 -0
  136. {azure_ai_evaluation-1.7.0.dist-info → azure_ai_evaluation-1.9.0.dist-info}/top_level.txt +0 -0
@@ -49,8 +49,8 @@ class AdversarialSimulator:
49
49
  """
50
50
  Initializes the adversarial simulator with a project scope.
51
51
 
52
- :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
53
- or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
52
+ :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
53
+ or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
54
54
  :type azure_ai_project: Union[str, AzureAIProject]
55
55
  :param credential: The credential for connecting to Azure AI project.
56
56
  :type credential: ~azure.core.credentials.TokenCredential
@@ -77,7 +77,7 @@ class AdversarialSimulator:
77
77
  logger=logging.getLogger("AdversarialSimulator"),
78
78
  credential=self.credential,
79
79
  )
80
- self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
80
+ self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
81
81
  else:
82
82
  try:
83
83
  self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
@@ -96,7 +96,7 @@ class AdversarialSimulator:
96
96
  credential=self.credential,
97
97
  )
98
98
  self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
99
-
99
+
100
100
  self.adversarial_template_handler = AdversarialTemplateHandler(
101
101
  azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
102
102
  )
@@ -264,12 +264,21 @@ class AdversarialSimulator:
264
264
 
265
265
  # Limit to max_simulation_results if needed
266
266
  if len(template_parameter_pairs) > max_simulation_results:
267
- template_parameter_pairs = template_parameter_pairs[:max_simulation_results]
267
+ template_parameter_pairs = template_parameter_pairs[
268
+ :max_simulation_results
269
+ ] # Create a seeded random instance for jailbreak selection if randomization_seed is provided
270
+ jailbreak_random = None
271
+ if _jailbreak_type == "upia" and randomization_seed is not None:
272
+ jailbreak_random = random.Random(randomization_seed)
268
273
 
269
274
  # Single task append loop for all scenarios
270
275
  for template, parameter in template_parameter_pairs:
271
276
  if _jailbreak_type == "upia":
272
- parameter = self._add_jailbreak_parameter(parameter, random.choice(jailbreak_dataset))
277
+ if jailbreak_random is not None:
278
+ selected_jailbreak = jailbreak_random.choice(jailbreak_dataset)
279
+ else:
280
+ selected_jailbreak = random.choice(jailbreak_dataset)
281
+ parameter = self._add_jailbreak_parameter(parameter, selected_jailbreak)
273
282
 
274
283
  tasks.append(
275
284
  asyncio.create_task(
@@ -357,10 +366,21 @@ class AdversarialSimulator:
357
366
  target=target, role=ConversationRole.ASSISTANT, template=template, parameters=parameters, scenario=scenario
358
367
  )
359
368
  bots = [user_bot, system_bot]
360
-
369
+
370
+ async def run_simulation(session_obj):
371
+ async with semaphore:
372
+ _, conversation_history = await simulate_conversation(
373
+ bots=bots,
374
+ session=session_obj,
375
+ turn_limit=max_conversation_turns,
376
+ api_call_delay_sec=api_call_delay_sec,
377
+ language=language,
378
+ )
379
+ return conversation_history
380
+
361
381
  if isinstance(self.rai_client, AIProjectClient):
362
382
  session = self.rai_client
363
- else:
383
+ else:
364
384
  session = get_async_http_client().with_policies(
365
385
  retry_policy=AsyncRetryPolicy(
366
386
  retry_total=api_call_retry_limit,
@@ -368,13 +388,7 @@ class AdversarialSimulator:
368
388
  retry_mode=RetryMode.Fixed,
369
389
  )
370
390
  )
371
- _, conversation_history = await simulate_conversation(
372
- bots=bots,
373
- session=session,
374
- turn_limit=max_conversation_turns,
375
- api_call_delay_sec=api_call_delay_sec,
376
- language=language,
377
- )
391
+ conversation_history = await run_simulation(session)
378
392
 
379
393
  return self._to_chat_protocol(
380
394
  conversation_history=conversation_history,
@@ -384,7 +398,11 @@ class AdversarialSimulator:
384
398
  def _get_user_proxy_completion_model(
385
399
  self, template_key: str, template_parameters: TemplateParameters, simulation_id: str = ""
386
400
  ) -> ProxyChatCompletionsModel:
387
- endpoint_url = self.rai_client._config.endpoint + "/redTeams/simulation/chat/completions/submit" if isinstance(self.rai_client, AIProjectClient) else self.rai_client.simulation_submit_endpoint
401
+ endpoint_url = (
402
+ self.rai_client._config.endpoint + "/redTeams/simulation/chat/completions/submit"
403
+ if isinstance(self.rai_client, AIProjectClient)
404
+ else self.rai_client.simulation_submit_endpoint
405
+ )
388
406
  return ProxyChatCompletionsModel(
389
407
  name="raisvc_proxy_model",
390
408
  template_key=template_key,
@@ -419,13 +419,13 @@ class MultiModalConversationBot(ConversationBot):
419
419
  contents = []
420
420
  for msg in messages:
421
421
  if msg.startswith("image_understanding/"):
422
- if(isinstance(self.rai_client, RAIClient)):
422
+ if isinstance(self.rai_client, RAIClient):
423
423
  encoded_image = await self.rai_client.get_image_data(msg)
424
424
  else:
425
425
  response = self.rai_client.red_teams.get_template_parameters_image(path=msg, stream="true")
426
426
  image_data = b"".join(response)
427
427
  encoded_image = base64.b64encode(image_data).decode("utf-8")
428
-
428
+
429
429
  contents.append(
430
430
  {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{encoded_image}"}},
431
431
  )
@@ -28,8 +28,8 @@ class DirectAttackSimulator:
28
28
  Initialize a UPIA (user prompt injected attack) jailbreak adversarial simulator with a project scope.
29
29
  This simulator converses with your AI system using prompts designed to interrupt normal functionality.
30
30
 
31
- :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
32
- or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
31
+ :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
32
+ or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
33
33
  :type azure_ai_project: Union[str, AzureAIProject]
34
34
  :param credential: The credential for connecting to Azure AI project.
35
35
  :type credential: ~azure.core.credentials.TokenCredential
@@ -46,16 +46,16 @@ class DirectAttackSimulator:
46
46
 
47
47
  def __init__(self, *, azure_ai_project: Union[str, AzureAIProject], credential: TokenCredential):
48
48
  """Constructor."""
49
-
49
+
50
50
  if is_onedp_project(azure_ai_project):
51
51
  self.azure_ai_project = azure_ai_project
52
- self.credential=cast(TokenCredential, credential)
52
+ self.credential = cast(TokenCredential, credential)
53
53
  self.token_manager = ManagedIdentityAPITokenManager(
54
54
  token_scope=TokenScope.COGNITIVE_SERVICES_MANAGEMENT,
55
55
  logger=logging.getLogger("AdversarialSimulator"),
56
- credential=self.credential
56
+ credential=self.credential,
57
57
  )
58
- self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
58
+ self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
59
59
  else:
60
60
  try:
61
61
  self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
@@ -67,14 +67,14 @@ class DirectAttackSimulator:
67
67
  category=e.category,
68
68
  blame=e.blame,
69
69
  ) from e
70
- self.credential = cast(TokenCredential, credential)
70
+ self.credential = cast(TokenCredential, credential)
71
71
  self.token_manager = ManagedIdentityAPITokenManager(
72
72
  token_scope=TokenScope.DEFAULT_AZURE_MANAGEMENT,
73
73
  logger=logging.getLogger("AdversarialSimulator"),
74
74
  credential=self.credential,
75
75
  )
76
76
  self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
77
-
77
+
78
78
  self.adversarial_template_handler = AdversarialTemplateHandler(
79
79
  azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
80
80
  )
@@ -30,8 +30,8 @@ class IndirectAttackSimulator(AdversarialSimulator):
30
30
  """
31
31
  Initializes the XPIA (cross domain prompt injected attack) jailbreak adversarial simulator with a project scope.
32
32
 
33
- :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
34
- or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
33
+ :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
34
+ or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
35
35
  :type azure_ai_project: Union[str, AzureAIProject]
36
36
  :param credential: The credential for connecting to Azure AI project.
37
37
  :type credential: ~azure.core.credentials.TokenCredential
@@ -51,13 +51,13 @@ class IndirectAttackSimulator(AdversarialSimulator):
51
51
 
52
52
  if is_onedp_project(azure_ai_project):
53
53
  self.azure_ai_project = azure_ai_project
54
- self.credential=cast(TokenCredential, credential)
54
+ self.credential = cast(TokenCredential, credential)
55
55
  self.token_manager = ManagedIdentityAPITokenManager(
56
56
  token_scope=TokenScope.COGNITIVE_SERVICES_MANAGEMENT,
57
57
  logger=logging.getLogger("AdversarialSimulator"),
58
- credential=self.credential
58
+ credential=self.credential,
59
59
  )
60
- self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
60
+ self.rai_client = AIProjectClient(endpoint=azure_ai_project, credential=credential)
61
61
  self.adversarial_template_handler = AdversarialTemplateHandler(
62
62
  azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
63
63
  )
@@ -6,6 +6,7 @@ import os
6
6
  from typing import Dict, List, Optional, Union
7
7
 
8
8
  from azure.core.credentials import TokenCredential
9
+ from azure.core.pipeline.policies import UserAgentPolicy
9
10
  from azure.ai.evaluation._model_configurations import AzureAIProject
10
11
  from azure.ai.evaluation.simulator._model_tools import ManagedIdentityAPITokenManager
11
12
  from azure.ai.evaluation._common.raiclient import MachineLearningServicesClient
@@ -13,30 +14,35 @@ from azure.ai.evaluation._constants import TokenScope
13
14
  from azure.ai.evaluation._common.utils import is_onedp_project
14
15
  from azure.ai.evaluation._common.onedp import AIProjectClient
15
16
  from azure.ai.evaluation._common import EvaluationServiceOneDPClient
17
+ from azure.ai.evaluation._user_agent import UserAgentSingleton
16
18
  import jwt
17
19
  import time
18
20
  import ast
19
21
 
22
+
20
23
  class GeneratedRAIClient:
21
24
  """Client for the Responsible AI Service using the auto-generated MachineLearningServicesClient.
22
-
23
- :param azure_ai_project: The scope of the Azure AI project. It contains subscription id, resource group, and project name.
24
- :type azure_ai_project: ~azure.ai.evaluation.AzureAIProject
25
+
26
+ :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
27
+ or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
28
+ :type azure_ai_project: Union[str, ~azure.ai.evaluation.AzureAIProject]
25
29
  :param token_manager: The token manager
26
30
  :type token_manager: ~azure.ai.evaluation.simulator._model_tools._identity_manager.APITokenManager
27
31
  """
28
-
32
+
29
33
  def __init__(self, azure_ai_project: Union[AzureAIProject, str], token_manager: ManagedIdentityAPITokenManager):
30
34
  self.azure_ai_project = azure_ai_project
31
35
  self.token_manager = token_manager
32
-
36
+
37
+ user_agent_policy = UserAgentPolicy(base_user_agent=UserAgentSingleton().value)
38
+
33
39
  if not is_onedp_project(azure_ai_project):
34
40
  # Service URL construction
35
41
  if "RAI_SVC_URL" in os.environ:
36
42
  endpoint = os.environ["RAI_SVC_URL"].rstrip("/")
37
43
  else:
38
44
  endpoint = self._get_service_discovery_url()
39
-
45
+
40
46
  # Create the autogenerated client
41
47
  self._client = MachineLearningServicesClient(
42
48
  endpoint=endpoint,
@@ -46,19 +52,24 @@ class GeneratedRAIClient:
46
52
  credential=self.token_manager,
47
53
  ).rai_svc
48
54
  else:
49
- self._client = AIProjectClient(endpoint=azure_ai_project, credential=token_manager).red_teams
50
- self._evaluation_onedp_client = EvaluationServiceOneDPClient(endpoint=azure_ai_project, credential=token_manager)
51
-
55
+ self._client = AIProjectClient(
56
+ endpoint=azure_ai_project, credential=token_manager, user_agent_policy=user_agent_policy
57
+ ).red_teams
58
+ self._evaluation_onedp_client = EvaluationServiceOneDPClient(
59
+ endpoint=azure_ai_project, credential=token_manager, user_agent_policy=user_agent_policy
60
+ )
61
+
52
62
  def _get_service_discovery_url(self):
53
63
  """Get the service discovery URL.
54
-
64
+
55
65
  :return: The service discovery URL
56
66
  :rtype: str
57
67
  """
58
68
  import requests
69
+
59
70
  bearer_token = self._fetch_or_reuse_token(self.token_manager)
60
71
  headers = {"Authorization": f"Bearer {bearer_token}", "Content-Type": "application/json"}
61
-
72
+
62
73
  response = requests.get(
63
74
  f"https://management.azure.com/subscriptions/{self.azure_ai_project['subscription_id']}/"
64
75
  f"resourceGroups/{self.azure_ai_project['resource_group_name']}/"
@@ -67,7 +78,7 @@ class GeneratedRAIClient:
67
78
  headers=headers,
68
79
  timeout=5,
69
80
  )
70
-
81
+
71
82
  if response.status_code != 200:
72
83
  msg = (
73
84
  f"Failed to connect to your Azure AI project. Please check if the project scope is configured "
@@ -78,51 +89,71 @@ class GeneratedRAIClient:
78
89
 
79
90
  # Parse the discovery URL
80
91
  from urllib.parse import urlparse
92
+
81
93
  base_url = urlparse(response.json()["properties"]["discoveryUrl"])
82
94
  return f"{base_url.scheme}://{base_url.netloc}"
83
-
84
- async def get_attack_objectives(self, risk_category: Optional[str] = None, application_scenario: str = None, strategy: Optional[str] = None) -> Dict:
95
+
96
+ async def get_attack_objectives(
97
+ self,
98
+ *,
99
+ risk_type: Optional[str] = None,
100
+ risk_category: Optional[str] = None,
101
+ application_scenario: str = None,
102
+ strategy: Optional[str] = None,
103
+ scan_session_id: Optional[str] = None,
104
+ ) -> Dict:
85
105
  """Get attack objectives using the auto-generated operations.
86
-
106
+
107
+ :param risk_type: Optional risk type to filter the attack objectives
108
+ :type risk_type: Optional[str]
87
109
  :param risk_category: Optional risk category to filter the attack objectives
88
110
  :type risk_category: Optional[str]
89
111
  :param application_scenario: Optional description of the application scenario for context
90
112
  :type application_scenario: str
91
113
  :param strategy: Optional strategy to filter the attack objectives
92
114
  :type strategy: Optional[str]
115
+ :param scan_session_id: Optional unique session ID for the scan
116
+ :type scan_session_id: Optional[str]
93
117
  :return: The attack objectives
94
118
  :rtype: Dict
95
- """
119
+ """
96
120
  try:
97
121
  # Send the request using the autogenerated client
98
122
  response = self._client.get_attack_objectives(
99
- risk_types=[risk_category],
123
+ risk_types=[risk_type],
124
+ risk_category=risk_category,
100
125
  lang="en",
101
126
  strategy=strategy,
127
+ headers={"client_request_id": scan_session_id},
102
128
  )
103
129
  return response
104
-
130
+
105
131
  except Exception as e:
106
132
  # Log the exception for debugging purposes
107
133
  import logging
134
+
108
135
  logging.error(f"Error in get_attack_objectives: {str(e)}")
109
136
  raise
110
-
111
- async def get_jailbreak_prefixes(self) -> List[str]:
137
+
138
+ async def get_jailbreak_prefixes(self, scan_session_id: Optional[str] = None) -> List[str]:
112
139
  """Get jailbreak prefixes using the auto-generated operations.
113
-
140
+
141
+ :param scan_session_id: Optional unique session ID for the scan
142
+ :type scan_session_id: Optional[str]
114
143
  :return: The jailbreak prefixes
115
144
  :rtype: List[str]
116
145
  """
117
146
  try:
118
147
  # Send the request using the autogenerated client
119
- response = self._client.get_jail_break_dataset_with_type(type="upia")
148
+ response = self._client.get_jail_break_dataset_with_type(
149
+ type="upia", headers={"client_request_id": scan_session_id}
150
+ )
120
151
  if isinstance(response, list):
121
152
  return response
122
153
  else:
123
154
  self.logger.error("Unexpected response format from get_jail_break_dataset_with_type")
124
155
  raise ValueError("Unexpected response format from get_jail_break_dataset_with_type")
125
-
156
+
126
157
  except Exception as e:
127
158
  return [""]
128
159
 
@@ -78,7 +78,13 @@ class APITokenManager(ABC):
78
78
 
79
79
  @abstractmethod
80
80
  def get_token(
81
- self, scopes: Union[str, None] = None, claims: Union[str, None] = None, tenant_id: Union[str, None] = None, enable_cae: bool = False, **kwargs: Any) -> AccessToken:
81
+ self,
82
+ scopes: Union[str, None] = None,
83
+ claims: Union[str, None] = None,
84
+ tenant_id: Union[str, None] = None,
85
+ enable_cae: bool = False,
86
+ **kwargs: Any,
87
+ ) -> AccessToken:
82
88
  """Async method to get the API token. Subclasses should implement this method.
83
89
 
84
90
  :return: API token
@@ -9,7 +9,7 @@ import uuid
9
9
  from typing import Any, Dict, List, Optional, cast, Union
10
10
 
11
11
  from azure.ai.evaluation._http_utils import AsyncHttpPipeline, get_async_http_client
12
- from azure.ai.evaluation._user_agent import USER_AGENT
12
+ from azure.ai.evaluation._user_agent import UserAgentSingleton
13
13
  from azure.core.exceptions import HttpResponseError
14
14
  from azure.core.pipeline.policies import AsyncRetryPolicy, RetryMode
15
15
  from azure.ai.evaluation._common.onedp._client import AIProjectClient
@@ -43,15 +43,15 @@ class SimulationRequestDTO:
43
43
  headers: Dict[str, str],
44
44
  payload: Dict[str, Any],
45
45
  params: Dict[str, str],
46
- template_key: str,
47
- template_parameters: Optional[TemplateParameters],
46
+ templateKey: str,
47
+ templateParameters: Optional[TemplateParameters],
48
48
  ):
49
49
  self.url = url
50
50
  self.headers = headers
51
51
  self.json = json.dumps(payload)
52
52
  self.params = params
53
- self.template_key = template_key
54
- self.templateParameters = template_parameters
53
+ self.templateKey = templateKey
54
+ self.templateParameters = templateParameters
55
55
 
56
56
  def to_dict(self) -> Dict:
57
57
  """Convert the DTO to a dictionary.
@@ -166,7 +166,7 @@ class ProxyChatCompletionsModel(OpenAIChatCompletionsModel):
166
166
  proxy_headers = {
167
167
  "Authorization": f"Bearer {token}",
168
168
  "Content-Type": "application/json",
169
- "User-Agent": USER_AGENT,
169
+ "User-Agent": UserAgentSingleton().value,
170
170
  }
171
171
 
172
172
  headers = {
@@ -186,14 +186,14 @@ class ProxyChatCompletionsModel(OpenAIChatCompletionsModel):
186
186
  headers=headers,
187
187
  payload=request_data,
188
188
  params=params,
189
- template_key=self.tkey,
190
- template_parameters=self.tparam,
189
+ templateKey=self.tkey,
190
+ templateParameters=self.tparam,
191
191
  )
192
192
 
193
193
  time_start = time.time()
194
194
  full_response = None
195
195
 
196
- if(isinstance(session, AIProjectClient)):
196
+ if isinstance(session, AIProjectClient):
197
197
  sim_request_dto = SimulationDTO(
198
198
  headers=headers,
199
199
  params=params,
@@ -203,11 +203,20 @@ class ProxyChatCompletionsModel(OpenAIChatCompletionsModel):
203
203
  )
204
204
  response_data = session.red_teams.submit_simulation(sim_request_dto, headers=headers, params=params)
205
205
  operation_id = response_data["location"].split("/")[-1]
206
-
206
+
207
207
  request_count = 0
208
208
  flag = True
209
209
  while flag:
210
- response = session.evaluations.operation_results(operation_id, headers=headers)
210
+ try:
211
+ response = session.evaluations.operation_results(operation_id, headers=headers)
212
+ except Exception as e:
213
+ from types import SimpleNamespace # pylint: disable=forgotten-debug-statement
214
+
215
+ response = SimpleNamespace(status_code=202, text=str(e), json=lambda: {"error": str(e)})
216
+ if isinstance(response, dict):
217
+ response_data = response
218
+ flag = False
219
+ break
211
220
  if response.status_code == 200:
212
221
  response_data = cast(List[Dict], response.json())
213
222
  flag = False
@@ -220,10 +229,11 @@ class ProxyChatCompletionsModel(OpenAIChatCompletionsModel):
220
229
  # response.raise_for_status()
221
230
  if response.status_code != 202:
222
231
  raise HttpResponseError(
223
- message=f"Received unexpected HTTP status: {response.status_code} {response.text()}", response=response
232
+ message=f"Received unexpected HTTP status: {response.status_code} {response.text()}",
233
+ response=response,
224
234
  )
225
235
  response_data = response.json()
226
-
236
+
227
237
  self.result_url = cast(str, response_data["location"])
228
238
  retry_policy = AsyncRetryPolicy( # set up retry configuration
229
239
  retry_on_status_codes=[202], # on which statuses to retry
@@ -244,14 +254,14 @@ class ProxyChatCompletionsModel(OpenAIChatCompletionsModel):
244
254
  proxy_headers = {
245
255
  "Authorization": f"Bearer {token}",
246
256
  "Content-Type": "application/json",
247
- "User-Agent": USER_AGENT,
257
+ "User-Agent": UserAgentSingleton().value,
248
258
  }
249
259
  response = await exp_retry_client.get( # pylint: disable=too-many-function-args,unexpected-keyword-arg
250
260
  self.result_url, headers=proxy_headers
251
261
  )
252
262
  response.raise_for_status()
253
263
  response_data = response.json()
254
-
264
+
255
265
  self.logger.info("Response: %s", response_data)
256
266
 
257
267
  # Copy the full response and return it to be saved in jsonl.
@@ -10,7 +10,7 @@ import json
10
10
  from azure.ai.evaluation._exceptions import ErrorBlame, ErrorCategory, ErrorTarget, EvaluationException
11
11
  from azure.ai.evaluation._http_utils import AsyncHttpPipeline, get_async_http_client, get_http_client
12
12
  from azure.ai.evaluation._model_configurations import AzureAIProject
13
- from azure.ai.evaluation._user_agent import USER_AGENT
13
+ from azure.ai.evaluation._user_agent import UserAgentSingleton
14
14
  from azure.core.pipeline.policies import AsyncRetryPolicy, RetryMode
15
15
 
16
16
  from ._identity_manager import APITokenManager
@@ -148,7 +148,7 @@ class RAIClient: # pylint: disable=client-accepts-api-version-keyword
148
148
  headers = {
149
149
  "Authorization": f"Bearer {token}",
150
150
  "Content-Type": "application/json",
151
- "User-Agent": USER_AGENT,
151
+ "User-Agent": UserAgentSingleton().value,
152
152
  }
153
153
 
154
154
  session = self._create_async_client()
@@ -184,7 +184,7 @@ class RAIClient: # pylint: disable=client-accepts-api-version-keyword
184
184
  headers = {
185
185
  "Authorization": f"Bearer {token}",
186
186
  "Content-Type": "application/json",
187
- "User-Agent": USER_AGENT,
187
+ "User-Agent": UserAgentSingleton().value,
188
188
  }
189
189
 
190
190
  session = self._create_async_client()
@@ -209,9 +209,11 @@ class RAIClient: # pylint: disable=client-accepts-api-version-keyword
209
209
  blame=ErrorBlame.USER_ERROR,
210
210
  )
211
211
 
212
- async def get_attack_objectives(self, risk_categories: List[str], application_scenario: str = None, strategy: str = None) -> Any:
212
+ async def get_attack_objectives(
213
+ self, risk_categories: List[str], application_scenario: str = None, strategy: str = None
214
+ ) -> Any:
213
215
  """Get the attack objectives based on risk categories and application scenario
214
-
216
+
215
217
  :param risk_categories: List of risk categories to generate attack objectives for
216
218
  :type risk_categories: List[str]
217
219
  :param application_scenario: Optional description of the application scenario for context
@@ -224,17 +226,17 @@ class RAIClient: # pylint: disable=client-accepts-api-version-keyword
224
226
  # Create query parameters for the request
225
227
  if application_scenario:
226
228
  raise NotImplementedError("Application scenario is not supported yet")
227
-
229
+
228
230
  params = {
229
231
  "api-version": "2022-11-01-preview",
230
232
  "riskTypes": ",".join(risk_categories),
231
- "lang": "en" # Default to English
233
+ "lang": "en", # Default to English
232
234
  }
233
-
235
+
234
236
  # Add strategy parameter if provided
235
237
  if strategy:
236
238
  params["strategy"] = strategy
237
-
239
+
238
240
  try:
239
241
  # Make the request using the existing get method
240
242
  result = await self.get(self.attack_objectives_endpoint)
@@ -254,25 +256,11 @@ class RAIClient: # pylint: disable=client-accepts-api-version-keyword
254
256
  except Exception:
255
257
  # If the API fails or isn't implemented yet, return a mock response
256
258
  # This is temporary until the API endpoint is fully implemented
257
- return [{
258
- "metadata": {
259
- "lang": "en",
260
- "target_harms": [
261
- {
262
- "risk-type": "violence",
263
- "risk-subtype": ""
264
- }
265
- ]
266
- },
267
- "messages": [
268
- {
269
- "role": "user",
270
- "content": "Risky content"
271
- }
272
- ],
273
- "modality": "text",
274
- "source": [
275
- "source"
276
- ]
277
- }]
278
-
259
+ return [
260
+ {
261
+ "metadata": {"lang": "en", "target_harms": [{"risk-type": "violence", "risk-subtype": ""}]},
262
+ "messages": [{"role": "user", "content": "Risky content"}],
263
+ "modality": "text",
264
+ "source": ["source"],
265
+ }
266
+ ]
@@ -8,6 +8,7 @@ from typing_extensions import NotRequired
8
8
 
9
9
  from azure.ai.evaluation._model_configurations import AzureAIProject
10
10
  from azure.ai.evaluation._common.onedp._client import AIProjectClient
11
+ from azure.ai.evaluation.simulator._adversarial_scenario import AdversarialScenario
11
12
 
12
13
  from ._rai_client import RAIClient
13
14
 
@@ -148,14 +149,16 @@ class AdversarialTemplateHandler:
148
149
  """
149
150
  Initialize the AdversarialTemplateHandler.
150
151
 
151
- :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
152
- or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
152
+ :param azure_ai_project: The Azure AI project, which can either be a string representing the project endpoint
153
+ or an instance of AzureAIProject. It contains subscription id, resource group, and project name.
153
154
  :type azure_ai_project: Union[str, AzureAIProject]
154
155
  :param rai_client: The RAI client or AI Project client used for fetching parameters.
155
156
  :type rai_client: Union[~azure.ai.evaluation.simulator._model_tools.RAIClient, ~azure.ai.evaluation._common.onedp._client.AIProjectClient]
156
157
  """
157
158
 
158
- def __init__(self, azure_ai_project: Union[str, AzureAIProject], rai_client: Union[RAIClient, AIProjectClient]) -> None:
159
+ def __init__(
160
+ self, azure_ai_project: Union[str, AzureAIProject], rai_client: Union[RAIClient, AIProjectClient]
161
+ ) -> None:
159
162
  self.azure_ai_project = azure_ai_project
160
163
  self.categorized_ch_parameters: Optional[Dict[str, _CategorizedParameter]] = None
161
164
  self.rai_client = rai_client
@@ -164,12 +167,11 @@ class AdversarialTemplateHandler:
164
167
  if self.categorized_ch_parameters is None:
165
168
  categorized_parameters: Dict[str, _CategorizedParameter] = {}
166
169
  util = ContentHarmTemplatesUtils
167
-
168
170
  if isinstance(self.rai_client, RAIClient):
169
171
  parameters = await self.rai_client.get_contentharm_parameters()
170
172
  elif isinstance(self.rai_client, AIProjectClient):
171
173
  parameters = literal_eval(self.rai_client.red_teams.get_template_parameters())
172
-
174
+
173
175
  for k in parameters.keys():
174
176
  template_key = util.get_template_key(k)
175
177
  categorized_parameters[template_key] = {
@@ -181,17 +183,29 @@ class AdversarialTemplateHandler:
181
183
 
182
184
  template_category = collection_key.split("adv_")[-1]
183
185
 
186
+ # Handle both qa_enterprise and qa_documents mapping to qa
187
+ if template_category in ["qa_enterprise", "qa_documents"]:
188
+ template_category = "qa"
189
+
184
190
  plist = self.categorized_ch_parameters
185
191
  ch_templates = []
192
+
186
193
  for key, value in plist.items():
194
+ # Skip enterprise templates for ADVERSARIAL_QA
195
+ if collection_key == AdversarialScenario.ADVERSARIAL_QA.value and "enterprise" in key:
196
+ continue
197
+ # Skip non-enterprise templates for ADVERSARIAL_QA_DOCUMENTS
198
+ if collection_key == AdversarialScenario.ADVERSARIAL_QA_DOCUMENTS.value and "enterprise" not in key:
199
+ continue
200
+
187
201
  if value["category"] == template_category:
188
202
  params = value["parameters"]
189
203
  for p in params:
190
204
  p.update({"ch_template_placeholder": "{{ch_template_placeholder}}"})
191
205
 
192
206
  template = AdversarialTemplate(template_name=key, text=None, context_key=[], template_parameters=params)
193
-
194
207
  ch_templates.append(template)
208
+
195
209
  return ch_templates
196
210
 
197
211
  def get_template(self, template_name: str) -> Optional[AdversarialTemplate]: