Skip to content
Open
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
85 changes: 75 additions & 10 deletions doc/code/scoring/2_float_scale_scorers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,20 @@
"metadata": {},
"outputs": [
{
"name": "stdout",
"name": "stderr",
"output_type": "stream",
"text": [
"Found default environment files: ['./.pyrit/.env', './.pyrit/.env.local']\n",
"Loaded environment file: ./.pyrit/.env\n",
"Loaded environment file: ./.pyrit/.env.local\n"
"Auto-discovered plaintext environment file ./.pyrit/.env will be loaded. Azure Key Vault through env_akv_ref is more secure for shared or deployed secrets; use .env.local only for deliberate local overrides. To inspect a resolved AKV-only configuration from a source checkout, run `python -m build_scripts.export_akv_environment`; it writes ~/.pyrit/.env_akv.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING: Auto-discovered plaintext environment file ./.pyrit/.env will be loaded. Azure Key Vault through env_akv_ref is more secure for shared or deployed secrets; use .env.local only for deliberate local overrides. To inspect a resolved AKV-only configuration from a source checkout, run `python -m build_scripts.export_akv_environment`; it writes ~/.pyrit/.env_akv.\n",
"Found default environment files: ['./.pyrit/.env', './.pyrit/.env.local']\n",
"Loaded environment file: ./.pyrit/.env\n",
"Loaded environment file: ./.pyrit/.env.local\n",
"[pyrit:alembic] No new upgrade operations detected.\n"
]
}
Expand Down Expand Up @@ -215,6 +217,69 @@
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"### RobloxPiiScorer\n",
"\n",
"`RobloxPiiScorer` runs [Roblox PII Classifier v2](https://huggingface.co/Roblox/roblox-pii-classifier-v2) locally and emits one `float_scale` score for each model category:\n",
"\n",
"- `privacy_asking_for_pii`\n",
"- `privacy_giving_pii`\n",
"- `directing_users_off_platform`\n",
"\n",
"Install the local runtime with `pip install \"pyrit[huggingface]\"`. The scorer uses a pinned model revision and reads `HUGGINGFACE_TOKEN` when authentication is needed. Construction is lightweight; the first scoring call downloads the roughly 2.2 GB model into the standard Hugging Face cache and loads it into memory. Applications can call `await scorer.load_model_async()` during startup to warm it.\n",
"\n",
"The values are uncalibrated sigmoid model scores in `[0, 1]`; this float scorer does not apply policy thresholds. The model card recommends `0.60` for asking, `0.55` for giving, and `0.10` for directing users off-platform. Validate those cutoffs against your own traffic before using them as decisions.\n",
"\n",
"For persisted `MessageScorable` evidence, the scorer formats chat history through the selected turn and treats that turn's role as target `t`. Later turns are excluded, so each score remains linked to one message and the context available at that point.\n",
"\n",
"Inspect all three categories rather than assuming that platform names map only to `directing_users_off_platform`: requests for handles often score as asking for PII, while sharing a handle often scores as giving PII."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "87e20c692af44ac0846539035a6d23ad",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Loading weights: 0%| | 0/393 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"['privacy_asking_for_pii'] 0.0002600505329220284\n",
"['privacy_giving_pii'] 0.9989187442474733\n",
"['directing_users_off_platform'] 0.00014016487649233598\n"
]
}
],
"source": [
"from pyrit.score import RobloxPiiScorer\n",
"\n",
"scorer = RobloxPiiScorer()\n",
"await scorer.load_model_async() # optional warm-up\n",
"scores = await scorer.score_text_async(text=\"add me on Discord; my username is skyfox_4821\")\n",
"\n",
"for score in scores:\n",
" print(score.score_category, score.get_value())"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {
"lines_to_next_cell": 0
},
Expand All @@ -233,7 +298,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"id": "12",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -263,7 +328,7 @@
},
{
"cell_type": "markdown",
"id": "11",
"id": "13",
"metadata": {
"lines_to_next_cell": 0
},
Expand All @@ -276,7 +341,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"id": "14",
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -309,7 +374,7 @@
},
{
"cell_type": "markdown",
"id": "13",
"id": "15",
"metadata": {
"lines_to_next_cell": 0
},
Expand All @@ -325,7 +390,7 @@
},
{
"cell_type": "markdown",
"id": "14",
"id": "16",
"metadata": {},
"source": [
"## Multimodal scorers\n",
Expand Down Expand Up @@ -355,7 +420,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.4"
"version": "3.13.15"
}
},
"nbformat": 4,
Expand Down
29 changes: 28 additions & 1 deletion doc/code/scoring/2_float_scale_scorers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.19.4
# jupytext_version: 1.19.5
# ---

# %% [markdown]
Expand Down Expand Up @@ -116,6 +116,33 @@
leak_score = (await system_prompt_scorer.score_async(response))[0] # type: ignore
print(f"[system prompt extraction] overlap={leak_score.get_value()}")

# %% [markdown]
# ### RobloxPiiScorer
#
# `RobloxPiiScorer` runs [Roblox PII Classifier v2](https://huggingface.co/Roblox/roblox-pii-classifier-v2) locally and emits one `float_scale` score for each model category:
#
# - `privacy_asking_for_pii`
# - `privacy_giving_pii`
# - `directing_users_off_platform`
#
# Install the local runtime with `pip install "pyrit[huggingface]"`. The scorer uses a pinned model revision and reads `HUGGINGFACE_TOKEN` when authentication is needed. Construction is lightweight; the first scoring call downloads the roughly 2.2 GB model into the standard Hugging Face cache and loads it into memory. Applications can call `await scorer.load_model_async()` during startup to warm it.
#
# The values are uncalibrated sigmoid model scores in `[0, 1]`; this float scorer does not apply policy thresholds. The model card recommends `0.60` for asking, `0.55` for giving, and `0.10` for directing users off-platform. Validate those cutoffs against your own traffic before using them as decisions.
#
# For persisted `MessageScorable` evidence, the scorer formats chat history through the selected turn and treats that turn's role as target `t`. Later turns are excluded, so each score remains linked to one message and the context available at that point.
#
# Inspect all three categories rather than assuming that platform names map only to `directing_users_off_platform`: requests for handles often score as asking for PII, while sharing a handle often scores as giving PII.

# %%
from pyrit.score import RobloxPiiScorer

scorer = RobloxPiiScorer()
await scorer.load_model_async() # optional warm-up
scores = await scorer.score_text_async(text="add me on Discord; my username is skyfox_4821")

for score in scores:
print(score.score_category, score.get_value())

# %% [markdown]
# ## Slow scorers (LLM self-ask)
#
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ dev = [
[project.optional-dependencies]
# always make sure the individual ones are in sync with the all group
huggingface = [
"sentencepiece>=0.2.0",
"torch>=2.7.0",
]
gcg = [
Expand Down Expand Up @@ -156,6 +157,7 @@ all = [
"opencv-python>=4.11.0.86",
"playwright>=1.49.0",
"pyarrow>=22.0.0; python_version >= '3.14'",
"sentencepiece>=0.2.0",
"spacy>=3.8.13,!=3.8.14,!=3.8.15", # 3.8.14-3.8.15 missing cp314 wheels
"torch>=2.7.0",
]
Expand Down
3 changes: 3 additions & 0 deletions pyrit/score/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pyrit.score.float_scale.likert_scale import LikertScale, LikertScaleEntry
from pyrit.score.float_scale.numeric_scale import NumericRange, NumericRubric
from pyrit.score.float_scale.plagiarism_scorer import PlagiarismMetric, PlagiarismScorer
from pyrit.score.float_scale.roblox_pii_scorer import RobloxPiiCategory, RobloxPiiScorer
from pyrit.score.float_scale.self_ask_general_float_scale_scorer import SelfAskGeneralFloatScaleScorer
from pyrit.score.float_scale.self_ask_likert_scorer import (
LikertScaleEvalFiles,
Expand Down Expand Up @@ -202,6 +203,8 @@
"render_shieldgemma_prompt": "pyrit.score.true_false.shieldgemma_scorer",
"render_true_false_system_prompt": "pyrit.score.true_false.self_ask_true_false_scorer",
"ResponseHandler": "pyrit.score.response_handler",
"RobloxPiiCategory": "pyrit.score.float_scale.roblox_pii_scorer",
"RobloxPiiScorer": "pyrit.score.float_scale.roblox_pii_scorer",
"Scorer": "pyrit.score.scorer",
"Scorable": "pyrit.score.scorable",
"ScorerEvalDatasetFiles": "pyrit.score.scorer_evaluation.scorer_evaluator",
Expand Down
4 changes: 4 additions & 0 deletions pyrit/score/_classifiers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""Private classifier implementations used by scorers."""
Loading
Loading