Skip to content

Commit feb69a4

Browse files
slister1001Copilot
andauthored
Red Team Agent Scenario Integration (#44551)
* pyrit foundry integration spec * init implementation still need to figure out xpia / context and fix tests * fix tests and imports * updates * updates * Fix red team tests for new PyRIT API compatibility - Update imports from initialize_pyrit to CentralMemory - Change PromptRequestResponse to Message, PromptRequestPiece to MessagePiece - Update request_pieces to message_pieces throughout - Change orchestrator_identifier to attack_identifier - Fix PyritException instantiation to use keyword argument - Add skip decorators for tests relying on removed orchestrator module - Add skip decorators for tests when scorer class is abstract * Add e2e tests for RedTeam Foundry integration - test_foundry_basic_execution: Basic Foundry execution path - test_foundry_indirect_jailbreak: XPIA attacks with context - test_foundry_multiple_risk_categories: Multiple risk categories - test_foundry_with_application_scenario: Application scenario context - test_foundry_strategy_combination: Multiple attack strategies * Fix PyRIT API compatibility for Foundry integration - Update RAIServiceScorer to inherit from TrueFalseScorer and match new score_async signature - Update _CallbackChatTarget.send_prompt_async to use Message parameter and return List[Message] - Fix AttackScoringConfig to use objective_scorer and use_score_as_feedback parameters - Change scenario.run_attack_async() to scenario.run_async() - Fix _scenario_result.attack_results access pattern - Add None handling for context_type in dataset builder - Remove context prompts for standard attacks (converters only support text) - Update memory API from get_chat_messages_with_conversation_id to get_conversation - Handle both dict and object message formats in strategy_utils - Pass adversarial_chat_target to FoundryExecutionManager - Update unit tests for new API signatures * Handle baseline-only execution limitation in PyRIT Foundry - Add include_baseline parameter to ScenarioOrchestrator.execute() - Log warning when baseline-only is requested (PyRIT requires Foundry strategy) - Update tests to use Baseline + Base64 (workaround for PyRIT limitation) - Fix _red_team.py to pass flattened_attack_strategies for proper Baseline detection - Update get_attack_results() and get_memory() to not raise when no scenario executed * Use PyRIT branch with baseline-only support and simplify orchestrator - Point pyrit dependency to slister1001/PyRIT@feature/baseline-only-execution (TODO: Revert to @main once PR #1321 is merged) - Simplify scenario orchestrator by removing baseline-only workaround - Update e2e tests to use baseline-only execution (AttackStrategy.Baseline) * Remove spec file from PR (documentation only) * Revert to Azure/PyRIT@main until PR #1321 is merged The fork URL was causing CI failures. Reverting to main branch with baseline+Base64 workaround until baseline-only support is merged. Added TODO comments to track where changes are needed once PR #1321 lands. * Split dev requirements to avoid pillow version conflict Create separate dev_requirements_redteam.txt for redteam tests to avoid dependency conflict between promptflow-devkit (pillow<=11.3.0) and pyrit (pillow>=12.1.0). - dev_requirements.txt: removes [redteam] extra, used for regular CI - dev_requirements_redteam.txt: includes [redteam] but excludes promptflow-devkit * Add script for running red team tests locally Add run_redteam_tests.py script that installs from dev_requirements_redteam.txt and runs the red team e2e tests. This allows developers to run redteam tests locally without the pillow version conflicts from promptflow-devkit. Usage: python scripts/run_redteam_tests.py [pytest_args...] * Add dedicated CI job for red team tests Add a separate matrix entry (redteam_Ubuntu2404_310) that runs red team tests with dev_requirements_redteam.txt to avoid pillow version conflicts. The redteam job: - Uses Python 3.10 (required by pyrit) - Skips all standard tox environments - Installs from dev_requirements_redteam.txt (without promptflow-devkit) - Runs red team e2e tests with the [redteam] extra * Add unit tests for DatasetConfigurationBuilder binary_path support Add tests for context file creation, extension mapping, data type determination, and cleanup functionality. * Fix CI issues: add cspell words and handle sphinx autodoc gracefully * Add @pyrit_target_retry support to CallbackChatTarget Enable PyRIT's retry decorator for callback-based chat targets to handle rate limit errors and empty responses with exponential backoff. Changes: - Add retry_enabled parameter (default True) to _CallbackChatTarget - Apply @pyrit_target_retry decorator when retry is enabled - Translate OpenAI RateLimitError to PyRIT RateLimitException - Detect rate limits in error messages as fallback - Raise EmptyResponseException for empty/whitespace responses - Add comprehensive unit tests for retry behavior - Add 'redef' to cspell ignore list for type: ignore comments * Fix tests to use updated PyRIT API parameter names * Apply black formatting to red team module * fix: Accept both message and prompt_request keywords in _CallbackChatTarget Update send_prompt_async to accept both 'message' (PyRIT standard) and 'prompt_request' (SDK convention) keywords for compatibility. This prevents TypeError when orchestrators call with different keyword conventions. Changes: - Update send_prompt_async signature to accept both keywords as Optional - Add validation to ensure exactly one keyword is provided - Add tests for prompt_request keyword and error cases * fix: Store context_items in metadata for standard attacks DatasetConfigurationBuilder.add_objective_with_context was silently dropping context_items for non-indirect (standard) attacks. The comment claimed context was stored in metadata but this wasn't happening. Changes: - Store context_items in objective metadata for standard attacks - Update comment to accurately describe the behavior - Add tests verifying context storage in metadata This enables scoring/result reconstruction to access context data for standard Foundry runs. * fix: Enable baseline-only execution in ScenarioOrchestrator PyRIT 0.10.x now supports empty strategy list with include_baseline=True. Remove the workaround that silently skipped baseline-only execution. Changes: - Remove workaround that skipped execution for empty strategies - Now allow empty strategies when include_baseline=True (baseline-only) - Fail fast with ValueError when both strategies empty AND include_baseline=False - Add debug logging for no-results case This enables proper baseline-only runs instead of silently completing with no data. * fix: Standardize FoundryStrategy imports and remove unused variable - Use shorter import path pyrit.scenario.foundry instead of pyrit.scenario.scenarios.foundry across all files - Remove unused has_indirect variable in _red_team.py * chore: Update pyrit dependency to released v0.11.0 Switch from git main branch to the stable PyPI release for production readiness. * fix: Remove duplicate package install in CI dev_requirements_redteam.txt already installs azure-ai-evaluation[redteam], so the separate pip install is redundant. * fix: Replace class-level mutable state with per-instance TemporaryDirectory * Fix ASR artificially lowered by scoring errors - RAIServiceScorer._score_piece_async() now re-raises exceptions instead of returning score_value='false', so PyRIT treats scoring errors as UNDETERMINED rather than FAILURE - Added _build_identifier() to RAIServiceScorer (required by PyRIT ABC) - Updated calculate_asr() and calculate_asr_by_strategy() to exclude UNDETERMINED from denominator (matches PyRIT's _compute_stats approach) - Updated get_summary_stats() ASR to use decided (success+failure) as denominator - Added 6 regression tests in TestASRScoringErrorRegression - Applied black formatting * Fix review issues: null guards, response validation, cleanup safety, test assertions - Fix NoneType crash when eval_result.results is None in _rai_service_eval_chat_target.py - Guard orchestrator instantiation with _ORCHESTRATOR_AVAILABLE checks - Validate callback response structure before key access in _callback_chat_target.py - Add __del__ and debug logging to DatasetConfigurationBuilder cleanup - Add try/except with helpful message for FoundryStrategy import - Add changelog note for pyrit/promptflow-devkit pillow version conflict - Fix tautological >= 0 assertions in test_foundry.py - Add assertion to cleanup test in test_dataset_builder_binary_path.py - Strengthen e2e test assertions in test_red_team_foundry.py * Fix scoring: switch RAIServiceScorer to sync_evals endpoint with passed field * Add regression tests for council review findings (H4, M1-M6) * Address review comments: add logging, deduplicate code, extract redteam CI matrix - Add log line for orchestrator-based execution path (legacy PyRIT) - Add log suggesting upgrade to PyRIT 0.11+ for Foundry execution - Extract shared _read_seed_content() to deduplicate file-reading logic between _rai_scorer.py and _foundry_result_processor.py - Extract redteam matrix entry into separate platform-matrix-redteam.json with its own MatrixConfig in ci.yml so it always gets a PR build job * Revert redteam CI to await eng sys InjectedPackages support Remove separate redteam MatrixConfig, AfterTestSteps, and platform-matrix-redteam.json. These don't work in the shared PR pipeline and the eng sys team is building InjectedPackages support for conflicting dependency scenarios. Added TODO comments in platform-matrix.json and dev_requirements.txt for what to change when the feature is delivered. * Fix build: remove _comment_todo from platform-matrix.json and apply black formatting * Apply black formatting and align setup.py httpx bound with upstream main * Fix red team e2e test failures and apply SDK black formatting (24.4.0) Bug fixes: - Map PromptSendingAttack to indirect_jailbreak in Foundry result/execution processors - Remap hate_fairness to hate_unfairness for Sync API in RAI scorer - Accept binary_path and image_path data types in callback chat target validation - Fix context KeyError in evaluation processor for messages without context field - Fix test callback to handle messages as list (not dict) Formatting: - Applied black 24.4.0 via tox to all red_team source and test files * Update red team test recording assets to tag _5922d0e1e4 * Remove eval_sim_test_image_understanding.jsonl and spec_pyrit_foundry.md * Enable red team tests in CI via InjectedPackages matrix entry * Fix pillow conflict: remove [redteam] from dev_requirements.txt The redteam extra conflicts with promptflow-devkit due to pillow version incompatibility (pyrit requires >=12.1, promptflow <11). The redteam extra is installed via InjectedPackages in platform-matrix.json for the dedicated CI job instead. * fix: keep whl check enabled for redteam CI job * Fix redteam CI: inject pyrit directly to avoid conflicting URLs * Fix PROXY_URL usage: call as function after upstream change PROXY_URL in devtools_testutils.config was changed from a module-level constant to a function in commit 9233cd8. This fix calls it properly as PROXY_URL() to get the string value instead of passing the function object. * Fix pillow version conflict between pyrit and promptflow-devkit in redteam CI * fix build issues * Fix test failures: skip promptflow-dependent tests when promptflow is excluded, update assertion for changed error message * Remove unused dev_requirements_redteam.txt and run_redteam_tests.py * Guard Configuration.set_config against opentelemetry import errors in proxy_client.py * fix tests and promptflow-less env * Fix CI: promptflow Configuration compat + test recording sanitizer - Validate imported promptflow Configuration accepts override_config kwarg before using it; fall back to local impl on TypeError (fixes sk job where semantic-kernel brings incompatible promptflow version) - Add body key sanitizer for query field in sync_evals requests to handle dynamic adversarial prompt content in test recordings (fixes 5 red team foundry e2e test recording mismatches) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix SK build (promptflow 1.18.1 compat) and redteam recording mismatch - _check.py: Also verify promptflow.client.PFClient is importable so MISSING_LEGACY_SDK is True when promptflow-devkit 1.18.1 drops the promptflow namespace package. Tests that depend on PFClient now correctly skip. - conftest.py: Use (?s).+ regex in the query body sanitizer so multi-line adversarial prompt values are fully replaced. The default .+ regex doesn't match newlines, causing recording/playback body mismatches for hate_unfairness queries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix strategy-name mismatch in Foundry red team path _group_results_by_strategy() used PyRIT's attack_identifier.__type__ (always 'PromptSendingAttack' for single-turn) and mapped it to 'indirect_jailbreak', causing all strategies to get wrong attribution in evaluation and scorecard. Fix: Use the requested AttackStrategy list with get_strategy_name() to produce keys that match ATTACK_STRATEGY_COMPLEXITY_MAP (e.g. 'base64', 'rot13'). Also remove incorrect pyrit_technique_map from _foundry_result_processor.py that labeled all JSONL rows as 'indirect_jailbreak'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI: multi-turn assertion, IndirectJailbreak strategy key - Relax multi-turn conversation assertion from >2 to >=2 (Foundry path may terminate early if target immediately refuses) - Include special strategies (e.g., IndirectJailbreak) in result grouping so attack_technique is correctly set instead of falling back to 'Foundry' - Search both foundry and special strategies when matching eval strategy - Add unit test for IndirectJailbreak strategy grouping Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Defensive fix for attack_identifier type mismatch (Bug 1) - Add _get_attack_type_name() helper that handles both dict (current pyrit 0.11.0) and future Identifier-object forms of attack_identifier - Update _foundry_result_processor.py and _scenario_orchestrator.py to use the helper instead of calling .get() directly on attack_identifier - Add 5 unit tests for dict, object, None, empty dict, and missing key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fdf43b0 commit feb69a4

57 files changed

Lines changed: 8241 additions & 282 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eng/scripts/dispatch_checks.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ def _inject_custom_reqs(req_file: str, injected_packages: str, package_dir: str)
8383
if not injected_list:
8484
return
8585

86+
# Entries prefixed with '!' are exclusion-only: they remove matching packages
87+
# from dev_requirements but are not themselves installed.
88+
excluded = [p[1:] for p in injected_list if p.startswith("!")]
89+
installable = [p for p in injected_list if not p.startswith("!")]
90+
# Build a combined list for filtering (both injected installs and exclusions)
91+
all_filter_names = installable + excluded
92+
8693
logger.info(f"Adding custom packages to requirements for {package_dir}")
8794
with open(req_file, "r") as handle:
8895
for line in handle:
@@ -95,13 +102,13 @@ def _inject_custom_reqs(req_file: str, injected_packages: str, package_dir: str)
95102
req_lines.append((line, parsed_req))
96103

97104
if req_lines:
98-
all_adjustments = injected_list + [
105+
all_adjustments = installable + [
99106
line_tuple[0].strip()
100107
for line_tuple in req_lines
101-
if line_tuple[0].strip() and not _compare_req_to_injected_reqs(line_tuple[1], injected_list)
108+
if line_tuple[0].strip() and not _compare_req_to_injected_reqs(line_tuple[1], all_filter_names)
102109
]
103110
else:
104-
all_adjustments = injected_list
111+
all_adjustments = installable
105112

106113
logger.info(f"Generated Custom Reqs: {req_lines}")
107114

sdk/evaluation/azure-ai-evaluation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Prevent recursive stdout/stderr forwarding when NodeLogManager is nested, avoiding RecursionError in concurrent evaluation runs.
88

9+
### Other Changes
10+
11+
- The `[redteam]` extra now requires `pyrit==0.11.0`, which depends on `pillow>=12.1.0`. This conflicts with `promptflow-devkit` (`pillow<=11.3.0`). Use separate virtual environments if you need both packages.
12+
913
## 1.14.0 (2026-01-05)
1014

1115
### Bugs Fixed

sdk/evaluation/azure-ai-evaluation/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/evaluation/azure-ai-evaluation",
5-
"Tag": "python/evaluation/azure-ai-evaluation_409699f40b"
5+
"Tag": "python/evaluation/azure-ai-evaluation_2ae9b6b8ea"
66
}

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_evaluate/_batch_run/proxy_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
from azure.ai.evaluation._evaluate._batch_run.batch_clients import BatchClientRun, HasAsyncCallable
2323

2424

25-
Configuration.get_instance().set_config("trace.destination", "none")
25+
try:
26+
Configuration.get_instance().set_config("trace.destination", "none")
27+
except Exception:
28+
pass
2629
LOGGER = logging.getLogger(__name__)
2730

2831

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_legacy/_adapters/_check.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
_has_legacy = False
99
try:
1010
from promptflow._constants import FlowType
11+
from promptflow.client import PFClient
1112

1213
_has_legacy = True
1314
except ImportError:

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_legacy/_adapters/_configuration.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
try:
1111
from promptflow._sdk._configuration import Configuration as _Configuration
12-
except ImportError:
12+
13+
# Validate that the imported Configuration accepts our expected kwargs.
14+
# Some versions of promptflow expose Configuration but with an incompatible signature.
15+
_Configuration(override_config=None)
16+
except (ImportError, TypeError):
1317
_global_config: Final[Dict[str, Any]] = {}
1418

1519
class _Configuration:

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/red_team/__init__.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,53 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# ---------------------------------------------------------
44

5+
_PYRIT_INSTALLED = False
6+
57
try:
68
from ._red_team import RedTeam
79
from ._attack_strategy import AttackStrategy
810
from ._attack_objective_generator import RiskCategory, SupportedLanguages
911
from ._red_team_result import RedTeamResult
12+
13+
_PYRIT_INSTALLED = True
1014
except ImportError:
11-
raise ImportError(
12-
"Could not import Pyrit. Please install the dependency with `pip install azure-ai-evaluation[redteam]`."
13-
)
15+
# When pyrit is not installed, provide placeholder classes for documentation
16+
# This allows sphinx autodoc to document the module without the optional dependency
17+
import sys
18+
19+
# Check if we're being imported by sphinx for documentation
20+
_is_sphinx = "sphinx" in sys.modules
21+
22+
if not _is_sphinx:
23+
raise ImportError(
24+
"Could not import Pyrit. Please install the dependency with `pip install azure-ai-evaluation[redteam]`."
25+
)
26+
27+
# Provide placeholder docstrings for sphinx
28+
class RedTeam: # type: ignore[no-redef]
29+
"""Red team testing orchestrator. Requires pyrit: `pip install azure-ai-evaluation[redteam]`."""
30+
31+
pass
32+
33+
class AttackStrategy: # type: ignore[no-redef]
34+
"""Attack strategy enumeration. Requires pyrit: `pip install azure-ai-evaluation[redteam]`."""
35+
36+
pass
37+
38+
class RiskCategory: # type: ignore[no-redef]
39+
"""Risk category enumeration. Requires pyrit: `pip install azure-ai-evaluation[redteam]`."""
40+
41+
pass
42+
43+
class SupportedLanguages: # type: ignore[no-redef]
44+
"""Supported languages enumeration. Requires pyrit: `pip install azure-ai-evaluation[redteam]`."""
45+
46+
pass
47+
48+
class RedTeamResult: # type: ignore[no-redef]
49+
"""Red team result container. Requires pyrit: `pip install azure-ai-evaluation[redteam]`."""
50+
51+
pass
1452

1553

1654
__all__ = [

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/red_team/_agent/_agent_functions.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def _get_tool_provider() -> RedTeamToolProvider:
3939

4040

4141
def red_team_fetch_harmful_prompt(
42-
risk_category: str, strategy: str = "baseline", convert_with_strategy: Optional[str] = None
42+
risk_category: str,
43+
strategy: str = "baseline",
44+
convert_with_strategy: Optional[str] = None,
4345
) -> str:
4446
"""
4547
Fetch a harmful prompt for a specific risk category to test content filters.
@@ -58,7 +60,9 @@ def red_team_fetch_harmful_prompt(
5860
# Run the async method in a new event loop
5961
result = asyncio.run(
6062
provider.fetch_harmful_prompt(
61-
risk_category_text=risk_category, strategy=strategy, convert_with_strategy=convert_with_strategy
63+
risk_category_text=risk_category,
64+
strategy=strategy,
65+
convert_with_strategy=convert_with_strategy,
6266
)
6367
)
6468

@@ -194,7 +198,13 @@ def red_team_send_to_target(prompt: str) -> str:
194198

195199
return json.dumps({"status": "success", "prompt": prompt, "response": response})
196200
except Exception as e:
197-
return json.dumps({"status": "error", "message": f"Error calling target function: {str(e)}", "prompt": prompt})
201+
return json.dumps(
202+
{
203+
"status": "error",
204+
"message": f"Error calling target function: {str(e)}",
205+
"prompt": prompt,
206+
}
207+
)
198208

199209

200210
# Example User Input for Each Function

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/red_team/_agent/_agent_tools.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from azure.ai.evaluation._common._experimental import experimental
1818
from azure.ai.evaluation.red_team._attack_objective_generator import RiskCategory
1919
from azure.ai.evaluation.simulator._model_tools import ManagedIdentityAPITokenManager
20-
from azure.ai.evaluation.simulator._model_tools._generated_rai_client import GeneratedRAIClient
20+
from azure.ai.evaluation.simulator._model_tools._generated_rai_client import (
21+
GeneratedRAIClient,
22+
)
2123
from ._agent_utils import AgentUtils
2224

2325
# Setup logging
@@ -59,7 +61,8 @@ def __init__(
5961

6062
# Create the generated RAI client for fetching attack objectives
6163
self.generated_rai_client = GeneratedRAIClient(
62-
azure_ai_project=self.azure_ai_project_endpoint, token_manager=self.token_manager.get_aad_credential()
64+
azure_ai_project=self.azure_ai_project_endpoint,
65+
token_manager=self.token_manager.get_aad_credential(),
6366
)
6467

6568
# Cache for attack objectives to avoid repeated API calls
@@ -165,11 +168,15 @@ async def _get_attack_objectives(self, risk_category: RiskCategory, strategy: st
165168
# Get strategy-specific dataset for tense strategy
166169
if "tense" in strategy:
167170
objectives_response = await self.generated_rai_client.get_attack_objectives(
168-
risk_category=risk_cat_value, application_scenario=self.application_scenario or "", strategy="tense"
171+
risk_category=risk_cat_value,
172+
application_scenario=self.application_scenario or "",
173+
strategy="tense",
169174
)
170175
else:
171176
objectives_response = await self.generated_rai_client.get_attack_objectives(
172-
risk_category=risk_cat_value, application_scenario=self.application_scenario or "", strategy=None
177+
risk_category=risk_cat_value,
178+
application_scenario=self.application_scenario or "",
179+
strategy=None,
173180
)
174181

175182
# Handle jailbreak strategy - apply jailbreak prefixes to messages
@@ -199,7 +206,10 @@ async def _get_attack_objectives(self, risk_category: RiskCategory, strategy: st
199206
return []
200207

201208
async def fetch_harmful_prompt(
202-
self, risk_category_text: str, strategy: str = "baseline", convert_with_strategy: Optional[str] = None
209+
self,
210+
risk_category_text: str,
211+
strategy: str = "baseline",
212+
convert_with_strategy: Optional[str] = None,
203213
) -> Dict[str, Any]:
204214
"""Fetch a harmful prompt for a specific risk category.
205215
@@ -276,7 +286,10 @@ async def fetch_harmful_prompt(
276286
"note": "This prompt was generated and converted for responsible AI testing purposes only.",
277287
}
278288
except Exception as e:
279-
return {"status": "error", "message": f"Error converting prompt: {str(e)}"}
289+
return {
290+
"status": "error",
291+
"message": f"Error converting prompt: {str(e)}",
292+
}
280293

281294
# Return with information about available strategies
282295
return {
@@ -394,7 +407,10 @@ async def red_team(self, category: str, strategy: Optional[str] = None) -> Dict[
394407
"note": f"This prompt was generated for responsible AI testing purposes only and converted using the {strategy} strategy.",
395408
}
396409
except Exception as e:
397-
return {"status": "error", "message": f"Error converting prompt with strategy {strategy}: {str(e)}"}
410+
return {
411+
"status": "error",
412+
"message": f"Error converting prompt with strategy {strategy}: {str(e)}",
413+
}
398414

399415
except Exception as e:
400416
logger.error(f"Error in red_team: {str(e)}")

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/red_team/_agent/_semantic_kernel_plugin.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ def __init__(
7474
@kernel_function(description="Fetch a harmful prompt for a specific risk category to test content filters")
7575
async def fetch_harmful_prompt(
7676
self,
77-
risk_category: Annotated[str, "The risk category (e.g., 'violence', 'hate_unfairness', 'sexual', 'self_harm')"],
77+
risk_category: Annotated[
78+
str,
79+
"The risk category (e.g., 'violence', 'hate_unfairness', 'sexual', 'self_harm')",
80+
],
7881
strategy: Annotated[str, "Attack strategy to use (e.g., 'baseline', 'jailbreak')"] = "baseline",
7982
convert_with_strategy: Annotated[str, "Optional strategy to convert the prompt"] = "",
8083
) -> Annotated[str, "A JSON string with the harmful prompt and metadata"]:
@@ -92,7 +95,9 @@ async def fetch_harmful_prompt(
9295

9396
# Directly await the async method instead of using asyncio.run()
9497
result = await self.tool_provider.fetch_harmful_prompt(
95-
risk_category_text=risk_category, strategy=strategy, convert_with_strategy=convert_with_strategy
98+
risk_category_text=risk_category,
99+
strategy=strategy,
100+
convert_with_strategy=convert_with_strategy,
96101
)
97102

98103
# Store the prompt for later conversion if successful
@@ -131,7 +136,10 @@ async def convert_prompt(
131136
@kernel_function(description="Get a harmful prompt for a specific risk category and optionally convert it")
132137
async def red_team_unified(
133138
self,
134-
category: Annotated[str, "The risk category (e.g., 'violence', 'hate_unfairness', 'sexual', 'self_harm')"],
139+
category: Annotated[
140+
str,
141+
"The risk category (e.g., 'violence', 'hate_unfairness', 'sexual', 'self_harm')",
142+
],
135143
strategy: Annotated[str, "Optional strategy to convert the prompt"] = "",
136144
) -> Annotated[str, "A JSON string with the harmful prompt and metadata"]:
137145
"""
@@ -158,7 +166,9 @@ async def red_team_unified(
158166
return json.dumps(result)
159167

160168
@kernel_function(description="Get a list of all available prompt conversion strategies")
161-
async def get_available_strategies(self) -> Annotated[str, "A JSON string with available conversion strategies"]:
169+
async def get_available_strategies(
170+
self,
171+
) -> Annotated[str, "A JSON string with available conversion strategies"]:
162172
"""
163173
Get a list of all available prompt conversion strategies.
164174
@@ -171,7 +181,9 @@ async def get_available_strategies(self) -> Annotated[str, "A JSON string with a
171181
return json.dumps({"status": "success", "available_strategies": strategies})
172182

173183
@kernel_function(description="Explain the purpose and responsible use of red teaming tools")
174-
async def explain_purpose(self) -> Annotated[str, "A JSON string with information about red teaming tools"]:
184+
async def explain_purpose(
185+
self,
186+
) -> Annotated[str, "A JSON string with information about red teaming tools"]:
175187
"""
176188
Explain the purpose and responsible use of red teaming tools.
177189
@@ -224,5 +236,9 @@ async def send_to_target(
224236
return json.dumps({"status": "success", "prompt": prompt, "response": response})
225237
except Exception as e:
226238
return json.dumps(
227-
{"status": "error", "message": f"Error calling target function: {str(e)}", "prompt": prompt}
239+
{
240+
"status": "error",
241+
"message": f"Error calling target function: {str(e)}",
242+
"prompt": prompt,
243+
}
228244
)

0 commit comments

Comments
 (0)