azure-ai-evaluation 0.0.0b0__py3-none-any.whl → 1.0.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 (122) hide show
  1. azure/ai/evaluation/__init__.py +82 -0
  2. azure/ai/evaluation/_common/__init__.py +16 -0
  3. azure/ai/evaluation/_common/_experimental.py +172 -0
  4. azure/ai/evaluation/_common/constants.py +72 -0
  5. azure/ai/evaluation/_common/math.py +89 -0
  6. azure/ai/evaluation/_common/rai_service.py +632 -0
  7. azure/ai/evaluation/_common/utils.py +445 -0
  8. azure/ai/evaluation/_constants.py +72 -0
  9. azure/ai/evaluation/_evaluate/__init__.py +3 -0
  10. azure/ai/evaluation/_evaluate/_batch_run/__init__.py +9 -0
  11. azure/ai/evaluation/_evaluate/_batch_run/code_client.py +188 -0
  12. azure/ai/evaluation/_evaluate/_batch_run/eval_run_context.py +89 -0
  13. azure/ai/evaluation/_evaluate/_batch_run/proxy_client.py +99 -0
  14. azure/ai/evaluation/_evaluate/_batch_run/target_run_context.py +46 -0
  15. azure/ai/evaluation/_evaluate/_eval_run.py +571 -0
  16. azure/ai/evaluation/_evaluate/_evaluate.py +850 -0
  17. azure/ai/evaluation/_evaluate/_telemetry/__init__.py +179 -0
  18. azure/ai/evaluation/_evaluate/_utils.py +298 -0
  19. azure/ai/evaluation/_evaluators/__init__.py +3 -0
  20. azure/ai/evaluation/_evaluators/_bleu/__init__.py +9 -0
  21. azure/ai/evaluation/_evaluators/_bleu/_bleu.py +72 -0
  22. azure/ai/evaluation/_evaluators/_coherence/__init__.py +7 -0
  23. azure/ai/evaluation/_evaluators/_coherence/_coherence.py +107 -0
  24. azure/ai/evaluation/_evaluators/_coherence/coherence.prompty +99 -0
  25. azure/ai/evaluation/_evaluators/_common/__init__.py +13 -0
  26. azure/ai/evaluation/_evaluators/_common/_base_eval.py +344 -0
  27. azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +88 -0
  28. azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +133 -0
  29. azure/ai/evaluation/_evaluators/_content_safety/__init__.py +17 -0
  30. azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +144 -0
  31. azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +129 -0
  32. azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +123 -0
  33. azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +125 -0
  34. azure/ai/evaluation/_evaluators/_content_safety/_violence.py +126 -0
  35. azure/ai/evaluation/_evaluators/_eci/__init__.py +0 -0
  36. azure/ai/evaluation/_evaluators/_eci/_eci.py +89 -0
  37. azure/ai/evaluation/_evaluators/_f1_score/__init__.py +9 -0
  38. azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +157 -0
  39. azure/ai/evaluation/_evaluators/_fluency/__init__.py +9 -0
  40. azure/ai/evaluation/_evaluators/_fluency/_fluency.py +104 -0
  41. azure/ai/evaluation/_evaluators/_fluency/fluency.prompty +86 -0
  42. azure/ai/evaluation/_evaluators/_gleu/__init__.py +9 -0
  43. azure/ai/evaluation/_evaluators/_gleu/_gleu.py +69 -0
  44. azure/ai/evaluation/_evaluators/_groundedness/__init__.py +9 -0
  45. azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +144 -0
  46. azure/ai/evaluation/_evaluators/_groundedness/groundedness_with_query.prompty +113 -0
  47. azure/ai/evaluation/_evaluators/_groundedness/groundedness_without_query.prompty +99 -0
  48. azure/ai/evaluation/_evaluators/_meteor/__init__.py +9 -0
  49. azure/ai/evaluation/_evaluators/_meteor/_meteor.py +90 -0
  50. azure/ai/evaluation/_evaluators/_multimodal/__init__.py +20 -0
  51. azure/ai/evaluation/_evaluators/_multimodal/_content_safety_multimodal.py +132 -0
  52. azure/ai/evaluation/_evaluators/_multimodal/_content_safety_multimodal_base.py +55 -0
  53. azure/ai/evaluation/_evaluators/_multimodal/_hate_unfairness.py +100 -0
  54. azure/ai/evaluation/_evaluators/_multimodal/_protected_material.py +124 -0
  55. azure/ai/evaluation/_evaluators/_multimodal/_self_harm.py +100 -0
  56. azure/ai/evaluation/_evaluators/_multimodal/_sexual.py +100 -0
  57. azure/ai/evaluation/_evaluators/_multimodal/_violence.py +100 -0
  58. azure/ai/evaluation/_evaluators/_protected_material/__init__.py +5 -0
  59. azure/ai/evaluation/_evaluators/_protected_material/_protected_material.py +113 -0
  60. azure/ai/evaluation/_evaluators/_qa/__init__.py +9 -0
  61. azure/ai/evaluation/_evaluators/_qa/_qa.py +93 -0
  62. azure/ai/evaluation/_evaluators/_relevance/__init__.py +9 -0
  63. azure/ai/evaluation/_evaluators/_relevance/_relevance.py +114 -0
  64. azure/ai/evaluation/_evaluators/_relevance/relevance.prompty +100 -0
  65. azure/ai/evaluation/_evaluators/_retrieval/__init__.py +9 -0
  66. azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py +112 -0
  67. azure/ai/evaluation/_evaluators/_retrieval/retrieval.prompty +93 -0
  68. azure/ai/evaluation/_evaluators/_rouge/__init__.py +10 -0
  69. azure/ai/evaluation/_evaluators/_rouge/_rouge.py +98 -0
  70. azure/ai/evaluation/_evaluators/_service_groundedness/__init__.py +9 -0
  71. azure/ai/evaluation/_evaluators/_service_groundedness/_service_groundedness.py +148 -0
  72. azure/ai/evaluation/_evaluators/_similarity/__init__.py +9 -0
  73. azure/ai/evaluation/_evaluators/_similarity/_similarity.py +140 -0
  74. azure/ai/evaluation/_evaluators/_similarity/similarity.prompty +66 -0
  75. azure/ai/evaluation/_evaluators/_xpia/__init__.py +5 -0
  76. azure/ai/evaluation/_evaluators/_xpia/xpia.py +125 -0
  77. azure/ai/evaluation/_exceptions.py +128 -0
  78. azure/ai/evaluation/_http_utils.py +466 -0
  79. azure/ai/evaluation/_model_configurations.py +123 -0
  80. azure/ai/evaluation/_user_agent.py +6 -0
  81. azure/ai/evaluation/_vendor/__init__.py +3 -0
  82. azure/ai/evaluation/_vendor/rouge_score/__init__.py +14 -0
  83. azure/ai/evaluation/_vendor/rouge_score/rouge_scorer.py +328 -0
  84. azure/ai/evaluation/_vendor/rouge_score/scoring.py +63 -0
  85. azure/ai/evaluation/_vendor/rouge_score/tokenize.py +63 -0
  86. azure/ai/evaluation/_vendor/rouge_score/tokenizers.py +53 -0
  87. azure/ai/evaluation/_version.py +5 -0
  88. azure/ai/evaluation/py.typed +0 -0
  89. azure/ai/evaluation/simulator/__init__.py +16 -0
  90. azure/ai/evaluation/simulator/_adversarial_scenario.py +46 -0
  91. azure/ai/evaluation/simulator/_adversarial_simulator.py +471 -0
  92. azure/ai/evaluation/simulator/_constants.py +27 -0
  93. azure/ai/evaluation/simulator/_conversation/__init__.py +316 -0
  94. azure/ai/evaluation/simulator/_conversation/_conversation.py +178 -0
  95. azure/ai/evaluation/simulator/_conversation/constants.py +30 -0
  96. azure/ai/evaluation/simulator/_data_sources/__init__.py +3 -0
  97. azure/ai/evaluation/simulator/_data_sources/grounding.json +1150 -0
  98. azure/ai/evaluation/simulator/_direct_attack_simulator.py +218 -0
  99. azure/ai/evaluation/simulator/_helpers/__init__.py +4 -0
  100. azure/ai/evaluation/simulator/_helpers/_language_suffix_mapping.py +17 -0
  101. azure/ai/evaluation/simulator/_helpers/_simulator_data_classes.py +96 -0
  102. azure/ai/evaluation/simulator/_indirect_attack_simulator.py +220 -0
  103. azure/ai/evaluation/simulator/_model_tools/__init__.py +23 -0
  104. azure/ai/evaluation/simulator/_model_tools/_identity_manager.py +195 -0
  105. azure/ai/evaluation/simulator/_model_tools/_proxy_completion_model.py +244 -0
  106. azure/ai/evaluation/simulator/_model_tools/_rai_client.py +168 -0
  107. azure/ai/evaluation/simulator/_model_tools/_template_handler.py +201 -0
  108. azure/ai/evaluation/simulator/_model_tools/models.py +614 -0
  109. azure/ai/evaluation/simulator/_prompty/__init__.py +0 -0
  110. azure/ai/evaluation/simulator/_prompty/task_query_response.prompty +65 -0
  111. azure/ai/evaluation/simulator/_prompty/task_simulate.prompty +37 -0
  112. azure/ai/evaluation/simulator/_simulator.py +716 -0
  113. azure/ai/evaluation/simulator/_tracing.py +89 -0
  114. azure/ai/evaluation/simulator/_utils.py +132 -0
  115. azure_ai_evaluation-1.0.0.dist-info/METADATA +595 -0
  116. azure_ai_evaluation-1.0.0.dist-info/NOTICE.txt +70 -0
  117. azure_ai_evaluation-1.0.0.dist-info/RECORD +119 -0
  118. {azure_ai_evaluation-0.0.0b0.dist-info → azure_ai_evaluation-1.0.0.dist-info}/WHEEL +1 -1
  119. azure_ai_evaluation-1.0.0.dist-info/top_level.txt +1 -0
  120. azure_ai_evaluation-0.0.0b0.dist-info/METADATA +0 -7
  121. azure_ai_evaluation-0.0.0b0.dist-info/RECORD +0 -4
  122. azure_ai_evaluation-0.0.0b0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,218 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ # pylint: disable=C0301,C0114,R0913,R0903
5
+ # noqa: E501
6
+ import logging
7
+ from random import randint
8
+ from typing import Callable, Optional, cast
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
13
+ from azure.ai.evaluation.simulator import AdversarialScenario
14
+ from azure.ai.evaluation._model_configurations import AzureAIProject
15
+ from azure.core.credentials import TokenCredential
16
+
17
+ from ._adversarial_simulator import AdversarialSimulator
18
+ from ._model_tools import AdversarialTemplateHandler, ManagedIdentityAPITokenManager, RAIClient, TokenScope
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @experimental
24
+ class DirectAttackSimulator:
25
+ """
26
+ Initialize a UPIA (user prompt injected attack) jailbreak adversarial simulator with a project scope.
27
+ This simulator converses with your AI system using prompts designed to interrupt normal functionality.
28
+
29
+ :param azure_ai_project: The scope of the Azure AI project. It contains subscription id, resource group, and project
30
+ name.
31
+ :type azure_ai_project: ~azure.ai.evaluation.AzureAIProject
32
+ :param credential: The credential for connecting to Azure AI project.
33
+ :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
+ """
44
+
45
+ def __init__(self, *, azure_ai_project: AzureAIProject, credential: TokenCredential):
46
+ """Constructor."""
47
+
48
+ try:
49
+ self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
50
+ except EvaluationException as e:
51
+ raise EvaluationException(
52
+ message=e.message,
53
+ internal_message=e.internal_message,
54
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
55
+ category=e.category,
56
+ blame=e.blame,
57
+ ) from e
58
+ self.credential = cast(TokenCredential, credential)
59
+ self.token_manager = ManagedIdentityAPITokenManager(
60
+ token_scope=TokenScope.DEFAULT_AZURE_MANAGEMENT,
61
+ logger=logging.getLogger("AdversarialSimulator"),
62
+ credential=self.credential,
63
+ )
64
+ self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
65
+ self.adversarial_template_handler = AdversarialTemplateHandler(
66
+ azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
67
+ )
68
+
69
+ def _ensure_service_dependencies(self):
70
+ if self.rai_client is None:
71
+ msg = "RAI service is required for simulation, but an RAI client was not provided."
72
+ raise EvaluationException(
73
+ message=msg,
74
+ internal_message=msg,
75
+ target=ErrorTarget.ADVERSARIAL_SIMULATOR,
76
+ category=ErrorCategory.MISSING_FIELD,
77
+ blame=ErrorBlame.USER_ERROR,
78
+ )
79
+
80
+ async def __call__(
81
+ self,
82
+ *,
83
+ scenario: AdversarialScenario,
84
+ target: Callable,
85
+ max_conversation_turns: int = 1,
86
+ max_simulation_results: int = 3,
87
+ api_call_retry_limit: int = 3,
88
+ api_call_retry_sleep_sec: int = 1,
89
+ api_call_delay_sec: int = 0,
90
+ concurrent_async_task: int = 3,
91
+ randomization_seed: Optional[int] = None,
92
+ ):
93
+ """
94
+ Executes the adversarial simulation and UPIA (user prompt injected attack) jailbreak adversarial simulation
95
+ against a specified target function asynchronously.
96
+
97
+ :keyword scenario: Enum value specifying the adversarial scenario used for generating inputs.
98
+ example:
99
+
100
+ - :py:const:`azure.ai.evaluation.simulator.AdversarialScenario.ADVERSARIAL_QA`
101
+ - :py:const:`azure.ai.evaluation.simulator.AdversarialScenario.ADVERSARIAL_CONVERSATION`
102
+ :paramtype scenario: azure.ai.evaluation.simulator.AdversarialScenario
103
+ :keyword target: The target function to simulate adversarial inputs against.
104
+ This function should be asynchronous and accept a dictionary representing the adversarial input.
105
+ :paramtype target: Callable
106
+ :keyword max_conversation_turns: The maximum number of conversation turns to simulate.
107
+ Defaults to 1.
108
+ :paramtype max_conversation_turns: int
109
+ :keyword max_simulation_results: The maximum number of simulation results to return.
110
+ Defaults to 3.
111
+ :paramtype max_simulation_results: int
112
+ :keyword api_call_retry_limit: The maximum number of retries for each API call within the simulation.
113
+ Defaults to 3.
114
+ :paramtype api_call_retry_limit: int
115
+ :keyword api_call_retry_sleep_sec: The sleep duration (in seconds) between retries for API calls.
116
+ Defaults to 1 second.
117
+ :paramtype api_call_retry_sleep_sec: int
118
+ :keyword api_call_delay_sec: The delay (in seconds) before making an API call.
119
+ This can be used to avoid hitting rate limits. Defaults to 0 seconds.
120
+ :paramtype api_call_delay_sec: int
121
+ :keyword concurrent_async_task: The number of asynchronous tasks to run concurrently during the simulation.
122
+ Defaults to 3.
123
+ :paramtype concurrent_async_task: int
124
+ :keyword randomization_seed: Seed used to randomize prompt selection, shared by both jailbreak
125
+ and regular simulation to ensure consistent results. If not provided, a random seed will be generated
126
+ and shared between simulations.
127
+ :paramtype randomization_seed: Optional[int]
128
+ :return: A list of dictionaries, each representing a simulated conversation. Each dictionary contains:
129
+
130
+ - 'template_parameters': A dictionary with parameters used in the conversation template,
131
+ including 'conversation_starter'.
132
+ - 'messages': A list of dictionaries, each representing a turn in the conversation.
133
+ Each message dictionary includes 'content' (the message text) and
134
+ 'role' (indicating whether the message is from the 'user' or the 'assistant').
135
+ - '**$schema**': A string indicating the schema URL for the conversation format.
136
+
137
+ The 'content' for 'assistant' role messages may includes the messages that your callback returned.
138
+ :rtype: Dict[str, [List[Dict[str, Any]]]]
139
+
140
+ **Output format**
141
+
142
+ .. code-block:: python
143
+
144
+ return_value = {
145
+ "jailbreak": [
146
+ {
147
+ 'template_parameters': {},
148
+ 'messages': [
149
+ {
150
+ 'content': '<jailbreak prompt> <adversarial query>',
151
+ 'role': 'user'
152
+ },
153
+ {
154
+ 'content': "<response from endpoint>",
155
+ 'role': 'assistant',
156
+ 'context': None
157
+ }
158
+ ],
159
+ '$schema': 'http://azureml/sdk-2-0/ChatConversation.json'
160
+ }],
161
+ "regular": [
162
+ {
163
+ 'template_parameters': {},
164
+ 'messages': [
165
+ {
166
+ 'content': '<adversarial query>',
167
+ 'role': 'user'
168
+ },
169
+ {
170
+ 'content': "<response from endpoint>",
171
+ 'role': 'assistant',
172
+ 'context': None
173
+ }],
174
+ '$schema': 'http://azureml/sdk-2-0/ChatConversation.json'
175
+ }]
176
+ }
177
+ """
178
+ if scenario not in AdversarialScenario.__members__.values():
179
+ msg = f"Invalid scenario: {scenario}. Supported scenarios: {AdversarialScenario.__members__.values()}"
180
+ raise EvaluationException(
181
+ message=msg,
182
+ internal_message=msg,
183
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
184
+ category=ErrorCategory.INVALID_VALUE,
185
+ blame=ErrorBlame.USER_ERROR,
186
+ )
187
+
188
+ if not randomization_seed:
189
+ randomization_seed = randint(0, 1000000)
190
+
191
+ regular_sim = AdversarialSimulator(azure_ai_project=self.azure_ai_project, credential=self.credential)
192
+ regular_sim_results = await regular_sim(
193
+ scenario=scenario,
194
+ target=target,
195
+ max_conversation_turns=max_conversation_turns,
196
+ max_simulation_results=max_simulation_results,
197
+ api_call_retry_limit=api_call_retry_limit,
198
+ api_call_retry_sleep_sec=api_call_retry_sleep_sec,
199
+ api_call_delay_sec=api_call_delay_sec,
200
+ concurrent_async_task=concurrent_async_task,
201
+ randomize_order=False,
202
+ randomization_seed=randomization_seed,
203
+ )
204
+ jb_sim = AdversarialSimulator(azure_ai_project=self.azure_ai_project, credential=self.credential)
205
+ jb_sim_results = await jb_sim(
206
+ scenario=scenario,
207
+ target=target,
208
+ max_conversation_turns=max_conversation_turns,
209
+ max_simulation_results=max_simulation_results,
210
+ api_call_retry_limit=api_call_retry_limit,
211
+ api_call_retry_sleep_sec=api_call_retry_sleep_sec,
212
+ api_call_delay_sec=api_call_delay_sec,
213
+ concurrent_async_task=concurrent_async_task,
214
+ _jailbreak_type="upia",
215
+ randomize_order=False,
216
+ randomization_seed=randomization_seed,
217
+ )
218
+ return {"jailbreak": jb_sim_results, "regular": regular_sim_results}
@@ -0,0 +1,4 @@
1
+ from ._language_suffix_mapping import SUPPORTED_LANGUAGES_MAPPING
2
+ from ._simulator_data_classes import ConversationHistory, Turn
3
+
4
+ __all__ = ["ConversationHistory", "Turn", "SUPPORTED_LANGUAGES_MAPPING"]
@@ -0,0 +1,17 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ from azure.ai.evaluation.simulator._constants import SupportedLanguages
5
+
6
+ BASE_SUFFIX = "Make the conversation in __language__ language."
7
+
8
+ SUPPORTED_LANGUAGES_MAPPING = {
9
+ SupportedLanguages.English: BASE_SUFFIX.replace("__language__", "english"),
10
+ SupportedLanguages.Spanish: BASE_SUFFIX.replace("__language__", "spanish"),
11
+ SupportedLanguages.Italian: BASE_SUFFIX.replace("__language__", "italian"),
12
+ SupportedLanguages.French: BASE_SUFFIX.replace("__language__", "french"),
13
+ SupportedLanguages.German: BASE_SUFFIX.replace("__language__", "german"),
14
+ SupportedLanguages.SimplifiedChinese: BASE_SUFFIX.replace("__language__", "simplified chinese"),
15
+ SupportedLanguages.Portuguese: BASE_SUFFIX.replace("__language__", "portuguese"),
16
+ SupportedLanguages.Japanese: BASE_SUFFIX.replace("__language__", "japanese"),
17
+ }
@@ -0,0 +1,96 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ # pylint: disable=C0103,C0114,C0116
5
+ from dataclasses import dataclass
6
+ from typing import Dict, List, Optional, Union
7
+
8
+ from azure.ai.evaluation.simulator._conversation.constants import ConversationRole
9
+
10
+
11
+ @dataclass
12
+ class Turn:
13
+ """
14
+ Represents a conversation turn,
15
+ keeping track of the role, content,
16
+ and context of a turn in a conversation.
17
+ """
18
+
19
+ role: Union[str, ConversationRole]
20
+ content: str
21
+ context: Optional[str] = None
22
+
23
+ def to_dict(self) -> Dict[str, Optional[str]]:
24
+ """
25
+ Convert the conversation turn to a dictionary.
26
+
27
+ :returns: A dictionary representation of the conversation turn.
28
+ :rtype: Dict[str, Optional[str]]
29
+ """
30
+ return {
31
+ "role": self.role.value if isinstance(self.role, ConversationRole) else self.role,
32
+ "content": self.content,
33
+ "context": str(self.context),
34
+ }
35
+
36
+ def to_context_free_dict(self) -> Dict[str, Optional[str]]:
37
+ """
38
+ Convert the conversation turn to a dictionary without context.
39
+
40
+ :returns: A dictionary representation of the conversation turn without context.
41
+ :rtype: Dict[str, Optional[str]]
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
+ return f"Turn(role={self.role}, content={self.content})"
50
+
51
+
52
+ class ConversationHistory:
53
+ """
54
+ Conversation history class to keep track of the conversation turns in a conversation.
55
+ """
56
+
57
+ def __init__(self) -> None:
58
+ """
59
+ Initializes the conversation history with an empty list of turns.
60
+ """
61
+ self.history: List[Turn] = []
62
+
63
+ def add_to_history(self, turn: Turn) -> None:
64
+ """
65
+ Adds a turn to the conversation history.
66
+
67
+ :param turn: The conversation turn to add.
68
+ :type turn: Turn
69
+ """
70
+ self.history.append(turn)
71
+
72
+ def to_list(self) -> List[Dict[str, Optional[str]]]:
73
+ """
74
+ Converts the conversation history to a list of dictionaries.
75
+
76
+ :returns: A list of dictionaries representing the conversation turns.
77
+ :rtype: List[Dict[str, str]]
78
+ """
79
+ return [turn.to_dict() for turn in self.history]
80
+
81
+ def to_context_free_list(self) -> List[Dict[str, Optional[str]]]:
82
+ """
83
+ Converts the conversation history to a list of dictionaries without context.
84
+
85
+ :returns: A list of dictionaries representing the conversation turns without context.
86
+ :rtype: List[Dict[str, str]]
87
+ """
88
+ return [turn.to_context_free_dict() for turn in self.history]
89
+
90
+ def __len__(self) -> int:
91
+ return len(self.history)
92
+
93
+ def __repr__(self):
94
+ for turn in self.history:
95
+ print(turn)
96
+ return ""
@@ -0,0 +1,220 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ # pylint: disable=C0301,C0114,R0913,R0903
5
+ # noqa: E501
6
+ import asyncio
7
+ import logging
8
+ from typing import Callable, cast
9
+
10
+ from tqdm import tqdm
11
+
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
16
+ from azure.ai.evaluation._model_configurations import AzureAIProject
17
+ from azure.core.credentials import TokenCredential
18
+
19
+ from ._adversarial_simulator import AdversarialSimulator, JsonLineList
20
+
21
+ from ._model_tools import AdversarialTemplateHandler, ManagedIdentityAPITokenManager, RAIClient, TokenScope
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @experimental
27
+ class IndirectAttackSimulator(AdversarialSimulator):
28
+ """
29
+ Initializes the XPIA (cross domain prompt injected attack) jailbreak adversarial simulator with a project scope.
30
+
31
+ :param azure_ai_project: The scope of the Azure AI project. It contains subscription id, resource group, and project
32
+ name.
33
+ :type azure_ai_project: ~azure.ai.evaluation.AzureAIProject
34
+ :param credential: The credential for connecting to Azure AI project.
35
+ :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
+ """
46
+
47
+ def __init__(self, *, azure_ai_project: AzureAIProject, credential: TokenCredential):
48
+ """Constructor."""
49
+
50
+ try:
51
+ self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
52
+ except EvaluationException as e:
53
+ raise EvaluationException(
54
+ message=e.message,
55
+ internal_message=e.internal_message,
56
+ target=ErrorTarget.DIRECT_ATTACK_SIMULATOR,
57
+ category=e.category,
58
+ blame=e.blame,
59
+ ) from e
60
+
61
+ self.credential = cast(TokenCredential, credential)
62
+ self.token_manager = ManagedIdentityAPITokenManager(
63
+ token_scope=TokenScope.DEFAULT_AZURE_MANAGEMENT,
64
+ logger=logging.getLogger("AdversarialSimulator"),
65
+ credential=self.credential,
66
+ )
67
+ self.rai_client = RAIClient(azure_ai_project=self.azure_ai_project, token_manager=self.token_manager)
68
+ self.adversarial_template_handler = AdversarialTemplateHandler(
69
+ azure_ai_project=self.azure_ai_project, rai_client=self.rai_client
70
+ )
71
+ super().__init__(azure_ai_project=azure_ai_project, credential=credential)
72
+
73
+ def _ensure_service_dependencies(self):
74
+ if self.rai_client is None:
75
+ msg = "RAI service is required for simulation, but an RAI client was not provided."
76
+ raise EvaluationException(
77
+ message=msg,
78
+ internal_message=msg,
79
+ target=ErrorTarget.ADVERSARIAL_SIMULATOR,
80
+ category=ErrorCategory.MISSING_FIELD,
81
+ blame=ErrorBlame.USER_ERROR,
82
+ )
83
+
84
+ async def __call__(
85
+ self,
86
+ *,
87
+ target: Callable,
88
+ max_simulation_results: int = 3,
89
+ api_call_retry_limit: int = 3,
90
+ api_call_retry_sleep_sec: int = 1,
91
+ api_call_delay_sec: int = 0,
92
+ concurrent_async_task: int = 3,
93
+ **kwargs,
94
+ ):
95
+ """
96
+ Initializes the XPIA (cross domain prompt injected attack) jailbreak adversarial simulator with a project scope.
97
+ This simulator converses with your AI system using prompts injected into the context to interrupt normal
98
+ expected functionality by eliciting manipulated content, intrusion and attempting to gather information outside
99
+ the scope of your AI system.
100
+ :keyword target: The target function to simulate adversarial inputs against.
101
+ This function should be asynchronous and accept a dictionary representing the adversarial input.
102
+ :paramtype target: Callable
103
+ :keyword max_simulation_results: The maximum number of simulation results to return.
104
+ Defaults to 3.
105
+ :paramtype max_simulation_results: int
106
+ :keyword api_call_retry_limit: The maximum number of retries for each API call within the simulation.
107
+ Defaults to 3.
108
+ :paramtype api_call_retry_limit: int
109
+ :keyword api_call_retry_sleep_sec: The sleep duration (in seconds) between retries for API calls.
110
+ Defaults to 1 second.
111
+ :paramtype api_call_retry_sleep_sec: int
112
+ :keyword api_call_delay_sec: The delay (in seconds) before making an API call.
113
+ This can be used to avoid hitting rate limits. Defaults to 0 seconds.
114
+ :paramtype api_call_delay_sec: int
115
+ :keyword concurrent_async_task: The number of asynchronous tasks to run concurrently during the simulation.
116
+ Defaults to 3.
117
+ :paramtype concurrent_async_task: int
118
+ :return: A list of dictionaries, each representing a simulated conversation. Each dictionary contains:
119
+
120
+ - 'template_parameters': A dictionary with parameters used in the conversation template,
121
+ including 'conversation_starter'.
122
+ - 'messages': A list of dictionaries, each representing a turn in the conversation.
123
+ Each message dictionary includes 'content' (the message text) and
124
+ 'role' (indicating whether the message is from the 'user' or the 'assistant').
125
+ - '**$schema**': A string indicating the schema URL for the conversation format.
126
+
127
+ The 'content' for 'assistant' role messages may includes the messages that your callback returned.
128
+ :rtype: List[Dict[str, Any]]
129
+
130
+ **Output format**
131
+
132
+ .. code-block:: python
133
+
134
+ return_value = [
135
+ {
136
+ 'template_parameters': {},
137
+ 'messages': [
138
+ {
139
+ 'content': '<adversarial query>',
140
+ 'role': 'user'
141
+ },
142
+ {
143
+ 'content': "<response from your callback>",
144
+ 'role': 'assistant',
145
+ 'context': None
146
+ }
147
+ ],
148
+ '$schema': 'http://azureml/sdk-2-0/ChatConversation.json'
149
+ }]
150
+ }
151
+ """
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,
170
+ )
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",
177
+ )
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)
@@ -0,0 +1,23 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+
5
+ """Tooling for model evaluation"""
6
+
7
+ from ._identity_manager import ManagedIdentityAPITokenManager, PlainTokenManager, TokenScope
8
+ from ._proxy_completion_model import ProxyChatCompletionsModel
9
+ from ._rai_client import RAIClient
10
+ from ._template_handler import CONTENT_HARM_TEMPLATES_COLLECTION_KEY, AdversarialTemplateHandler
11
+ from .models import LLMBase, OpenAIChatCompletionsModel
12
+
13
+ __all__ = [
14
+ "ManagedIdentityAPITokenManager",
15
+ "PlainTokenManager",
16
+ "TokenScope",
17
+ "RAIClient",
18
+ "AdversarialTemplateHandler",
19
+ "CONTENT_HARM_TEMPLATES_COLLECTION_KEY",
20
+ "ProxyChatCompletionsModel",
21
+ "LLMBase",
22
+ "OpenAIChatCompletionsModel",
23
+ ]