Skip to content

FEAT Add dedicated LlamaGuard scorer#1867

Open
immu4989 wants to merge 11 commits into
microsoft:mainfrom
immu4989:feat/llamaguard-scorer
Open

FEAT Add dedicated LlamaGuard scorer#1867
immu4989 wants to merge 11 commits into
microsoft:mainfrom
immu4989:feat/llamaguard-scorer

Conversation

@immu4989

@immu4989 immu4989 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #1830.

Summary

Adds a dedicated LlamaGuardScorer built on the scorer-composition API introduced by #2125, #2146, and #2157. The scorer sends Llama Guard's native plain-text classification request and parses its native safe / unsafe\nS1,S6 response without adding a generic SelfAskTrueFalseScorer hook.

  • Adds explicit LlamaGuardMessageRole.USER and .AGENT request framing, defaulting to Agent for model-response classification.
  • Keeps the PyRIT scoring objective on the resulting Score without inserting it into the Llama Guard conversation.
  • Adds versioned Llama Guard 3 prompt and MLCommons policy assets under pyrit/datasets/score/llamaguard/.
  • Adds LlamaGuardPolicy and LlamaGuardCategory value types for validated custom taxonomies and optional category descriptions.
  • Binds strict response parsing to the configured policy, rejecting malformed output, duplicate categories, and categories outside that policy.
  • Sends no system prompt, JSON response format, or response schema to the classifier endpoint.
  • Replaces the architecture refactor's test-only Llama Guard pilot with production scorer, policy, parser, and transport coverage.

Usage

from pyrit.score import LlamaGuardScorer

scorer = LlamaGuardScorer(chat_target=llamaguard_endpoint)
scores = await scorer.score_text_async("Model response to classify")

User-input classification is available with message_role=LlamaGuardMessageRole.USER. Custom policies can be loaded with LlamaGuardPolicy.from_yaml(...) and passed through the policy argument.

Validation

  • Complete scorer unit suite: 1,378 passed, 16 skipped.
  • Changed-file pre-commit hooks, including Ruff and ty: passed.

The endpoint round trip is covered with a mocked PromptTarget; this has not been exercised against a live Llama Guard deployment.

…rd support

Per the design discussion in microsoft#1830, extend SelfAskTrueFalseScorer with an optional response_parser callable so the same scorer can wrap fine-tuned safety classifiers (LlamaGuard, ShieldGemma, WildGuard, HarmBench-paper) whose output is not JSON. Default behavior is unchanged.

Ships a parse_llamaguard_response helper plus YAML assets (TrueFalseQuestion and system prompt) so users can drop in any LlamaGuard-serving endpoint via PromptChatTarget. No local transformers or torch dependency.

Also fixes a latent typing issue in Scorer._score_value_with_llm: score_value_description now defaults to '' when the response omits the description field, instead of being None against a str-typed field.

@romanlutz romanlutz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a llama-guard deployment and can't test this. Can you confirm that you did test it?

Comment thread pyrit/datasets/score/true_false_question/llamaguard.yaml Outdated
Comment thread pyrit/datasets/score/true_false_question/llamaguard_system_prompt.yaml Outdated
Comment thread pyrit/score/scorer.py Outdated
Comment thread pyrit/score/__init__.py Outdated
Comment thread pyrit/score/scorer.py Outdated
@immu4989

immu4989 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

I don't have a llama-guard deployment and can't test this. Can you confirm that you did test it?

The PR has not been exercised against a live LlamaGuard endpoint. The tests cover the plumbing in two places:

  1. The pure-parser tests in tests/unit/score/test_llamaguard_parser.py validate the contract against every documented LlamaGuard output shape (safe, Safe (mixed case), whitespace, unsafe with single, multiple, missing, and empty category lines, plus empty input, refusal strings, and a malformed verdict).
  2. The integration tests feed the same canonical output shapes through a mocked PromptChatTarget into SelfAskTrueFalseScorer with response_parser=parse_llamaguard_response, asserting the full Score object lands correctly with violated_categories in metadata.

What is not covered: the actual LlamaGuard-3-8B chat template rendering and the round-trip through a real endpoint. If a live smoke test against Together/Groq/Fireworks would help you merge with confidence, I am happy to run one and paste the transcript here. I held off on writing that as a unit-suite test because it would require a configured API key and would not be reproducible in CI.

immu4989 and others added 4 commits June 2, 2026 15:23
- Wire YAMLs into discoverable paths: add TrueFalseQuestionPaths.LLAMAGUARD and LLAMAGUARD_SYSTEM_PROMPT_PATH module-level constant.

- Drop misleading 'parameters' declaration in llamaguard_system_prompt.yaml; template is static.

- Switch :class: reST cross-references to plain double-backticks in scorer.py and self_ask_true_false_scorer.py (PyRIT docs build is MyST).

- Reorder __all__ in pyrit/score/__init__.py: parse_llamaguard_response between ObjectiveScorerMetrics and PlagiarismMetric, LLAMAGUARD_SYSTEM_PROMPT_PATH between LikertScalePaths and MarkdownInjectionScorer.
# Conflicts:
#	pyrit/score/__init__.py
#	pyrit/score/scorer.py
#	pyrit/score/true_false/self_ask_true_false_scorer.py
# Conflicts:
#	pyrit/score/scorer.py
#	pyrit/score/true_false/self_ask_true_false_scorer.py
@immu4989

Copy link
Copy Markdown
Contributor Author

Friendly bump on this. Conflicts resolved twice now and merge-state is clean apart from the four conversations I've marked addressed. Happy to apply any further changes if you'd like, or close if direction has shifted.

@immu4989

immu4989 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Closing this. The response_parser hook here was the right shape for the immediate LlamaGuard need, but the scorer-architecture series (#2125, #2146) replaces it with a cleaner CallableResponseHandler, and I would rather not leave a stale hook in the base class competing with that direction.

Glad parse_llamaguard_response is carried into the pilot in #2146. Once that lands I would like to follow up with ShieldGemma and WildGuard handlers on the same CallableResponseHandler pattern, which is the natural next step for the safety-classifier coverage this PR was aiming at.

Thanks for the review time on this one.

@immu4989 immu4989 closed this Jul 12, 2026
@romanlutz

Copy link
Copy Markdown
Contributor

I actually think this is a great contribution and inspired me to do #2125 #2146 #2157 and now we can revive this! I hope you don't mind...

@romanlutz romanlutz reopened this Jul 12, 2026
Copilot AI added 3 commits July 12, 2026 15:37
…r' into feat/llamaguard-scorer

# Conflicts:
#	pyrit/score/true_false/self_ask_true_false_scorer.py
# Conflicts:
#	pyrit/score/__init__.py
#	pyrit/score/scorer.py
#	pyrit/score/true_false/self_ask_true_false_scorer.py
Ship the LlamaGuard parser and static prompt as production components for SelfAskTrueFalseScorer through CallableResponseHandler. Replace the architecture pilot with coverage of the exported assets and metadata contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9be2dff6-a4fb-4690-9f8d-767dea2f98bd
@immu4989

Copy link
Copy Markdown
Contributor Author

I actually think this is a great contribution and inspired me to do #2125 #2146 #2157 and now we can revive this! I hope you don't mind...

Not at all, I am glad it was useful, and happy to revive it. Seeing the series land, the composition approach is clearly the better home for this than the response_parser hook this PR originally added.

Since #2125 through #2157 removed that hook, reviving this means reworking the PR onto the new API rather than merging as is. Concretely: drop the old response_parser plumbing, and ship the LlamaGuard piece as a parse_llamaguard_response helper plus the canonical Llama-Guard-3 system prompt, wired through CallableResponseHandler. No new scorer class, consistent with the series. The parser and prompt carry over almost directly from what was already here.

One consolidation note: a few hours before you reopened this, I filed #2172 proposing exactly that, the LlamaGuard parser and prompt on CallableResponseHandler, with a question about whether you wanted it in-tree. Your reopen answers that. I am happy to do the work here and have this PR close #2172, or keep #2172 as the design thread if you prefer. Whichever you would rather track.

I will start the rework unless you would rather shape the approach first.

@romanlutz romanlutz changed the title FEAT Add response_parser hook to SelfAskTrueFalseScorer with LlamaGuard support FEAT Add LlamaGuard scorer composition Jul 13, 2026
@romanlutz

Copy link
Copy Markdown
Contributor

@immu4989 i was curious to see if it would go as cleanly as I thought. See the current PR state. Is this in line with your thinking?

@immu4989

Copy link
Copy Markdown
Contributor Author

Yes, this is exactly it. I pulled the branch and ran it: the two new test files pass (16), the full tests/unit/score suite is green (1379), and ty and ruff are clean. The shape is what I had in mind: parse_llamaguard_response and LLAMAGUARD_SYSTEM_PROMPT_PATH as the reusable pieces, the bundled question and system-prompt YAML, TrueFalseQuestionPaths.LLAMAGUARD, and the pilot replaced by real parser and composition tests. No new scorer class, composed through CallableResponseHandler.

One small optional thing from reading the parser: the metadata keys differ by verdict. The safe branch returns {"raw_classifier_output": ...} while the unsafe branch adds violated_categories, so a consumer doing score_metadata["violated_categories"] would KeyError on a safe score. If a stable schema across verdicts is worth it, the safe branch could return violated_categories: "" as well. Happy to push that small change to the branch if you would like it, otherwise it is fine as is.

On consolidation: this supersedes #2172, which I filed proposing this same work a few hours before you reopened here, so this PR can close it. I will add a pointer there.

I would also be glad to follow up with ShieldGemma and WildGuard on the same handler once this lands.

Copilot AI added 2 commits July 13, 2026 14:14
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9be2dff6-a4fb-4690-9f8d-767dea2f98bd
@romanlutz

Copy link
Copy Markdown
Contributor

I suspect it's better to have a proper scorer vs what's in the PR right now. I'll just push now so that you can see what that looks like but I can remove that commit, too.

@romanlutz romanlutz changed the title FEAT Add LlamaGuard scorer composition FEAT Add dedicated LlamaGuard scorer Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT Add LlamaGuard scorer for safety classification of model outputs

3 participants