Skip to content

Commit c1fc122

Browse files
Copilotslister1001Copilot
authored
Add language support to red_team with SupportedLanguages enum (#42132)
* Initial plan * Add language support to RedTeam with SupportedLanguages enum Co-authored-by: slister1001 <[email protected]> * Fix black formatting issues in RedTeam language support files Co-authored-by: slister1001 <[email protected]> * test fixes and black code formatting * Update sdk/evaluation/azure-ai-evaluation/tests/unittests/test_redteam/test_red_team_language_support.py Co-authored-by: Copilot <[email protected]> * Update sdk/evaluation/azure-ai-evaluation/tests/unittests/test_redteam/test_red_team_language_support.py Co-authored-by: Copilot <[email protected]> * Update test_red_team_language_support.py * add SupportedLanguages in redteam module * black formatting fix --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: slister1001 <[email protected]> Co-authored-by: Sydney Lister <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 265a933 commit c1fc122

File tree

5 files changed

+258
-6
lines changed

5 files changed

+258
-6
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
try:
66
from ._red_team import RedTeam
77
from ._attack_strategy import AttackStrategy
8-
from ._attack_objective_generator import RiskCategory
8+
from ._attack_objective_generator import RiskCategory, SupportedLanguages
99
from ._red_team_result import RedTeamResult
1010
except ImportError:
1111
raise ImportError(
@@ -18,4 +18,5 @@
1818
"AttackStrategy",
1919
"RiskCategory",
2020
"RedTeamResult",
21+
"SupportedLanguages",
2122
]

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ class RiskCategory(str, Enum):
2424
XPIA = "xpia"
2525

2626

27+
@experimental
28+
class SupportedLanguages(Enum):
29+
"""Supported languages for attack objectives, using ISO standard language codes."""
30+
31+
Spanish = "es"
32+
Italian = "it"
33+
French = "fr"
34+
German = "de"
35+
SimplifiedChinese = "zh-cn"
36+
Portuguese = "pt"
37+
Japanese = "ja"
38+
English = "en"
39+
Korean = "ko"
40+
41+
2742
@experimental
2843
class _InternalRiskCategory(str, Enum):
2944
ECI = "eci"

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from ._attack_strategy import AttackStrategy
4646
from ._attack_objective_generator import (
4747
RiskCategory,
48+
SupportedLanguages,
4849
_AttackObjectiveGenerator,
4950
)
5051

@@ -95,6 +96,8 @@ class RedTeam:
9596
:type application_scenario: Optional[str]
9697
:param custom_attack_seed_prompts: Path to a JSON file containing custom attack seed prompts (can be absolute or relative path)
9798
:type custom_attack_seed_prompts: Optional[str]
99+
:param language: Language to use for attack objectives generation. Defaults to English.
100+
:type language: SupportedLanguages
98101
:param output_dir: Directory to save output files (optional)
99102
:type output_dir: Optional[str]
100103
:param attack_success_thresholds: Threshold configuration for determining attack success.
@@ -113,6 +116,7 @@ def __init__(
113116
num_objectives: int = 10,
114117
application_scenario: Optional[str] = None,
115118
custom_attack_seed_prompts: Optional[str] = None,
119+
language: SupportedLanguages = SupportedLanguages.English,
116120
output_dir=".",
117121
attack_success_thresholds: Optional[Dict[RiskCategory, int]] = None,
118122
):
@@ -135,6 +139,8 @@ def __init__(
135139
:type application_scenario: Optional[str]
136140
:param custom_attack_seed_prompts: Path to a JSON file with custom attack prompts
137141
:type custom_attack_seed_prompts: Optional[str]
142+
:param language: Language to use for attack objectives generation. Defaults to English.
143+
:type language: SupportedLanguages
138144
:param output_dir: Directory to save evaluation outputs and logs. Defaults to current working directory.
139145
:type output_dir: str
140146
:param attack_success_thresholds: Threshold configuration for determining attack success.
@@ -147,6 +153,7 @@ def __init__(
147153
self.azure_ai_project = validate_azure_ai_project(azure_ai_project)
148154
self.credential = credential
149155
self.output_dir = output_dir
156+
self.language = language
150157
self._one_dp_project = is_onedp_project(azure_ai_project)
151158

152159
# Configure attack success thresholds
@@ -434,6 +441,7 @@ async def _get_rai_attack_objectives(
434441
risk_category=other_risk,
435442
application_scenario=application_scenario or "",
436443
strategy="tense",
444+
language=self.language.value,
437445
scan_session_id=self.scan_session_id,
438446
)
439447
else:
@@ -442,6 +450,7 @@ async def _get_rai_attack_objectives(
442450
risk_category=other_risk,
443451
application_scenario=application_scenario or "",
444452
strategy=None,
453+
language=self.language.value,
445454
scan_session_id=self.scan_session_id,
446455
)
447456

sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/simulator/_model_tools/_generated_rai_client.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ class GeneratedRAIClient:
3030
:type token_manager: ~azure.ai.evaluation.simulator._model_tools._identity_manager.APITokenManager
3131
"""
3232

33-
def __init__(self, azure_ai_project: Union[AzureAIProject, str], token_manager: ManagedIdentityAPITokenManager):
33+
def __init__(
34+
self,
35+
azure_ai_project: Union[AzureAIProject, str],
36+
token_manager: ManagedIdentityAPITokenManager,
37+
):
3438
self.azure_ai_project = azure_ai_project
3539
self.token_manager = token_manager
3640

@@ -53,10 +57,14 @@ def __init__(self, azure_ai_project: Union[AzureAIProject, str], token_manager:
5357
).rai_svc
5458
else:
5559
self._client = AIProjectClient(
56-
endpoint=azure_ai_project, credential=token_manager, user_agent_policy=user_agent_policy
60+
endpoint=azure_ai_project,
61+
credential=token_manager,
62+
user_agent_policy=user_agent_policy,
5763
).red_teams
5864
self._evaluation_onedp_client = EvaluationServiceOneDPClient(
59-
endpoint=azure_ai_project, credential=token_manager, user_agent_policy=user_agent_policy
65+
endpoint=azure_ai_project,
66+
credential=token_manager,
67+
user_agent_policy=user_agent_policy,
6068
)
6169

6270
def _get_service_discovery_url(self):
@@ -68,7 +76,10 @@ def _get_service_discovery_url(self):
6876
import requests
6977

7078
bearer_token = self._fetch_or_reuse_token(self.token_manager)
71-
headers = {"Authorization": f"Bearer {bearer_token}", "Content-Type": "application/json"}
79+
headers = {
80+
"Authorization": f"Bearer {bearer_token}",
81+
"Content-Type": "application/json",
82+
}
7283

7384
response = requests.get(
7485
f"https://management.azure.com/subscriptions/{self.azure_ai_project['subscription_id']}/"
@@ -100,6 +111,7 @@ async def get_attack_objectives(
100111
risk_category: Optional[str] = None,
101112
application_scenario: str = None,
102113
strategy: Optional[str] = None,
114+
language: str = "en",
103115
scan_session_id: Optional[str] = None,
104116
) -> Dict:
105117
"""Get attack objectives using the auto-generated operations.
@@ -112,6 +124,8 @@ async def get_attack_objectives(
112124
:type application_scenario: str
113125
:param strategy: Optional strategy to filter the attack objectives
114126
:type strategy: Optional[str]
127+
:param language: Language code for the attack objectives (e.g., "en", "es", "fr")
128+
:type language: str
115129
:param scan_session_id: Optional unique session ID for the scan
116130
:type scan_session_id: Optional[str]
117131
:return: The attack objectives
@@ -122,7 +136,7 @@ async def get_attack_objectives(
122136
response = self._client.get_attack_objectives(
123137
risk_types=[risk_type],
124138
risk_category=risk_category,
125-
lang="en",
139+
lang=language,
126140
strategy=strategy,
127141
headers={"x-ms-client-request-id": scan_session_id},
128142
)
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import pytest
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
from azure.ai.evaluation.red_team._red_team import RedTeam, RiskCategory, SupportedLanguages
4+
from azure.core.credentials import TokenCredential
5+
6+
7+
@pytest.fixture
8+
def mock_azure_ai_project():
9+
return {
10+
"subscription_id": "test-subscription",
11+
"resource_group_name": "test-resource-group",
12+
"project_name": "test-project",
13+
}
14+
15+
16+
@pytest.fixture
17+
def mock_credential():
18+
return MagicMock(spec=TokenCredential)
19+
20+
21+
class TestRedTeamLanguageSupport:
22+
"""Test language support functionality in RedTeam class."""
23+
24+
def test_red_team_init_default_language(self, mock_azure_ai_project, mock_credential):
25+
"""Test that RedTeam initializes with default English language."""
26+
with patch("azure.ai.evaluation.red_team._red_team.GeneratedRAIClient"), patch(
27+
"azure.ai.evaluation.red_team._red_team.setup_logger"
28+
) as mock_setup_logger, patch("azure.ai.evaluation.red_team._red_team.initialize_pyrit"), patch(
29+
"azure.ai.evaluation.red_team._red_team._AttackObjectiveGenerator"
30+
):
31+
32+
mock_logger = MagicMock()
33+
mock_setup_logger.return_value = mock_logger
34+
35+
agent = RedTeam(
36+
azure_ai_project=mock_azure_ai_project,
37+
credential=mock_credential,
38+
risk_categories=[RiskCategory.Violence],
39+
num_objectives=5,
40+
)
41+
42+
# Verify default language is English
43+
assert agent.language == SupportedLanguages.English
44+
45+
def test_red_team_init_custom_language(self, mock_azure_ai_project, mock_credential):
46+
"""Test that RedTeam initializes with custom language."""
47+
with patch("azure.ai.evaluation.red_team._red_team.GeneratedRAIClient"), patch(
48+
"azure.ai.evaluation.red_team._red_team.setup_logger"
49+
) as mock_setup_logger, patch("azure.ai.evaluation.red_team._red_team.initialize_pyrit"), patch(
50+
"azure.ai.evaluation.red_team._red_team._AttackObjectiveGenerator"
51+
):
52+
53+
mock_logger = MagicMock()
54+
mock_setup_logger.return_value = mock_logger
55+
56+
# Test with Spanish language
57+
agent = RedTeam(
58+
azure_ai_project=mock_azure_ai_project,
59+
credential=mock_credential,
60+
risk_categories=[RiskCategory.Violence],
61+
num_objectives=5,
62+
language=SupportedLanguages.Spanish,
63+
)
64+
65+
assert agent.language == SupportedLanguages.Spanish
66+
67+
@pytest.mark.parametrize(
68+
"language",
69+
[
70+
SupportedLanguages.English,
71+
SupportedLanguages.Spanish,
72+
SupportedLanguages.French,
73+
SupportedLanguages.German,
74+
SupportedLanguages.Italian,
75+
SupportedLanguages.Portuguese,
76+
SupportedLanguages.Japanese,
77+
SupportedLanguages.Korean,
78+
SupportedLanguages.SimplifiedChinese,
79+
],
80+
)
81+
def test_red_team_init_all_supported_languages(self, mock_azure_ai_project, mock_credential, language):
82+
"""Test that RedTeam initializes correctly with all supported languages."""
83+
with patch("azure.ai.evaluation.red_team._red_team.GeneratedRAIClient"), patch(
84+
"azure.ai.evaluation.red_team._red_team.setup_logger"
85+
) as mock_setup_logger, patch("azure.ai.evaluation.red_team._red_team.initialize_pyrit"), patch(
86+
"azure.ai.evaluation.red_team._red_team._AttackObjectiveGenerator"
87+
):
88+
89+
mock_logger = MagicMock()
90+
mock_setup_logger.return_value = mock_logger
91+
92+
agent = RedTeam(
93+
azure_ai_project=mock_azure_ai_project,
94+
credential=mock_credential,
95+
risk_categories=[RiskCategory.Violence],
96+
num_objectives=5,
97+
language=language,
98+
)
99+
100+
assert agent.language == language
101+
102+
@pytest.mark.asyncio
103+
async def test_get_attack_objectives_passes_language(self, mock_azure_ai_project, mock_credential):
104+
"""Test that _get_attack_objectives passes language parameter to generated RAI client."""
105+
with patch("azure.ai.evaluation.red_team._red_team.GeneratedRAIClient") as mock_rai_client_class, patch(
106+
"azure.ai.evaluation.red_team._red_team.setup_logger"
107+
) as mock_setup_logger, patch("azure.ai.evaluation.red_team._red_team.initialize_pyrit"), patch(
108+
"azure.ai.evaluation.red_team._red_team._AttackObjectiveGenerator"
109+
) as mock_attack_obj_generator_class:
110+
111+
mock_logger = MagicMock()
112+
mock_setup_logger.return_value = mock_logger
113+
114+
# Set up mock RAI client instance
115+
mock_rai_client = MagicMock()
116+
mock_rai_client.get_attack_objectives = AsyncMock(
117+
return_value=[
118+
{
119+
"id": "test-id",
120+
"messages": [{"role": "user", "content": "test prompt"}],
121+
"metadata": {"target_harms": [{"risk-type": "violence"}]},
122+
}
123+
]
124+
)
125+
mock_rai_client_class.return_value = mock_rai_client
126+
127+
# Set up mock attack objective generator instance
128+
mock_attack_obj_generator = MagicMock()
129+
mock_attack_obj_generator.num_objectives = 5
130+
mock_attack_obj_generator.custom_attack_seed_prompts = None
131+
mock_attack_obj_generator.validated_prompts = False
132+
mock_attack_obj_generator_class.return_value = mock_attack_obj_generator
133+
134+
# Create RedTeam instance with Spanish language
135+
agent = RedTeam(
136+
azure_ai_project=mock_azure_ai_project,
137+
credential=mock_credential,
138+
risk_categories=[RiskCategory.Violence],
139+
num_objectives=5,
140+
language=SupportedLanguages.Spanish,
141+
)
142+
143+
agent.generated_rai_client = mock_rai_client
144+
agent.scan_session_id = "test-session"
145+
146+
# Call _get_attack_objectives
147+
await agent._get_attack_objectives(
148+
risk_category=RiskCategory.Violence,
149+
application_scenario="test scenario",
150+
strategy="baseline",
151+
)
152+
153+
# Verify that get_attack_objectives was called with Spanish language
154+
mock_rai_client.get_attack_objectives.assert_called_once()
155+
call_args = mock_rai_client.get_attack_objectives.call_args
156+
assert call_args.kwargs["language"] == SupportedLanguages.Spanish.value
157+
158+
@pytest.mark.asyncio
159+
async def test_get_attack_objectives_tense_strategy_passes_language(self, mock_azure_ai_project, mock_credential):
160+
"""Test that _get_attack_objectives passes language parameter for tense strategy."""
161+
with patch("azure.ai.evaluation.red_team._red_team.GeneratedRAIClient") as mock_rai_client_class, patch(
162+
"azure.ai.evaluation.red_team._red_team.setup_logger"
163+
) as mock_setup_logger, patch("azure.ai.evaluation.red_team._red_team.initialize_pyrit"), patch(
164+
"azure.ai.evaluation.red_team._red_team._AttackObjectiveGenerator"
165+
) as mock_attack_obj_generator_class:
166+
167+
mock_logger = MagicMock()
168+
mock_setup_logger.return_value = mock_logger
169+
170+
# Set up mock RAI client instance
171+
mock_rai_client = MagicMock()
172+
mock_rai_client.get_attack_objectives = AsyncMock(
173+
return_value=[
174+
{
175+
"id": "test-id",
176+
"messages": [{"role": "user", "content": "test prompt"}],
177+
"metadata": {"target_harms": [{"risk-type": "violence"}]},
178+
}
179+
]
180+
)
181+
mock_rai_client_class.return_value = mock_rai_client
182+
183+
# Set up mock attack objective generator instance
184+
mock_attack_obj_generator = MagicMock()
185+
mock_attack_obj_generator.num_objectives = 5
186+
mock_attack_obj_generator.custom_attack_seed_prompts = None
187+
mock_attack_obj_generator.validated_prompts = False
188+
mock_attack_obj_generator_class.return_value = mock_attack_obj_generator
189+
190+
# Create RedTeam instance with French language
191+
agent = RedTeam(
192+
azure_ai_project=mock_azure_ai_project,
193+
credential=mock_credential,
194+
risk_categories=[RiskCategory.Violence],
195+
num_objectives=5,
196+
language=SupportedLanguages.French,
197+
)
198+
199+
agent.generated_rai_client = mock_rai_client
200+
agent.scan_session_id = "test-session"
201+
202+
# Call _get_attack_objectives with tense strategy
203+
await agent._get_attack_objectives(
204+
risk_category=RiskCategory.Violence,
205+
application_scenario="test scenario",
206+
strategy="tense",
207+
)
208+
209+
# Verify that get_attack_objectives was called with French language and tense strategy
210+
mock_rai_client.get_attack_objectives.assert_called_once()
211+
call_args = mock_rai_client.get_attack_objectives.call_args
212+
assert call_args.kwargs["language"] == SupportedLanguages.French.value # French language code
213+
assert call_args.kwargs["strategy"] == "tense"

0 commit comments

Comments
 (0)