Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 179 additions & 28 deletions doc/scanner/airt.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,9 @@
"text": [
"Found default environment files: ['./.pyrit/.env', './.pyrit/.env.local']\n",
"Loaded environment file: ./.pyrit/.env\n",
"Loaded environment file: ./.pyrit/.env.local\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded environment file: ./.pyrit/.env.local\n",
"[pyrit:alembic] No new upgrade operations detected.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"TextAdaptive: _EXCLUDED_TECHNIQUES entries ['prompt_sending'] are not in the current scenario-techniques catalog ['context_compliance', 'crescendo_history_lecture', 'crescendo_journalist_interview', 'crescendo_movie_director', 'crescendo_simulated', 'flip', 'many_shot', 'pair', 'red_teaming', 'role_play_movie_script', 'role_play_persuasion', 'role_play_persuasion_written', 'role_play_trivia_game', 'role_play_video_game', 'tap', 'violent_durian']; the exclusion is a no-op for those entries. Remove stale entries or update the catalog.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
Expand All @@ -71,9 +51,23 @@
" TechniqueInitializer,\n",
")\n",
"\n",
"dataset_initializer = LoadDefaultDatasets()\n",
"dataset_initializer.set_params_from_args(\n",
" args={\n",
" \"dataset_names\": [\n",
" \"airt_hate\",\n",
" \"airt_imminent_crisis\",\n",
" \"airt_leakage\",\n",
" \"airt_malware\",\n",
" \"airt_scams\",\n",
" \"harmbench\",\n",
" ]\n",
" }\n",
")\n",
"\n",
"await initialize_pyrit_async( # type: ignore\n",
" memory_db_type=IN_MEMORY,\n",
" initializers=[TargetInitializer(), ScorerInitializer(), TechniqueInitializer(), LoadDefaultDatasets()],\n",
" initializers=[TargetInitializer(), ScorerInitializer(), TechniqueInitializer(), dataset_initializer],\n",
")\n",
"\n",
"objective_target = OpenAIChatTarget()"
Expand Down Expand Up @@ -1267,6 +1261,163 @@
"cell_type": "markdown",
"id": "15",
"metadata": {},
"source": [
"## Multilingual\n",
"\n",
"Tests whether target safeguards remain effective when harmful objectives are presented in other\n",
"languages. A run crosses registered text-compatible attack techniques with datasets and translation\n",
"strategies. By default, `translation` translates each objective into every selected language, and\n",
"`random_translation` translates individual words using the full selected language pool. A baseline\n",
"sends each objective without translation and is included by default.\n",
"\n",
"```bash\n",
"pyrit_scan airt.multilingual \\\n",
" --initializers target load_default_datasets \\\n",
" --target openai_chat \\\n",
" --dataset-names harmbench \\\n",
" --max-dataset-size 1\n",
"```\n",
"\n",
"**Available techniques:** `prompt_sending` is the default. Every registry technique (`role_play_*`,\n",
"`many_shot`, `tap`, …) whose built-in request converter chain ends in text is also available.\n",
"\n",
"**Translation strategies:** `translation` and `random_translation` (both default). A bare run translates\n",
"five objectives into five randomly selected languages, plus a word-level random language translation.\n",
"Pass `num_languages` to change the random sample size or `languages` to provide an explicit list.\n",
"The two language selectors are mutually exclusive."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f5b9ec4dd8ea441196f7fc1ee4c80305",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Executing Multilingual: 0%| | 0/5 [00:00<?, ?attack/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyrit.scenario.airt import Multilingual\n",
"\n",
"dataset_config = DatasetAttackConfiguration(dataset_names=[\"harmbench\"], max_dataset_size=1)\n",
"\n",
"scenario = Multilingual()\n",
"scenario.set_params_from_args( # type: ignore\n",
" args={\n",
" \"objective_target\": objective_target,\n",
" \"languages\": [\"French\", \"Spanish\", \"German\"],\n",
" \"translation_strategies\": [\"translation\", \"random_translation\"],\n",
" \"dataset_config\": dataset_config,\n",
" }\n",
")\n",
"await scenario.initialize_async() # type: ignore\n",
"\n",
"scenario_result = await scenario.run_async() # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\u001b[1m\u001b[36m 📊 SCENARIO RESULTS: Multilingual \u001b[0m\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Scenario Information\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📋 Scenario Details\u001b[0m\n",
"\u001b[36m • Name: Multilingual\u001b[0m\n",
"\u001b[36m • Result ID: 9f2b7978-3492-44c8-b6bf-5282ddc6ae40\u001b[0m\n",
"\u001b[36m • Scenario Version: 1\u001b[0m\n",
"\u001b[36m • PyRIT Version: 1.1.0.dev0\u001b[0m\n",
"\u001b[36m • Description:\u001b[0m\n",
"\u001b[36m Multilingual scenario implementation for PyRIT. Tests how vulnerable a model is to non-English language use.\u001b[0m\n",
"\n",
"\u001b[1m 🎯 Target Information\u001b[0m\n",
"\u001b[36m • Target Type: OpenAIChatTarget\u001b[0m\n",
"\u001b[36m • Target Model: gpt-4o-japan-nilfilter\u001b[0m\n",
"\u001b[36m • Target Endpoint: https://pyrit-japan-test.openai.azure.com/openai/v1\u001b[0m\n",
"\n",
"\u001b[1m 📊 Scorer Information\u001b[0m\n",
"\u001b[37m ▸ Scorer Identifier\u001b[0m\n",
"\u001b[36m • Scorer Type: FloatScaleThresholdScorer\u001b[0m\n",
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
"\u001b[36m └─ Composite of 1 scorer(s):\u001b[0m\n",
"\u001b[36m • Scorer Type: AzureContentFilterScorer\u001b[0m\n",
"\u001b[36m • scorer_type: float_scale\u001b[0m\n",
"\n",
"\u001b[37m ▸ Performance Metrics\u001b[0m\n",
"\u001b[31m • Accuracy: 59.24%\u001b[0m\n",
"\u001b[36m • Accuracy Std Error: ±0.0247\u001b[0m\n",
"\u001b[31m • F1 Score: 0.5306\u001b[0m\n",
"\u001b[31m • Precision: 0.5987\u001b[0m\n",
"\u001b[31m • Recall: 0.4764\u001b[0m\n",
"\u001b[32m • Average Score Time: 0.04s\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Overall Statistics\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📈 Summary\u001b[0m\n",
"\u001b[32m • Total Techniques: 5\u001b[0m\n",
"\u001b[32m • Total Attack Results: 5\u001b[0m\n",
"\u001b[32m • Overall Success Rate: 0%\u001b[0m\n",
"\u001b[32m • Unique Objectives: 1\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Per-Group Breakdown\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: baseline\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: French\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: Spanish\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: German\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: Random Translation\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n"
]
}
],
"source": [
"await output_scenario_async(scenario_result)"
]
},
{
"cell_type": "markdown",
"id": "18",
"metadata": {},
"source": [
"## Leakage\n",
"\n",
Expand Down Expand Up @@ -1302,7 +1453,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "16",
"id": "19",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1334,7 +1485,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "17",
"id": "20",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1414,7 +1565,7 @@
},
{
"cell_type": "markdown",
"id": "18",
"id": "21",
"metadata": {},
"source": [
"## Scam\n",
Expand All @@ -1437,7 +1588,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "19",
"id": "22",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1469,7 +1620,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "20",
"id": "23",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1561,7 +1712,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
"version": "3.13.15"
}
},
"nbformat": 4,
Expand Down
64 changes: 62 additions & 2 deletions doc/scanner/airt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.19.4
# jupytext_version: 1.19.5
# ---

# %% [markdown]
Expand All @@ -30,9 +30,23 @@
TechniqueInitializer,
)

dataset_initializer = LoadDefaultDatasets()
dataset_initializer.set_params_from_args(
args={
"dataset_names": [
"airt_hate",
"airt_imminent_crisis",
"airt_leakage",
"airt_malware",
"airt_scams",
"harmbench",
]
}
)

await initialize_pyrit_async( # type: ignore
memory_db_type=IN_MEMORY,
initializers=[TargetInitializer(), ScorerInitializer(), TechniqueInitializer(), LoadDefaultDatasets()],
initializers=[TargetInitializer(), ScorerInitializer(), TechniqueInitializer(), dataset_initializer],
)

objective_target = OpenAIChatTarget()
Expand Down Expand Up @@ -201,6 +215,52 @@
# %%
await output_scenario_async(scenario_result)

# %% [markdown]
# ## Multilingual
#
# Tests whether target safeguards remain effective when harmful objectives are presented in other
# languages. A run crosses registered text-compatible attack techniques with datasets and translation
# strategies. By default, `translation` translates each objective into every selected language, and
# `random_translation` translates individual words using the full selected language pool. A baseline
# sends each objective without translation and is included by default.
#
# ```bash
# pyrit_scan airt.multilingual \
# --initializers target load_default_datasets \
# --target openai_chat \
# --dataset-names harmbench \
# --max-dataset-size 1
# ```
#
# **Available techniques:** `prompt_sending` is the default. Every registry technique (`role_play_*`,
# `many_shot`, `tap`, …) whose built-in request converter chain ends in text is also available.
#
# **Translation strategies:** `translation` and `random_translation` (both default). A bare run translates
# five objectives into five randomly selected languages, plus a word-level random language translation.
# Pass `num_languages` to change the random sample size or `languages` to provide an explicit list.
# The two language selectors are mutually exclusive.

# %%
from pyrit.scenario.airt import Multilingual

dataset_config = DatasetAttackConfiguration(dataset_names=["harmbench"], max_dataset_size=1)

scenario = Multilingual()
scenario.set_params_from_args( # type: ignore
args={
"objective_target": objective_target,
"languages": ["French", "Spanish", "German"],
"translation_strategies": ["translation", "random_translation"],
"dataset_config": dataset_config,
}
)
await scenario.initialize_async() # type: ignore

scenario_result = await scenario.run_async() # type: ignore

# %%
await output_scenario_async(scenario_result)

# %% [markdown]
# ## Leakage
#
Expand Down
18 changes: 17 additions & 1 deletion pyrit/converter/random_translation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pyrit.converter.llm_generic_text_converter import LLMGenericTextConverter
from pyrit.converter.text_selection_strategy import WordSelectionStrategy
from pyrit.converter.word_level_converter import WordLevelConverter
from pyrit.models import PromptDataType, SeedDataset, SeedPrompt
from pyrit.models import ComponentIdentifier, PromptDataType, SeedDataset, SeedPrompt
from pyrit.prompt_target import PromptTarget

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,6 +83,22 @@ def __init__(
else:
self.languages = languages

def _build_identifier(self) -> ComponentIdentifier:
"""
Build the converter identifier with the random translation language pool.

Returns:
ComponentIdentifier: The converter identifier.
"""
base_identifier = super()._build_identifier()
return self._create_identifier(
params={
**base_identifier.params,
"languages": sorted(self.languages, key=str.casefold),
},
converter_target=self._converter_target.get_identifier(),
)

async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
"""
Convert the given prompt into the target format supported by the converter.
Expand Down
Loading
Loading