azure-ai-evaluation 1.0.0__py3-none-any.whl → 1.0.0b1__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 (108) hide show
  1. azure/ai/evaluation/__init__.py +4 -26
  2. azure/ai/evaluation/_common/constants.py +2 -9
  3. azure/ai/evaluation/_common/rai_service.py +122 -302
  4. azure/ai/evaluation/_common/utils.py +35 -393
  5. azure/ai/evaluation/_constants.py +6 -28
  6. azure/ai/evaluation/_evaluate/{_batch_run → _batch_run_client}/__init__.py +2 -3
  7. azure/ai/evaluation/_evaluate/{_batch_run/eval_run_context.py → _batch_run_client/batch_run_context.py} +8 -25
  8. azure/ai/evaluation/_evaluate/{_batch_run → _batch_run_client}/code_client.py +30 -68
  9. azure/ai/evaluation/_evaluate/_batch_run_client/proxy_client.py +61 -0
  10. azure/ai/evaluation/_evaluate/_eval_run.py +40 -117
  11. azure/ai/evaluation/_evaluate/_evaluate.py +255 -416
  12. azure/ai/evaluation/_evaluate/_telemetry/__init__.py +19 -24
  13. azure/ai/evaluation/_evaluate/_utils.py +47 -108
  14. azure/ai/evaluation/_evaluators/_bleu/_bleu.py +19 -18
  15. azure/ai/evaluation/_evaluators/{_retrieval → _chat}/__init__.py +2 -2
  16. azure/ai/evaluation/_evaluators/_chat/_chat.py +350 -0
  17. azure/ai/evaluation/_evaluators/{_service_groundedness → _chat/retrieval}/__init__.py +2 -2
  18. azure/ai/evaluation/_evaluators/_chat/retrieval/_retrieval.py +163 -0
  19. azure/ai/evaluation/_evaluators/_chat/retrieval/retrieval.prompty +48 -0
  20. azure/ai/evaluation/_evaluators/_coherence/_coherence.py +93 -78
  21. azure/ai/evaluation/_evaluators/_coherence/coherence.prompty +39 -76
  22. azure/ai/evaluation/_evaluators/_content_safety/__init__.py +4 -0
  23. azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +68 -104
  24. azure/ai/evaluation/_evaluators/{_multimodal/_content_safety_multimodal_base.py → _content_safety/_content_safety_base.py} +35 -24
  25. azure/ai/evaluation/_evaluators/_content_safety/_content_safety_chat.py +296 -0
  26. azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +54 -105
  27. azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +52 -99
  28. azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +52 -101
  29. azure/ai/evaluation/_evaluators/_content_safety/_violence.py +51 -101
  30. azure/ai/evaluation/_evaluators/_eci/_eci.py +55 -45
  31. azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +20 -36
  32. azure/ai/evaluation/_evaluators/_fluency/_fluency.py +94 -76
  33. azure/ai/evaluation/_evaluators/_fluency/fluency.prompty +41 -66
  34. azure/ai/evaluation/_evaluators/_gleu/_gleu.py +17 -15
  35. azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +92 -113
  36. azure/ai/evaluation/_evaluators/_groundedness/groundedness.prompty +54 -0
  37. azure/ai/evaluation/_evaluators/_meteor/_meteor.py +27 -21
  38. azure/ai/evaluation/_evaluators/_protected_material/_protected_material.py +80 -89
  39. azure/ai/evaluation/_evaluators/_protected_materials/__init__.py +5 -0
  40. azure/ai/evaluation/_evaluators/_protected_materials/_protected_materials.py +104 -0
  41. azure/ai/evaluation/_evaluators/_qa/_qa.py +43 -25
  42. azure/ai/evaluation/_evaluators/_relevance/_relevance.py +101 -84
  43. azure/ai/evaluation/_evaluators/_relevance/relevance.prompty +47 -78
  44. azure/ai/evaluation/_evaluators/_rouge/_rouge.py +27 -27
  45. azure/ai/evaluation/_evaluators/_similarity/_similarity.py +45 -55
  46. azure/ai/evaluation/_evaluators/_similarity/similarity.prompty +5 -0
  47. azure/ai/evaluation/_evaluators/_xpia/xpia.py +106 -91
  48. azure/ai/evaluation/_exceptions.py +7 -28
  49. azure/ai/evaluation/_http_utils.py +134 -205
  50. azure/ai/evaluation/_model_configurations.py +8 -104
  51. azure/ai/evaluation/_version.py +1 -1
  52. azure/ai/evaluation/simulator/__init__.py +2 -3
  53. azure/ai/evaluation/simulator/_adversarial_scenario.py +1 -20
  54. azure/ai/evaluation/simulator/_adversarial_simulator.py +95 -116
  55. azure/ai/evaluation/simulator/_constants.py +1 -11
  56. azure/ai/evaluation/simulator/_conversation/__init__.py +13 -14
  57. azure/ai/evaluation/simulator/_conversation/_conversation.py +20 -20
  58. azure/ai/evaluation/simulator/_direct_attack_simulator.py +68 -34
  59. azure/ai/evaluation/simulator/_helpers/__init__.py +1 -1
  60. azure/ai/evaluation/simulator/_helpers/_simulator_data_classes.py +28 -31
  61. azure/ai/evaluation/simulator/_indirect_attack_simulator.py +95 -108
  62. azure/ai/evaluation/simulator/_model_tools/_identity_manager.py +22 -70
  63. azure/ai/evaluation/simulator/_model_tools/_proxy_completion_model.py +14 -30
  64. azure/ai/evaluation/simulator/_model_tools/_rai_client.py +14 -25
  65. azure/ai/evaluation/simulator/_model_tools/_template_handler.py +24 -68
  66. azure/ai/evaluation/simulator/_model_tools/models.py +21 -19
  67. azure/ai/evaluation/simulator/_prompty/task_query_response.prompty +10 -6
  68. azure/ai/evaluation/simulator/_prompty/task_simulate.prompty +5 -6
  69. azure/ai/evaluation/simulator/_tracing.py +28 -25
  70. azure/ai/evaluation/simulator/_utils.py +13 -34
  71. azure/ai/evaluation/simulator/simulator.py +579 -0
  72. azure_ai_evaluation-1.0.0b1.dist-info/METADATA +377 -0
  73. azure_ai_evaluation-1.0.0b1.dist-info/RECORD +97 -0
  74. {azure_ai_evaluation-1.0.0.dist-info → azure_ai_evaluation-1.0.0b1.dist-info}/WHEEL +1 -1
  75. azure/ai/evaluation/_common/_experimental.py +0 -172
  76. azure/ai/evaluation/_common/math.py +0 -89
  77. azure/ai/evaluation/_evaluate/_batch_run/proxy_client.py +0 -99
  78. azure/ai/evaluation/_evaluate/_batch_run/target_run_context.py +0 -46
  79. azure/ai/evaluation/_evaluators/_common/__init__.py +0 -13
  80. azure/ai/evaluation/_evaluators/_common/_base_eval.py +0 -344
  81. azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +0 -88
  82. azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +0 -133
  83. azure/ai/evaluation/_evaluators/_groundedness/groundedness_with_query.prompty +0 -113
  84. azure/ai/evaluation/_evaluators/_groundedness/groundedness_without_query.prompty +0 -99
  85. azure/ai/evaluation/_evaluators/_multimodal/__init__.py +0 -20
  86. azure/ai/evaluation/_evaluators/_multimodal/_content_safety_multimodal.py +0 -132
  87. azure/ai/evaluation/_evaluators/_multimodal/_hate_unfairness.py +0 -100
  88. azure/ai/evaluation/_evaluators/_multimodal/_protected_material.py +0 -124
  89. azure/ai/evaluation/_evaluators/_multimodal/_self_harm.py +0 -100
  90. azure/ai/evaluation/_evaluators/_multimodal/_sexual.py +0 -100
  91. azure/ai/evaluation/_evaluators/_multimodal/_violence.py +0 -100
  92. azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py +0 -112
  93. azure/ai/evaluation/_evaluators/_retrieval/retrieval.prompty +0 -93
  94. azure/ai/evaluation/_evaluators/_service_groundedness/_service_groundedness.py +0 -148
  95. azure/ai/evaluation/_vendor/__init__.py +0 -3
  96. azure/ai/evaluation/_vendor/rouge_score/__init__.py +0 -14
  97. azure/ai/evaluation/_vendor/rouge_score/rouge_scorer.py +0 -328
  98. azure/ai/evaluation/_vendor/rouge_score/scoring.py +0 -63
  99. azure/ai/evaluation/_vendor/rouge_score/tokenize.py +0 -63
  100. azure/ai/evaluation/_vendor/rouge_score/tokenizers.py +0 -53
  101. azure/ai/evaluation/simulator/_data_sources/__init__.py +0 -3
  102. azure/ai/evaluation/simulator/_data_sources/grounding.json +0 -1150
  103. azure/ai/evaluation/simulator/_prompty/__init__.py +0 -0
  104. azure/ai/evaluation/simulator/_simulator.py +0 -716
  105. azure_ai_evaluation-1.0.0.dist-info/METADATA +0 -595
  106. azure_ai_evaluation-1.0.0.dist-info/NOTICE.txt +0 -70
  107. azure_ai_evaluation-1.0.0.dist-info/RECORD +0 -119
  108. {azure_ai_evaluation-1.0.0.dist-info → azure_ai_evaluation-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -1,26 +1,54 @@
1
1
  # ---------------------------------------------------------
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  # ---------------------------------------------------------
4
- # pylint: disable=C0301,C0114,R0913,R0903
5
4
  # noqa: E501
5
+ import functools
6
6
  import logging
7
7
  from random import randint
8
- from typing import Callable, Optional, cast
8
+ from typing import Any, Callable, Dict, Optional
9
9
 
10
- from azure.ai.evaluation._common._experimental import experimental
11
- from azure.ai.evaluation._common.utils import validate_azure_ai_project
12
- from azure.ai.evaluation._exceptions import ErrorBlame, ErrorCategory, ErrorTarget, EvaluationException
10
+ from azure.identity import DefaultAzureCredential
11
+
12
+ from promptflow._sdk._telemetry import ActivityType, monitor_operation
13
+ from azure.ai.evaluation._exceptions import EvaluationException, ErrorBlame, ErrorCategory, ErrorTarget
13
14
  from azure.ai.evaluation.simulator import AdversarialScenario
14
15
  from azure.ai.evaluation._model_configurations import AzureAIProject
15
- from azure.core.credentials import TokenCredential
16
16
 
17
- from ._adversarial_simulator import AdversarialSimulator
18
17
  from ._model_tools import AdversarialTemplateHandler, ManagedIdentityAPITokenManager, RAIClient, TokenScope
18
+ from ._adversarial_simulator import AdversarialSimulator
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
- @experimental
23
+ def monitor_adversarial_scenario(func) -> Callable:
24
+ """Decorator to monitor adversarial scenario.
25
+
26
+ :param func: The function to be decorated.
27
+ :type func: Callable
28
+ :return: The decorated function.
29
+ :rtype: Callable
30
+ """
31
+
32
+ @functools.wraps(func)
33
+ def wrapper(*args, **kwargs):
34
+ scenario = str(kwargs.get("scenario", None))
35
+ max_conversation_turns = kwargs.get("max_conversation_turns", None)
36
+ max_simulation_results = kwargs.get("max_simulation_results", None)
37
+ decorated_func = monitor_operation(
38
+ activity_name="jailbreak.adversarial.simulator.call",
39
+ activity_type=ActivityType.PUBLICAPI,
40
+ custom_dimensions={
41
+ "scenario": scenario,
42
+ "max_conversation_turns": max_conversation_turns,
43
+ "max_simulation_results": max_simulation_results,
44
+ },
45
+ )(func)
46
+
47
+ return decorated_func(*args, **kwargs)
48
+
49
+ return wrapper
50
+
51
+
24
52
  class DirectAttackSimulator:
25
53
  """
26
54
  Initialize a UPIA (user prompt injected attack) jailbreak adversarial simulator with a project scope.
@@ -31,39 +59,44 @@ class DirectAttackSimulator:
31
59
  :type azure_ai_project: ~azure.ai.evaluation.AzureAIProject
32
60
  :param credential: The credential for connecting to Azure AI project.
33
61
  :type credential: ~azure.core.credentials.TokenCredential
34
-
35
- .. admonition:: Example:
36
-
37
- .. literalinclude:: ../samples/evaluation_samples_simulate.py
38
- :start-after: [START direct_attack_simulator]
39
- :end-before: [END direct_attack_simulator]
40
- :language: python
41
- :dedent: 8
42
- :caption: Run the DirectAttackSimulator to produce 2 results with 3 conversation turns each (6 messages in each result).
43
62
  """
44
63
 
45
- def __init__(self, *, azure_ai_project: AzureAIProject, credential: TokenCredential):
64
+ def __init__(self, *, azure_ai_project: AzureAIProject, credential=None):
46
65
  """Constructor."""
47
-
48
- try:
49
- self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
50
- except EvaluationException as e:
66
+ # check if azure_ai_project has the keys: subscription_id, resource_group_name, project_name, credential
67
+ if not all(key in azure_ai_project for key in ["subscription_id", "resource_group_name", "project_name"]):
68
+ msg = "azure_ai_project must contain keys: subscription_id, resource_group_name and project_name"
51
69
  raise EvaluationException(
52
- message=e.message,
53
- internal_message=e.internal_message,
70
+ message=msg,
71
+ internal_message=msg,
72
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
73
+ category=ErrorCategory.MISSING_FIELD,
74
+ blame=ErrorBlame.USER_ERROR,
75
+ )
76
+ # check the value of the keys in azure_ai_project is not none
77
+ if not all(azure_ai_project[key] for key in ["subscription_id", "resource_group_name", "project_name"]):
78
+ msg = "subscription_id, resource_group_name and project_name keys cannot be None"
79
+ raise EvaluationException(
80
+ message=msg,
81
+ internal_message=msg,
54
82
  target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
55
- category=e.category,
56
- blame=e.blame,
57
- ) from e
58
- self.credential = cast(TokenCredential, credential)
83
+ category=ErrorCategory.MISSING_FIELD,
84
+ blame=ErrorBlame.USER_ERROR,
85
+ )
86
+ if "credential" not in azure_ai_project and not credential:
87
+ credential = DefaultAzureCredential()
88
+ elif "credential" in azure_ai_project:
89
+ credential = azure_ai_project["credential"]
90
+ self.credential = credential
91
+ self.azure_ai_project = azure_ai_project
59
92
  self.token_manager = ManagedIdentityAPITokenManager(
60
93
  token_scope=TokenScope.DEFAULT_AZURE_MANAGEMENT,
61
94
  logger=logging.getLogger("AdversarialSimulator"),
62
- credential=self.credential,
95
+ credential=credential,
63
96
  )
64
- self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
97
+ self.rai_client = RAIClient(azure_ai_project=azure_ai_project, token_manager=self.token_manager)
65
98
  self.adversarial_template_handler = AdversarialTemplateHandler(
66
- azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
99
+ azure_ai_project=azure_ai_project, rai_client=self.rai_client
67
100
  )
68
101
 
69
102
  def _ensure_service_dependencies(self):
@@ -77,6 +110,7 @@ class DirectAttackSimulator:
77
110
  blame=ErrorBlame.USER_ERROR,
78
111
  )
79
112
 
113
+ # @monitor_adversarial_scenario
80
114
  async def __call__(
81
115
  self,
82
116
  *,
@@ -135,7 +169,7 @@ class DirectAttackSimulator:
135
169
  - '**$schema**': A string indicating the schema URL for the conversation format.
136
170
 
137
171
  The 'content' for 'assistant' role messages may includes the messages that your callback returned.
138
- :rtype: Dict[str, [List[Dict[str, Any]]]]
172
+ :rtype: Dict[str, [List[Dict[str, Any]]]] with two elements
139
173
 
140
174
  **Output format**
141
175
 
@@ -198,7 +232,7 @@ class DirectAttackSimulator:
198
232
  api_call_retry_sleep_sec=api_call_retry_sleep_sec,
199
233
  api_call_delay_sec=api_call_delay_sec,
200
234
  concurrent_async_task=concurrent_async_task,
201
- randomize_order=False,
235
+ randomize_order=True,
202
236
  randomization_seed=randomization_seed,
203
237
  )
204
238
  jb_sim = AdversarialSimulator(azure_ai_project=self.azure_ai_project, credential=self.credential)
@@ -212,7 +246,7 @@ class DirectAttackSimulator:
212
246
  api_call_delay_sec=api_call_delay_sec,
213
247
  concurrent_async_task=concurrent_async_task,
214
248
  _jailbreak_type="upia",
215
- randomize_order=False,
249
+ randomize_order=True,
216
250
  randomization_seed=randomization_seed,
217
251
  )
218
252
  return {"jailbreak": jb_sim_results, "regular": regular_sim_results}
@@ -1,4 +1,4 @@
1
- from ._language_suffix_mapping import SUPPORTED_LANGUAGES_MAPPING
2
1
  from ._simulator_data_classes import ConversationHistory, Turn
2
+ from ._language_suffix_mapping import SUPPORTED_LANGUAGES_MAPPING
3
3
 
4
4
  __all__ = ["ConversationHistory", "Turn", "SUPPORTED_LANGUAGES_MAPPING"]
@@ -3,7 +3,7 @@
3
3
  # ---------------------------------------------------------
4
4
  # pylint: disable=C0103,C0114,C0116
5
5
  from dataclasses import dataclass
6
- from typing import Dict, List, Optional, Union
6
+ from typing import Union
7
7
 
8
8
  from azure.ai.evaluation.simulator._conversation.constants import ConversationRole
9
9
 
@@ -18,34 +18,28 @@ class Turn:
18
18
 
19
19
  role: Union[str, ConversationRole]
20
20
  content: str
21
- context: Optional[str] = None
21
+ context: str = None
22
22
 
23
- def to_dict(self) -> Dict[str, Optional[str]]:
23
+ def to_dict(self):
24
24
  """
25
25
  Convert the conversation turn to a dictionary.
26
26
 
27
- :returns: A dictionary representation of the conversation turn.
28
- :rtype: Dict[str, Optional[str]]
27
+ Returns:
28
+ dict: A dictionary representation of the conversation turn.
29
29
  """
30
30
  return {
31
31
  "role": self.role.value if isinstance(self.role, ConversationRole) else self.role,
32
32
  "content": self.content,
33
- "context": str(self.context),
33
+ "context": self.context,
34
34
  }
35
35
 
36
- def to_context_free_dict(self) -> Dict[str, Optional[str]]:
36
+ def __repr__(self):
37
37
  """
38
- Convert the conversation turn to a dictionary without context.
38
+ Return the string representation of the conversation turn.
39
39
 
40
- :returns: A dictionary representation of the conversation turn without context.
41
- :rtype: Dict[str, Optional[str]]
40
+ Returns:
41
+ str: A string representation of the conversation turn.
42
42
  """
43
- return {
44
- "role": self.role.value if isinstance(self.role, ConversationRole) else self.role,
45
- "content": self.content,
46
- }
47
-
48
- def __repr__(self):
49
43
  return f"Turn(role={self.role}, content={self.content})"
50
44
 
51
45
 
@@ -54,43 +48,46 @@ class ConversationHistory:
54
48
  Conversation history class to keep track of the conversation turns in a conversation.
55
49
  """
56
50
 
57
- def __init__(self) -> None:
51
+ def __init__(self):
58
52
  """
59
53
  Initializes the conversation history with an empty list of turns.
60
54
  """
61
- self.history: List[Turn] = []
55
+ self.history = []
62
56
 
63
- def add_to_history(self, turn: Turn) -> None:
57
+ def add_to_history(self, turn: Turn):
64
58
  """
65
59
  Adds a turn to the conversation history.
66
60
 
67
- :param turn: The conversation turn to add.
68
- :type turn: Turn
61
+ Args:
62
+ turn (Turn): The conversation turn to add.
69
63
  """
70
64
  self.history.append(turn)
71
65
 
72
- def to_list(self) -> List[Dict[str, Optional[str]]]:
66
+ def to_list(self):
73
67
  """
74
68
  Converts the conversation history to a list of dictionaries.
75
69
 
76
- :returns: A list of dictionaries representing the conversation turns.
77
- :rtype: List[Dict[str, str]]
70
+ Returns:
71
+ list: A list of dictionaries representing the conversation turns.
78
72
  """
79
73
  return [turn.to_dict() for turn in self.history]
80
74
 
81
- def to_context_free_list(self) -> List[Dict[str, Optional[str]]]:
75
+ def get_length(self):
82
76
  """
83
- Converts the conversation history to a list of dictionaries without context.
77
+ Returns the length of the conversation.
84
78
 
85
- :returns: A list of dictionaries representing the conversation turns without context.
86
- :rtype: List[Dict[str, str]]
79
+ Returns:
80
+ int: The number of turns in the conversation history.
87
81
  """
88
- return [turn.to_context_free_dict() for turn in self.history]
89
-
90
- def __len__(self) -> int:
91
82
  return len(self.history)
92
83
 
93
84
  def __repr__(self):
85
+ """
86
+ Returns the string representation of the conversation history.
87
+
88
+ Returns:
89
+ str: A string representation of the conversation history.
90
+ """
94
91
  for turn in self.history:
95
92
  print(turn)
96
93
  return ""
@@ -1,30 +1,54 @@
1
1
  # ---------------------------------------------------------
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  # ---------------------------------------------------------
4
- # pylint: disable=C0301,C0114,R0913,R0903
5
4
  # noqa: E501
6
- import asyncio
5
+ import functools
7
6
  import logging
8
- from typing import Callable, cast
7
+ from typing import Any, Callable, Dict
9
8
 
10
- from tqdm import tqdm
9
+ from azure.identity import DefaultAzureCredential
11
10
 
12
- from azure.ai.evaluation._common.utils import validate_azure_ai_project
13
- from azure.ai.evaluation._common._experimental import experimental
14
- from azure.ai.evaluation._exceptions import ErrorBlame, ErrorCategory, ErrorTarget, EvaluationException
15
- from azure.ai.evaluation.simulator import AdversarialScenarioJailbreak, SupportedLanguages
11
+ from promptflow._sdk._telemetry import ActivityType, monitor_operation
12
+ from azure.ai.evaluation.simulator import AdversarialScenario
16
13
  from azure.ai.evaluation._model_configurations import AzureAIProject
17
- from azure.core.credentials import TokenCredential
18
-
19
- from ._adversarial_simulator import AdversarialSimulator, JsonLineList
20
14
 
21
15
  from ._model_tools import AdversarialTemplateHandler, ManagedIdentityAPITokenManager, RAIClient, TokenScope
16
+ from azure.ai.evaluation._exceptions import EvaluationException, ErrorBlame, ErrorCategory, ErrorTarget
17
+ from ._adversarial_simulator import AdversarialSimulator
22
18
 
23
19
  logger = logging.getLogger(__name__)
24
20
 
25
21
 
26
- @experimental
27
- class IndirectAttackSimulator(AdversarialSimulator):
22
+ def monitor_adversarial_scenario(func) -> Callable:
23
+ """Decorator to monitor adversarial scenario.
24
+
25
+ :param func: The function to be decorated.
26
+ :type func: Callable
27
+ :return: The decorated function.
28
+ :rtype: Callable
29
+ """
30
+
31
+ @functools.wraps(func)
32
+ def wrapper(*args, **kwargs):
33
+ scenario = str(kwargs.get("scenario", None))
34
+ max_conversation_turns = kwargs.get("max_conversation_turns", None)
35
+ max_simulation_results = kwargs.get("max_simulation_results", None)
36
+ decorated_func = monitor_operation(
37
+ activity_name="xpia.adversarial.simulator.call",
38
+ activity_type=ActivityType.PUBLICAPI,
39
+ custom_dimensions={
40
+ "scenario": scenario,
41
+ "max_conversation_turns": max_conversation_turns,
42
+ "max_simulation_results": max_simulation_results,
43
+ },
44
+ )(func)
45
+
46
+ return decorated_func(*args, **kwargs)
47
+
48
+ return wrapper
49
+
50
+
51
+ class IndirectAttackSimulator:
28
52
  """
29
53
  Initializes the XPIA (cross domain prompt injected attack) jailbreak adversarial simulator with a project scope.
30
54
 
@@ -33,42 +57,44 @@ class IndirectAttackSimulator(AdversarialSimulator):
33
57
  :type azure_ai_project: ~azure.ai.evaluation.AzureAIProject
34
58
  :param credential: The credential for connecting to Azure AI project.
35
59
  :type credential: ~azure.core.credentials.TokenCredential
36
-
37
- .. admonition:: Example:
38
-
39
- .. literalinclude:: ../samples/evaluation_samples_simulate.py
40
- :start-after: [START indirect_attack_simulator]
41
- :end-before: [END indirect_attack_simulator]
42
- :language: python
43
- :dedent: 8
44
- :caption: Run the IndirectAttackSimulator to produce 1 result with 1 conversation turn (2 messages in the result).
45
60
  """
46
61
 
47
- def __init__(self, *, azure_ai_project: AzureAIProject, credential: TokenCredential):
62
+ def __init__(self, *, azure_ai_project: AzureAIProject, credential=None):
48
63
  """Constructor."""
49
-
50
- try:
51
- self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
52
- except EvaluationException as e:
64
+ # check if azure_ai_project has the keys: subscription_id, resource_group_name, project_name, credential
65
+ if not all(key in azure_ai_project for key in ["subscription_id", "resource_group_name", "project_name"]):
66
+ msg = "azure_ai_project must contain keys: subscription_id, resource_group_name and project_name"
53
67
  raise EvaluationException(
54
- message=e.message,
55
- internal_message=e.internal_message,
68
+ message=msg,
69
+ internal_message=msg,
56
70
  target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
57
- category=e.category,
58
- blame=e.blame,
59
- ) from e
60
-
61
- self.credential = cast(TokenCredential, credential)
71
+ category=ErrorCategory.MISSING_FIELD,
72
+ blame=ErrorBlame.USER_ERROR,
73
+ )
74
+ if not all(azure_ai_project[key] for key in ["subscription_id", "resource_group_name", "project_name"]):
75
+ msg = "subscription_id, resource_group_name and project_name keys cannot be None"
76
+ raise EvaluationException(
77
+ message=msg,
78
+ internal_message=msg,
79
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
80
+ category=ErrorCategory.MISSING_FIELD,
81
+ blame=ErrorBlame.USER_ERROR,
82
+ )
83
+ if "credential" not in azure_ai_project and not credential:
84
+ credential = DefaultAzureCredential()
85
+ elif "credential" in azure_ai_project:
86
+ credential = azure_ai_project["credential"]
87
+ self.credential = credential
88
+ self.azure_ai_project = azure_ai_project
62
89
  self.token_manager = ManagedIdentityAPITokenManager(
63
90
  token_scope=TokenScope.DEFAULT_AZURE_MANAGEMENT,
64
91
  logger=logging.getLogger("AdversarialSimulator"),
65
- credential=self.credential,
92
+ credential=credential,
66
93
  )
67
- self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
94
+ self.rai_client = RAIClient(azure_ai_project=azure_ai_project, token_manager=self.token_manager)
68
95
  self.adversarial_template_handler = AdversarialTemplateHandler(
69
- azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
96
+ azure_ai_project=azure_ai_project, rai_client=self.rai_client
70
97
  )
71
- super().__init__(azure_ai_project=azure_ai_project, credential=credential)
72
98
 
73
99
  def _ensure_service_dependencies(self):
74
100
  if self.rai_client is None:
@@ -81,25 +107,33 @@ class IndirectAttackSimulator(AdversarialSimulator):
81
107
  blame=ErrorBlame.USER_ERROR,
82
108
  )
83
109
 
110
+ # @monitor_adversarial_scenario
84
111
  async def __call__(
85
112
  self,
86
113
  *,
114
+ scenario: AdversarialScenario,
87
115
  target: Callable,
116
+ max_conversation_turns: int = 1,
88
117
  max_simulation_results: int = 3,
89
118
  api_call_retry_limit: int = 3,
90
119
  api_call_retry_sleep_sec: int = 1,
91
120
  api_call_delay_sec: int = 0,
92
121
  concurrent_async_task: int = 3,
93
- **kwargs,
94
122
  ):
95
123
  """
96
124
  Initializes the XPIA (cross domain prompt injected attack) jailbreak adversarial simulator with a project scope.
97
125
  This simulator converses with your AI system using prompts injected into the context to interrupt normal
98
126
  expected functionality by eliciting manipulated content, intrusion and attempting to gather information outside
99
127
  the scope of your AI system.
128
+
129
+ :keyword scenario: Enum value specifying the adversarial scenario used for generating inputs.
130
+ :paramtype scenario: azure.ai.evaluation.simulator.AdversarialScenario
100
131
  :keyword target: The target function to simulate adversarial inputs against.
101
132
  This function should be asynchronous and accept a dictionary representing the adversarial input.
102
133
  :paramtype target: Callable
134
+ :keyword max_conversation_turns: The maximum number of conversation turns to simulate.
135
+ Defaults to 1.
136
+ :paramtype max_conversation_turns: int
103
137
  :keyword max_simulation_results: The maximum number of simulation results to return.
104
138
  Defaults to 3.
105
139
  :paramtype max_simulation_results: int
@@ -136,11 +170,11 @@ class IndirectAttackSimulator(AdversarialSimulator):
136
170
  'template_parameters': {},
137
171
  'messages': [
138
172
  {
139
- 'content': '<adversarial query>',
173
+ 'content': '<jailbreak prompt> <adversarial query>',
140
174
  'role': 'user'
141
175
  },
142
176
  {
143
- 'content': "<response from your callback>",
177
+ 'content': "<response from endpoint>",
144
178
  'role': 'assistant',
145
179
  'context': None
146
180
  }
@@ -149,72 +183,25 @@ class IndirectAttackSimulator(AdversarialSimulator):
149
183
  }]
150
184
  }
151
185
  """
152
- # values that cannot be changed:
153
- scenario = AdversarialScenarioJailbreak.ADVERSARIAL_INDIRECT_JAILBREAK
154
- max_conversation_turns = 2
155
- language = SupportedLanguages.English
156
- self._ensure_service_dependencies()
157
- templates = await self.adversarial_template_handler._get_content_harm_template_collections(scenario.value)
158
- concurrent_async_task = min(concurrent_async_task, 1000)
159
- semaphore = asyncio.Semaphore(concurrent_async_task)
160
- sim_results = []
161
- tasks = []
162
- total_tasks = sum(len(t.template_parameters) for t in templates)
163
- if max_simulation_results > total_tasks:
164
- logger.warning(
165
- "Cannot provide %s results due to maximum number of adversarial simulations that can be generated: %s."
166
- "\n %s simulations will be generated.",
167
- max_simulation_results,
168
- total_tasks,
169
- total_tasks,
186
+ if scenario not in AdversarialScenario.__members__.values():
187
+ msg = f"Invalid scenario: {scenario}. Supported scenarios: {AdversarialScenario.__members__.values()}"
188
+ raise EvaluationException(
189
+ message=msg,
190
+ internal_message=msg,
191
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
192
+ category=ErrorCategory.INVALID_VALUE,
193
+ blame=ErrorBlame.USER_ERROR,
170
194
  )
171
- total_tasks = min(total_tasks, max_simulation_results)
172
- progress_bar = tqdm(
173
- total=total_tasks,
174
- desc="generating jailbreak simulations",
175
- ncols=100,
176
- unit="simulations",
195
+ jb_sim = AdversarialSimulator(azure_ai_project=self.azure_ai_project, credential=self.credential)
196
+ jb_sim_results = await jb_sim(
197
+ scenario=scenario,
198
+ target=target,
199
+ max_conversation_turns=max_conversation_turns,
200
+ max_simulation_results=max_simulation_results,
201
+ api_call_retry_limit=api_call_retry_limit,
202
+ api_call_retry_sleep_sec=api_call_retry_sleep_sec,
203
+ api_call_delay_sec=api_call_delay_sec,
204
+ concurrent_async_task=concurrent_async_task,
205
+ _jailbreak_type="xpia",
177
206
  )
178
- for template in templates:
179
- for parameter in template.template_parameters:
180
- tasks.append(
181
- asyncio.create_task(
182
- self._simulate_async(
183
- target=target,
184
- template=template,
185
- parameters=parameter,
186
- max_conversation_turns=max_conversation_turns,
187
- api_call_retry_limit=api_call_retry_limit,
188
- api_call_retry_sleep_sec=api_call_retry_sleep_sec,
189
- api_call_delay_sec=api_call_delay_sec,
190
- language=language,
191
- semaphore=semaphore,
192
- )
193
- )
194
- )
195
- if len(tasks) >= max_simulation_results:
196
- break
197
- if len(tasks) >= max_simulation_results:
198
- break
199
- for task in asyncio.as_completed(tasks):
200
- completed_task = await task # type: ignore
201
- template_parameters = completed_task.get("template_parameters", {}) # type: ignore
202
- xpia_attack_type = template_parameters.get("xpia_attack_type", "") # type: ignore
203
- action = template_parameters.get("action", "") # type: ignore
204
- document_type = template_parameters.get("document_type", "") # type: ignore
205
- sim_results.append(
206
- {
207
- "messages": completed_task["messages"], # type: ignore
208
- "$schema": "http://azureml/sdk-2-0/ChatConversation.json",
209
- "template_parameters": {
210
- "metadata": {
211
- "xpia_attack_type": xpia_attack_type,
212
- "action": action,
213
- "document_type": document_type,
214
- },
215
- },
216
- }
217
- )
218
- progress_bar.update(1)
219
- progress_bar.close()
220
- return JsonLineList(sim_results)
207
+ return jb_sim_results