azure-ai-evaluation 1.4.0__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of azure-ai-evaluation might be problematic. Click here for more details.

Files changed (53) hide show
  1. azure/ai/evaluation/__init__.py +0 -16
  2. azure/ai/evaluation/_common/rai_service.py +1 -1
  3. azure/ai/evaluation/_common/utils.py +1 -1
  4. azure/ai/evaluation/_converters/__init__.py +1 -1
  5. azure/ai/evaluation/_converters/_ai_services.py +4 -4
  6. azure/ai/evaluation/_evaluate/_batch_run/code_client.py +18 -12
  7. azure/ai/evaluation/_evaluate/_batch_run/eval_run_context.py +9 -4
  8. azure/ai/evaluation/_evaluate/_batch_run/proxy_client.py +42 -22
  9. azure/ai/evaluation/_evaluate/_batch_run/target_run_context.py +1 -1
  10. azure/ai/evaluation/_evaluate/_eval_run.py +1 -1
  11. azure/ai/evaluation/_evaluate/_evaluate.py +84 -68
  12. azure/ai/evaluation/_evaluate/_telemetry/__init__.py +5 -89
  13. azure/ai/evaluation/_evaluate/_utils.py +3 -3
  14. azure/ai/evaluation/_evaluators/_common/_base_eval.py +1 -1
  15. azure/ai/evaluation/_evaluators/_common/_base_multi_eval.py +1 -1
  16. azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +1 -1
  17. azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +1 -1
  18. azure/ai/evaluation/_evaluators/_response_completeness/_response_completeness.py +1 -0
  19. azure/ai/evaluation/_legacy/_adapters/__init__.py +21 -0
  20. azure/ai/evaluation/_legacy/_adapters/_configuration.py +45 -0
  21. azure/ai/evaluation/_legacy/_adapters/_constants.py +10 -0
  22. azure/ai/evaluation/_legacy/_adapters/_errors.py +29 -0
  23. azure/ai/evaluation/_legacy/_adapters/_flows.py +28 -0
  24. azure/ai/evaluation/_legacy/_adapters/_service.py +16 -0
  25. azure/ai/evaluation/_legacy/_adapters/client.py +51 -0
  26. azure/ai/evaluation/_legacy/_adapters/entities.py +26 -0
  27. azure/ai/evaluation/_legacy/_adapters/tracing.py +28 -0
  28. azure/ai/evaluation/_legacy/_adapters/types.py +15 -0
  29. azure/ai/evaluation/_legacy/_adapters/utils.py +31 -0
  30. azure/ai/evaluation/_legacy/_batch_engine/_result.py +1 -1
  31. azure/ai/evaluation/_legacy/_batch_engine/_status.py +1 -1
  32. azure/ai/evaluation/_version.py +1 -1
  33. azure/ai/evaluation/red_team/__init__.py +19 -0
  34. azure/ai/evaluation/{_red_team → red_team}/_attack_objective_generator.py +3 -0
  35. azure/ai/evaluation/{_red_team → red_team}/_attack_strategy.py +3 -0
  36. azure/ai/evaluation/{_red_team → red_team}/_red_team.py +96 -67
  37. azure/ai/evaluation/red_team/_red_team_result.py +382 -0
  38. azure/ai/evaluation/{_red_team → red_team}/_utils/constants.py +2 -1
  39. azure/ai/evaluation/{_red_team → red_team}/_utils/formatting_utils.py +23 -22
  40. azure/ai/evaluation/{_red_team → red_team}/_utils/logging_utils.py +1 -1
  41. azure/ai/evaluation/{_red_team → red_team}/_utils/strategy_utils.py +8 -4
  42. azure/ai/evaluation/simulator/_simulator.py +1 -1
  43. {azure_ai_evaluation-1.4.0.dist-info → azure_ai_evaluation-1.5.0.dist-info}/METADATA +13 -2
  44. {azure_ai_evaluation-1.4.0.dist-info → azure_ai_evaluation-1.5.0.dist-info}/RECORD +50 -40
  45. azure/ai/evaluation/_red_team/_red_team_result.py +0 -246
  46. azure/ai/evaluation/_red_team/_utils/__init__.py +0 -3
  47. azure/ai/evaluation/simulator/_tracing.py +0 -89
  48. /azure/ai/evaluation/{_red_team → red_team}/_callback_chat_target.py +0 -0
  49. /azure/ai/evaluation/{_red_team → red_team}/_default_converter.py +0 -0
  50. /azure/ai/evaluation/{_red_team → red_team/_utils}/__init__.py +0 -0
  51. {azure_ai_evaluation-1.4.0.dist-info → azure_ai_evaluation-1.5.0.dist-info}/NOTICE.txt +0 -0
  52. {azure_ai_evaluation-1.4.0.dist-info → azure_ai_evaluation-1.5.0.dist-info}/WHEEL +0 -0
  53. {azure_ai_evaluation-1.4.0.dist-info → azure_ai_evaluation-1.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,382 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ from typing import Dict, List, Optional, TypedDict
5
+ import json
6
+ from azure.ai.evaluation._common._experimental import experimental
7
+
8
+
9
+ @experimental
10
+ class RiskCategorySummary(TypedDict):
11
+ """Summary of attack success rates across risk categories.
12
+
13
+ :param overall_asr: Overall attack success rate across all risk categories
14
+ :type overall_asr: float
15
+ :param overall_total: Total number of attacks across all risk categories
16
+ :type overall_total: int
17
+ :param overall_successful_attacks: Number of successful attacks across all risk categories
18
+ :type overall_successful_attacks: int
19
+ :param hate_unfairness_asr: Attack success rate for hate/unfairness risk category
20
+ :type hate_unfairness_asr: float
21
+ :param hate_unfairness_total: Total number of attacks for hate/unfairness risk category
22
+ :type hate_unfairness_total: int
23
+ :param hate_unfairness_successful_attacks: Number of successful attacks for hate/unfairness risk category
24
+ :type hate_unfairness_successful_attacks: int
25
+ :param violence_asr: Attack success rate for violence risk category
26
+ :type violence_asr: float
27
+ :param violence_total: Total number of attacks for violence risk category
28
+ :type violence_total: int
29
+ :param violence_successful_attacks: Number of successful attacks for violence risk category
30
+ :type violence_successful_attacks: int
31
+ :param sexual_asr: Attack success rate for sexual risk category
32
+ :type sexual_asr: float
33
+ :param sexual_total: Total number of attacks for sexual risk category
34
+ :type sexual_total: int
35
+ :param sexual_successful_attacks: Number of successful attacks for sexual risk category
36
+ :type sexual_successful_attacks: int
37
+ :param self_harm_asr: Attack success rate for self-harm risk category
38
+ :type self_harm_asr: float
39
+ :param self_harm_total: Total number of attacks for self-harm risk category
40
+ :type self_harm_total: int
41
+ :param self_harm_successful_attacks: Number of successful attacks for self-harm risk category
42
+ :type self_harm_successful_attacks: int
43
+ """
44
+ overall_asr: float
45
+ overall_total: int
46
+ overall_successful_attacks: int
47
+ hate_unfairness_asr: float
48
+ hate_unfairness_total: int
49
+ hate_unfairness_successful_attacks: int
50
+ violence_asr: float
51
+ violence_total: int
52
+ violence_successful_attacks: int
53
+ sexual_asr: float
54
+ sexual_total: int
55
+ sexual_successful_attacks: int
56
+ self_harm_asr: float
57
+ self_harm_total: int
58
+ self_harm_successful_attacks: int
59
+
60
+
61
+ @experimental
62
+ class AttackTechniqueSummary(TypedDict):
63
+ """Summary of attack success rates across complexity levels.
64
+
65
+ :param overall_asr: Overall attack success rate across all complexity levels
66
+ :type overall_asr: float
67
+ :param overall_total: Total number of attacks across all complexity levels
68
+ :type overall_total: int
69
+ :param overall_successful_attacks: Number of successful attacks across all complexity levels
70
+ :type overall_successful_attacks: int
71
+ :param baseline_asr: Attack success rate for baseline complexity level
72
+ :type baseline_asr: float
73
+ :param baseline_total: Total number of attacks for baseline complexity level
74
+ :type baseline_total: int
75
+ :param baseline_successful_attacks: Number of successful attacks for baseline complexity level
76
+ :type baseline_successful_attacks: int
77
+ :param easy_complexity_asr: Attack success rate for easy complexity level
78
+ :type easy_complexity_asr: float
79
+ :param easy_complexity_total: Total number of attacks for easy complexity level
80
+ :type easy_complexity_total: int
81
+ :param easy_complexity_successful_attacks: Number of successful attacks for easy complexity level
82
+ :type easy_complexity_successful_attacks: int
83
+ :param moderate_complexity_asr: Attack success rate for moderate complexity level
84
+ :type moderate_complexity_asr: float
85
+ :param moderate_complexity_total: Total number of attacks for moderate complexity level
86
+ :type moderate_complexity_total: int
87
+ :param moderate_complexity_successful_attacks: Number of successful attacks for moderate complexity level
88
+ :type moderate_complexity_successful_attacks: int
89
+ :param difficult_complexity_asr: Attack success rate for difficult complexity level
90
+ :type difficult_complexity_asr: float
91
+ :param difficult_complexity_total: Total number of attacks for difficult complexity level
92
+ :type difficult_complexity_total: int
93
+ :param difficult_complexity_successful_attacks: Number of successful attacks for difficult complexity level
94
+ :type difficult_complexity_successful_attacks: int
95
+ """
96
+ overall_asr: float
97
+ overall_total: int
98
+ overall_successful_attacks: int
99
+ baseline_asr: float
100
+ baseline_total: int
101
+ baseline_successful_attacks: int
102
+ easy_complexity_asr: float
103
+ easy_complexity_total: int
104
+ easy_complexity_successful_attacks: int
105
+ moderate_complexity_asr: float
106
+ moderate_complexity_total: int
107
+ moderate_complexity_successful_attacks: int
108
+ difficult_complexity_asr: float
109
+ difficult_complexity_total: int
110
+ difficult_complexity_successful_attacks: int
111
+
112
+
113
+ @experimental
114
+ class JointRiskAttackSummaryItem(TypedDict):
115
+ """Summary of attack success rates for a specific risk category across complexity levels.
116
+
117
+ :param risk_category: The risk category being summarized
118
+ :type risk_category: str
119
+ :param baseline_asr: Attack success rate for baseline complexity level
120
+ :type baseline_asr: float
121
+ :param easy_complexity_asr: Attack success rate for easy complexity level
122
+ :type easy_complexity_asr: float
123
+ :param moderate_complexity_asr: Attack success rate for moderate complexity level
124
+ :type moderate_complexity_asr: float
125
+ :param difficult_complexity_asr: Attack success rate for difficult complexity level
126
+ :type difficult_complexity_asr: float
127
+ """
128
+ risk_category: str
129
+ baseline_asr: float
130
+ easy_complexity_asr: float
131
+ moderate_complexity_asr: float
132
+ difficult_complexity_asr: float
133
+
134
+
135
+ @experimental
136
+ class RedTeamingScorecard(TypedDict):
137
+ """TypedDict representation of a Red Team Agent scorecard with the updated structure.
138
+
139
+ :param risk_category_summary: Overall metrics by risk category
140
+ :type risk_category_summary: List[RiskCategorySummary]
141
+ :param attack_technique_summary: Overall metrics by attack technique complexity
142
+ :type attack_technique_summary: List[AttackTechniqueSummary]
143
+ :param joint_risk_attack_summary: Detailed metrics by risk category and complexity level
144
+ :type joint_risk_attack_summary: List[JointRiskAttackSummaryItem]
145
+ :param detailed_joint_risk_attack_asr: Detailed ASR information broken down by complexity level, risk category, and converter
146
+ :type detailed_joint_risk_attack_asr: Dict[str, Dict[str, Dict[str, float]]]
147
+ """
148
+ risk_category_summary: List[RiskCategorySummary]
149
+ attack_technique_summary: List[AttackTechniqueSummary]
150
+ joint_risk_attack_summary: List[JointRiskAttackSummaryItem]
151
+ detailed_joint_risk_attack_asr: Dict[str, Dict[str, Dict[str, float]]]
152
+
153
+
154
+ @experimental
155
+ class AttackObjectiveSource(TypedDict):
156
+ """Information about how attack objectives were generated.
157
+
158
+ :param application_scenario: The application scenario used for generating attack objectives
159
+ :type application_scenario: str
160
+ :param risk_categories: List of risk categories targeted by the attack objectives
161
+ :type risk_categories: List[str]
162
+ :param custom_attack_seed_prompts: Custom prompts used to seed attack objective generation
163
+ :type custom_attack_seed_prompts: str
164
+ :param policy_document: Policy document used for generating attack objectives
165
+ :type policy_document: str
166
+ """
167
+ application_scenario: str
168
+ risk_categories: List[str]
169
+ custom_attack_seed_prompts: str
170
+ policy_document: str
171
+
172
+
173
+ @experimental
174
+ class RedTeamingParameters(TypedDict):
175
+ """TypedDict representation of parameters used in a red team evaluation with the updated structure.
176
+
177
+ :param attack_objective_generated_from: Information about the source of attack objectives
178
+ :type attack_objective_generated_from: AttackObjectiveSource
179
+ :param attack_complexity: List of complexity levels used in the attacks
180
+ :type attack_complexity: List[str]
181
+ :param techniques_used: Dictionary mapping complexity levels to lists of converter techniques used
182
+ :type techniques_used: Dict[str, List[str]]
183
+ """
184
+ attack_objective_generated_from: AttackObjectiveSource
185
+ attack_complexity: List[str]
186
+ techniques_used: Dict[str, List[str]]
187
+
188
+
189
+ @experimental
190
+ class RiskAssessment(TypedDict):
191
+ """Assessment of content safety risks in a conversation.
192
+
193
+ :param violence: Assessment of violence-related content
194
+ :type violence: Dict[str, str]
195
+ :param sexual: Assessment of sexual content
196
+ :type sexual: Dict[str, str]
197
+ :param self_harm: Assessment of self-harm content
198
+ :type self_harm: Dict[str, str]
199
+ :param hate_unfair: Assessment of hate or unfairness content
200
+ :type hate_unfair: Dict[str, str]
201
+ """
202
+ violence: Dict[str, str]
203
+ sexual: Dict[str, str]
204
+ self_harm: Dict[str, str]
205
+ hate_unfair: Dict[str, str]
206
+
207
+
208
+ @experimental
209
+ class AttackDetails(TypedDict):
210
+ """TypedDict representation of a simulated conversation in a red team evaluation.
211
+
212
+ :param attack_success: Whether the attack was successful
213
+ :type attack_success: Optional[bool]
214
+ :param attack_technique: The attack technique used in the conversation
215
+ :type attack_technique: str
216
+ :param attack_complexity: The complexity level of the attack
217
+ :type attack_complexity: str
218
+ :param risk_category: The risk category targeted by the attack
219
+ :type risk_category: str
220
+ :param conversation: List of messages exchanged in the conversation
221
+ :type conversation: List[Dict[str, str]]
222
+ :param risk_assessment: Dictionary containing content safety assessment for the conversation
223
+ :type risk_assessment: Optional[RiskAssessment]
224
+ """
225
+ attack_success: Optional[bool]
226
+ attack_technique: str
227
+ attack_complexity: str
228
+ risk_category: str
229
+ conversation: List[Dict[str, str]]
230
+ risk_assessment: Optional[RiskAssessment]
231
+
232
+
233
+ @experimental
234
+ class ScanResult(TypedDict):
235
+ """TypedDict representation of a Red Team Agent evaluation result with the updated structure.
236
+
237
+ :param scorecard: Scorecard containing summary and detailed ASR information
238
+ :type scorecard: RedTeamingScorecard
239
+ :param parameters: Parameters containing metadata about the evaluation run
240
+ :type parameters: RedTeamingParameters
241
+ :param attack_details: List of AttackDetails objects representing the conversations in the evaluation
242
+ :type attack_details: List[AttackDetails]
243
+ :param studio_url: Optional URL for the studio
244
+ :type studio_url: Optional[str]
245
+ """
246
+ scorecard: RedTeamingScorecard
247
+ parameters: RedTeamingParameters
248
+ attack_details: List[AttackDetails]
249
+ studio_url: Optional[str]
250
+
251
+
252
+ @experimental
253
+ class RedTeamResult():
254
+ def __init__(
255
+ self,
256
+ scan_result: Optional[ScanResult] = None,
257
+ attack_details: Optional[List[AttackDetails]] = None
258
+ ):
259
+ self.scan_result = scan_result
260
+ self.attack_details = attack_details
261
+
262
+ def to_json(self) -> str:
263
+ """
264
+ Converts a RedTeamResult object to a JSON-serializable dictionary.
265
+
266
+ :returns: A string containing the RedTeamResult in JSON format.
267
+ :rtype: str
268
+ """
269
+ return json.dumps(self.scan_result) if self.scan_result else ""
270
+
271
+ def to_scorecard(self) -> Optional[RedTeamingScorecard]:
272
+ """Extracts the scorecard from a RedTeamResult object."""
273
+ return self.scan_result.get("scorecard", None) if self.scan_result else None
274
+
275
+ def to_eval_qr_json_lines(self) -> str:
276
+ """
277
+ Converts conversations in messages format to query-response format suitable for evaluation.
278
+
279
+ The output format follows the JSONL pattern with each line containing:
280
+ {
281
+ "query": "user message content",
282
+ "response": "assistant message content",
283
+ "risk_category": "risk category",
284
+ "attack_strategy": "strategy name",
285
+ "attack_complexity": "complexity level",
286
+ "attack_success": "true|false", (if available from evaluation)
287
+ "category": "risk category", (if available from evaluation)
288
+ "severity_level": "low|medium|high", (if available from evaluation)
289
+ "threshold": "threshold value" (if available from evaluation)
290
+ }
291
+
292
+ :returns: A list of strings containing query-response pairs in JSONL format.
293
+ :rtype: List[str]
294
+ """
295
+ if not self.attack_details:
296
+ return ""
297
+
298
+ result_lines = []
299
+
300
+ for conversation in self.attack_details:
301
+ messages = conversation.get("conversation", [])
302
+ attack_technique = conversation.get("attack_technique", "")
303
+ attack_complexity = conversation.get("attack_complexity", "")
304
+ risk_category = conversation.get("risk_category", "")
305
+ attack_success = conversation.get("attack_success")
306
+ risk_assessment = conversation.get("risk_assessment", {})
307
+
308
+ for i in range(0, len(messages) - 1, 2):
309
+ if i + 1 < len(messages):
310
+ user_msg = messages[i]
311
+ assistant_msg = messages[i + 1]
312
+
313
+ if user_msg.get("role") == "user" and assistant_msg.get("role") == "assistant":
314
+ # Create the base query-response pair
315
+ qr_pair = {
316
+ "query": user_msg.get("content", ""),
317
+ "response": assistant_msg.get("content", ""),
318
+ "risk_category": risk_category,
319
+ "attack_strategy": attack_technique,
320
+ "attack_complexity": attack_complexity
321
+ }
322
+
323
+ # Add evaluation data if available
324
+ if attack_success is not None:
325
+ qr_pair["attack_success"] = str(attack_success).lower()
326
+
327
+ # Add risk assessment data if available
328
+ for category, assessment in risk_assessment.items() if risk_assessment else {}:
329
+ if assessment and assessment.get("severity_label", None):
330
+ qr_pair["category"] = category
331
+ qr_pair["severity_level"] = assessment.get("severity_label", "").lower()
332
+ # Add threshold if available in the future
333
+
334
+ result_lines.append(json.dumps(qr_pair))
335
+
336
+ return result_lines
337
+
338
+ def attack_simulation(self) -> str:
339
+ """
340
+ Returns the attack simulation data in a human-readable format.
341
+ :returns: A string containing the attack simulation data in a human-readable format.
342
+ :rtype: str
343
+ """
344
+ if not self.attack_details:
345
+ return ""
346
+
347
+ result_lines = []
348
+
349
+ for conversation in self.attack_details:
350
+ messages = conversation.get("conversation", [])
351
+ attack_technique = conversation.get("attack_technique", "")
352
+ attack_complexity = conversation.get("attack_complexity", "")
353
+ risk_category = conversation.get("risk_category", "")
354
+ attack_success = conversation.get("attack_success")
355
+ risk_assessment = conversation.get("risk_assessment", {})
356
+
357
+ result_lines.append(f"Attack Technique: {attack_technique}")
358
+ result_lines.append(f"Attack Complexity: {attack_complexity}")
359
+ result_lines.append(f"Risk Category: {risk_category}")
360
+ result_lines.append("")
361
+
362
+ for i in range(0, len(messages) - 1, 2):
363
+ if i + 1 < len(messages):
364
+ user_msg = messages[i]
365
+ assistant_msg = messages[i + 1]
366
+
367
+ if user_msg.get("role") == "user" and assistant_msg.get("role") == "assistant":
368
+ result_lines.append(f"User: {user_msg.get('content', '')}")
369
+ result_lines.append(f"Assistant: {assistant_msg.get('content', '')}")
370
+ result_lines.append("")
371
+
372
+ if attack_success is not None:
373
+ result_lines.append(f"Attack Success: {'Successful' if attack_success else 'Failed'}")
374
+ result_lines.append("")
375
+
376
+ for category, assessment in risk_assessment.items() if risk_assessment else {}:
377
+ if assessment and assessment.get("severity_label", None):
378
+ result_lines.append(f"Category: {category}")
379
+ result_lines.append(f"Severity Level: {assessment.get('severity_label', '')}")
380
+ result_lines.append("")
381
+
382
+ return "\n".join(result_lines)
@@ -60,5 +60,6 @@ TASK_STATUS = {
60
60
  "RUNNING": "running",
61
61
  "COMPLETED": "completed",
62
62
  "FAILED": "failed",
63
- "TIMEOUT": "timeout"
63
+ "TIMEOUT": "timeout",
64
+ "INCOMPLETE": "incomplete",
64
65
  }
@@ -8,7 +8,7 @@ import math
8
8
  from datetime import datetime
9
9
  from typing import Dict, List, Union, Any, Optional, cast
10
10
  from .._attack_strategy import AttackStrategy
11
- from .._red_team_result import _RedTeamResult
11
+ from .._red_team_result import RedTeamResult
12
12
  from pyrit.models import ChatMessage
13
13
 
14
14
 
@@ -63,24 +63,14 @@ def get_flattened_attack_strategies(attack_strategies: List[Union[AttackStrategy
63
63
  attack_strategies_temp.remove(AttackStrategy.EASY)
64
64
 
65
65
  if AttackStrategy.MODERATE in attack_strategies_temp:
66
- # TODO: change these before merging.
67
66
  attack_strategies_temp.extend([
68
- AttackStrategy.AsciiArt,
69
- AttackStrategy.Compose([
70
- AttackStrategy.Tense,
71
- AttackStrategy.Flip
72
- ]),
73
- ])
67
+ AttackStrategy.Tense],
68
+ )
74
69
  attack_strategies_temp.remove(AttackStrategy.MODERATE)
75
70
  if AttackStrategy.DIFFICULT in attack_strategies_temp:
76
- # TODO: change these before merging.
77
71
  attack_strategies_temp.extend([
78
72
  AttackStrategy.Compose([
79
73
  AttackStrategy.Tense,
80
- AttackStrategy.Morse
81
- ]),
82
- AttackStrategy.Compose([
83
- AttackStrategy.Morse,
84
74
  AttackStrategy.Base64
85
75
  ]),
86
76
  ])
@@ -108,21 +98,26 @@ def get_attack_success(result: str) -> bool:
108
98
  return result == EVALUATION_PASS_FAIL_MAPPING.get(False, "true")
109
99
 
110
100
 
111
- def format_scorecard(redteam_result: _RedTeamResult) -> str:
112
- """Format the _RedTeamResult into a human-readable scorecard.
101
+ def format_scorecard(redteam_result: RedTeamResult) -> str:
102
+ """Format the RedTeamResult into a human-readable scorecard.
113
103
 
114
104
  The scorecard presents a summary of attack success rates across different risk categories
115
105
  and attack complexity levels in a tabular format.
116
106
 
117
- :param redteam_result: The _RedTeamResult object to format
107
+ :param redteam_result: The RedTeamResult object to format
118
108
  :type redteam_result: Dict[str, Any]
119
109
  :return: A formatted scorecard as a string
120
110
  :rtype: str
121
111
  """
122
- scorecard = redteam_result["redteaming_scorecard"]
123
- overall_asr = scorecard["risk_category_summary"][0]["overall_asr"] if scorecard["risk_category_summary"] else 0
112
+ scorecard = redteam_result["scorecard"]
113
+ risk_summary = scorecard["risk_category_summary"][0] if scorecard["risk_category_summary"] else {}
114
+ overall_asr = risk_summary.get("overall_asr", 0)
124
115
 
125
116
  output = [f"Overall ASR: {overall_asr}%"]
117
+ overall_successes = risk_summary.get("overall_attack_successes", 0)
118
+ overall_total = risk_summary.get("overall_total", 0)
119
+ output.append(f"Attack Success: {overall_successes}/{overall_total} attacks were successful")
120
+
126
121
 
127
122
  separator = "-" * 108
128
123
  output.append(separator)
@@ -131,10 +126,16 @@ def format_scorecard(redteam_result: _RedTeamResult) -> str:
131
126
 
132
127
  for item in scorecard["joint_risk_attack_summary"]:
133
128
  risk_category = item["risk_category"].replace("_", "-").capitalize()
134
- baseline = f"{item['baseline_asr']}%" if 'baseline_asr' in item else "N/A"
135
- easy = f"{item['easy_complexity_asr']}%" if 'easy_complexity_asr' in item else "N/A"
136
- moderate = f"{item['moderate_complexity_asr']}%" if 'moderate_complexity_asr' in item else "N/A"
137
- difficult = f"{item['difficult_complexity_asr']}%" if 'difficult_complexity_asr' in item else "N/A"
129
+
130
+ baseline_val = item.get('baseline_asr')
131
+ easy_val = item.get('easy_complexity_asr')
132
+ moderate_val = item.get('moderate_complexity_asr')
133
+ difficult_val = item.get('difficult_complexity_asr')
134
+
135
+ baseline = "N/A" if is_none_or_nan(baseline_val) else f"{baseline_val}%"
136
+ easy = "N/A" if is_none_or_nan(easy_val) else f"{easy_val}%"
137
+ moderate = "N/A" if is_none_or_nan(moderate_val) else f"{moderate_val}%"
138
+ difficult = "N/A" if is_none_or_nan(difficult_val) else f"{difficult_val}%"
138
139
 
139
140
  output.append(f"{risk_category:<15}| {baseline:<14} | {easy:<28} | {moderate:<31} | {difficult:<30}")
140
141
 
@@ -136,4 +136,4 @@ def log_error(logger, message, exception=None, context=None):
136
136
  error_msg = f"[{context}] {error_msg}"
137
137
  if exception:
138
138
  error_msg = f"{error_msg}: {str(exception)}"
139
- logger.error(error_msg)
139
+ logger.error(error_msg, exc_info=True)
@@ -41,7 +41,7 @@ def strategy_converter_map() -> Dict[Any, Union[PromptConverter, List[PromptConv
41
41
  return {
42
42
  AttackStrategy.EASY: [Base64Converter(), FlipConverter(), MorseConverter()],
43
43
  AttackStrategy.MODERATE: [_DefaultConverter()],
44
- AttackStrategy.DIFFICULT: [_DefaultConverter()],
44
+ AttackStrategy.DIFFICULT: [[_DefaultConverter(), Base64Converter()]],
45
45
  AttackStrategy.AnsiAttack: AnsiAttackConverter(),
46
46
  AttackStrategy.AsciiArt: AsciiArtConverter(),
47
47
  AttackStrategy.AsciiSmuggler: AsciiSmugglerConverter(),
@@ -107,23 +107,27 @@ def get_chat_target(target: Union[PromptChatTarget, Callable, AzureOpenAIModelCo
107
107
  if not isinstance(target, Callable):
108
108
  if "azure_deployment" in target and "azure_endpoint" in target: # Azure OpenAI
109
109
  api_key = target.get("api_key", None)
110
+ api_version = target.get("api_version", "2024-06-01")
110
111
  if not api_key:
111
112
  chat_target = OpenAIChatTarget(
112
113
  model_name=target["azure_deployment"],
113
114
  endpoint=target["azure_endpoint"],
114
- use_aad_auth=True
115
+ use_aad_auth=True,
116
+ api_version=api_version,
115
117
  )
116
118
  else:
117
119
  chat_target = OpenAIChatTarget(
118
120
  model_name=target["azure_deployment"],
119
121
  endpoint=target["azure_endpoint"],
120
- api_key=api_key
122
+ api_key=api_key,
123
+ api_version=api_version,
121
124
  )
122
125
  else: # OpenAI
123
126
  chat_target = OpenAIChatTarget(
124
127
  model_name=target["model"],
125
128
  endpoint=target.get("base_url", None),
126
- api_key=target["api_key"]
129
+ api_key=target["api_key"],
130
+ api_version=target.get("api_version", "2024-06-01"),
127
131
  )
128
132
  else:
129
133
  # Target is callable
@@ -11,7 +11,7 @@ import re
11
11
  import warnings
12
12
  from typing import Any, Callable, Dict, List, Optional, Union, Tuple
13
13
 
14
- from promptflow.core import AsyncPrompty
14
+ from azure.ai.evaluation._legacy._adapters._flows import AsyncPrompty
15
15
  from tqdm import tqdm
16
16
 
17
17
  from azure.ai.evaluation._common._experimental import experimental
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-ai-evaluation
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Microsoft Azure Evaluation Library for Python
5
5
  Home-page: https://github.com/Azure/azure-sdk-for-python
6
6
  Author: Microsoft Corporation
@@ -28,8 +28,13 @@ Requires-Dist: azure-identity>=1.16.0
28
28
  Requires-Dist: azure-core>=1.30.2
29
29
  Requires-Dist: nltk>=3.9.1
30
30
  Requires-Dist: azure-storage-blob>=12.10.0
31
+ Requires-Dist: httpx>=0.25.1
32
+ Requires-Dist: pandas<3.0.0,>=2.1.2
33
+ Requires-Dist: openai>=1.40.0
34
+ Requires-Dist: ruamel.yaml<1.0.0,>=0.17.10
35
+ Requires-Dist: msrest>=0.6.21
31
36
  Provides-Extra: redteam
32
- Requires-Dist: pyrit>=0.8.0; extra == "redteam"
37
+ Requires-Dist: pyrit==0.8.1; extra == "redteam"
33
38
 
34
39
  # Azure AI Evaluation client library for Python
35
40
 
@@ -376,6 +381,12 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
376
381
 
377
382
  # Release History
378
383
 
384
+ ## 1.5.0 (2025-04-04)
385
+
386
+ ### Features Added
387
+
388
+ - New `RedTeam` agent functionality to assess the safety and resilience of AI systems against adversarial prompt attacks
389
+
379
390
  ## 1.4.0 (2025-03-27)
380
391
 
381
392
  ### Features Added