-
Notifications
You must be signed in to change notification settings - Fork 653
FEAT: More Informative Attack Exceptions #1318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rlundeen2
merged 6 commits into
Azure:main
from
rlundeen2:users/rlundeen/2026_01_16_better_exceptions
Jan 27, 2026
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
403b9a5
Adding better exception messages
rlundeen2 d48ee9f
pre-commit
rlundeen2 563999a
adding objective
rlundeen2 aa65ff8
pr feedback
rlundeen2 a27e98a
Merge branch 'main' into users/rlundeen/2026_01_16_better_exceptions
rlundeen2 ec62ed3
pre-commit
rlundeen2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT license. | ||
|
|
||
| """ | ||
| Context management for enhanced exception and retry logging in PyRIT. | ||
|
|
||
| This module provides a contextvar-based system for tracking which component | ||
| (objective_target, adversarial_chat, objective_scorer, etc.) is currently | ||
| executing, allowing retry decorators and exception handlers to include | ||
| meaningful context in their messages. | ||
| """ | ||
|
|
||
| from contextvars import ContextVar | ||
| from dataclasses import dataclass, field | ||
| from enum import Enum | ||
| from typing import Any, Dict, Optional | ||
|
|
||
|
|
||
| class ComponentRole(Enum): | ||
| """ | ||
| Identifies the role of a component within an attack execution. | ||
|
|
||
| This enum is used to provide meaningful context in error messages and retry logs, | ||
| helping users identify which part of an attack encountered an issue. | ||
| """ | ||
|
|
||
| # Core attack components | ||
| OBJECTIVE_TARGET = "objective_target" | ||
| ADVERSARIAL_CHAT = "adversarial_chat" | ||
|
|
||
| # Scoring components | ||
| OBJECTIVE_SCORER = "objective_scorer" | ||
| OBJECTIVE_SCORER_TARGET = "objective_scorer_target" | ||
| REFUSAL_SCORER = "refusal_scorer" | ||
| REFUSAL_SCORER_TARGET = "refusal_scorer_target" | ||
| AUXILIARY_SCORER = "auxiliary_scorer" | ||
| AUXILIARY_SCORER_TARGET = "auxiliary_scorer_target" | ||
|
|
||
| # Conversion components | ||
| CONVERTER = "converter" | ||
| CONVERTER_TARGET = "converter_target" | ||
|
|
||
| # Other components | ||
| UNKNOWN = "unknown" | ||
|
|
||
|
|
||
| @dataclass | ||
| class ExecutionContext: | ||
| """ | ||
| Holds context information about the currently executing component. | ||
|
|
||
| This context is used to enrich error messages and retry logs with | ||
| information about which component failed and its configuration. | ||
| """ | ||
|
|
||
| # The role of the component (e.g., objective_scorer, adversarial_chat) | ||
| component_role: ComponentRole = ComponentRole.UNKNOWN | ||
|
|
||
| # The attack strategy class name (e.g., "PromptSendingAttack") | ||
| attack_strategy_name: Optional[str] = None | ||
|
|
||
| # The identifier from the attack strategy's get_identifier() | ||
| attack_identifier: Optional[Dict[str, Any]] = None | ||
|
|
||
| # The identifier from the component's get_identifier() (target, scorer, etc.) | ||
| component_identifier: Optional[Dict[str, Any]] = None | ||
|
|
||
| # The objective target conversation ID if available | ||
| objective_target_conversation_id: Optional[str] = None | ||
|
|
||
| # The endpoint/URI if available (extracted from component_identifier for quick access) | ||
| endpoint: Optional[str] = None | ||
|
|
||
| # The component class name (extracted from component_identifier.__type__ for quick access) | ||
| component_name: Optional[str] = None | ||
|
|
||
| # The attack objective if available | ||
| objective: Optional[str] = None | ||
|
|
||
| def get_retry_context_string(self) -> str: | ||
| """ | ||
| Generate a concise context string for retry log messages. | ||
|
|
||
| Returns: | ||
| str: A formatted string with component role, component name, and endpoint. | ||
| """ | ||
| parts = [self.component_role.value] | ||
| if self.component_name: | ||
| parts.append(f"({self.component_name})") | ||
| if self.endpoint: | ||
| parts.append(f"endpoint: {self.endpoint}") | ||
| return " ".join(parts) | ||
|
|
||
| def get_exception_details(self) -> str: | ||
| """ | ||
| Generate detailed exception context for error messages. | ||
|
|
||
| Returns: | ||
| str: A multi-line formatted string with full context details. | ||
| """ | ||
| lines = [] | ||
|
|
||
| if self.attack_strategy_name: | ||
| lines.append(f"Attack: {self.attack_strategy_name}") | ||
|
|
||
| lines.append(f"Component: {self.component_role.value}") | ||
|
|
||
| if self.objective: | ||
| # Normalize to single line and truncate to 120 characters | ||
| objective_single_line = " ".join(self.objective.split()) | ||
| if len(objective_single_line) > 120: | ||
| objective_single_line = objective_single_line[:117] + "..." | ||
| lines.append(f"Objective: {objective_single_line}") | ||
|
|
||
| if self.objective_target_conversation_id: | ||
| lines.append(f"Objective target conversation ID: {self.objective_target_conversation_id}") | ||
|
|
||
| if self.attack_identifier: | ||
| lines.append(f"Attack identifier: {self.attack_identifier}") | ||
|
|
||
| if self.component_identifier: | ||
| lines.append(f"{self.component_role.value} identifier: {self.component_identifier}") | ||
|
|
||
| return "\n".join(lines) | ||
|
|
||
|
|
||
| # The contextvar that stores the current execution context | ||
| _execution_context: ContextVar[Optional[ExecutionContext]] = ContextVar("execution_context", default=None) | ||
|
|
||
|
|
||
| def get_execution_context() -> Optional[ExecutionContext]: | ||
| """ | ||
| Get the current execution context. | ||
|
|
||
| Returns: | ||
| Optional[ExecutionContext]: The current context, or None if not set. | ||
| """ | ||
| return _execution_context.get() | ||
|
|
||
|
|
||
| def set_execution_context(context: ExecutionContext) -> None: | ||
| """ | ||
| Set the current execution context. | ||
|
|
||
| Args: | ||
| context: The execution context to set. | ||
| """ | ||
| _execution_context.set(context) | ||
|
|
||
|
|
||
| def clear_execution_context() -> None: | ||
| """Clear the current execution context.""" | ||
| _execution_context.set(None) | ||
|
|
||
|
|
||
| @dataclass | ||
| class ExecutionContextManager: | ||
| """ | ||
| A context manager for setting execution context during component operations. | ||
|
|
||
| This class provides a convenient way to set and automatically clear | ||
| execution context when entering and exiting a code block. | ||
|
|
||
| On successful exit, the context is restored to its previous value. | ||
| On exception, the context is preserved so exception handlers can access it. | ||
| """ | ||
|
|
||
| context: ExecutionContext | ||
| _token: Any = field(default=None, init=False, repr=False) | ||
|
|
||
| def __enter__(self) -> "ExecutionContextManager": | ||
| """Set the execution context on entry.""" | ||
| self._token = _execution_context.set(self.context) | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: | ||
| """ | ||
| Restore the previous context on exit. | ||
|
|
||
| If an exception occurred, the context is preserved so that exception | ||
| handlers higher in the call stack can access it for enhanced error messages. | ||
| """ | ||
| if exc_type is None: | ||
| # No exception - restore previous context | ||
| _execution_context.reset(self._token) | ||
| # On exception, leave context in place for exception handlers to read | ||
|
|
||
|
|
||
| def execution_context( | ||
| *, | ||
| component_role: ComponentRole, | ||
| attack_strategy_name: Optional[str] = None, | ||
| attack_identifier: Optional[Dict[str, Any]] = None, | ||
| component_identifier: Optional[Dict[str, Any]] = None, | ||
| objective_target_conversation_id: Optional[str] = None, | ||
| objective: Optional[str] = None, | ||
| ) -> ExecutionContextManager: | ||
| """ | ||
| Create an execution context manager with the specified parameters. | ||
|
|
||
| Args: | ||
| component_role: The role of the component being executed. | ||
| attack_strategy_name: The name of the attack strategy class. | ||
| attack_identifier: The identifier from attack.get_identifier(). | ||
| component_identifier: The identifier from component.get_identifier(). | ||
| objective_target_conversation_id: The objective target conversation ID if available. | ||
| objective: The attack objective if available. | ||
|
|
||
| Returns: | ||
| ExecutionContextManager: A context manager that sets/clears the context. | ||
| """ | ||
| # Extract endpoint and component_name from component_identifier if available | ||
| endpoint = None | ||
| component_name = None | ||
| if component_identifier: | ||
| endpoint = component_identifier.get("endpoint") | ||
| component_name = component_identifier.get("__type__") | ||
|
|
||
| context = ExecutionContext( | ||
| component_role=component_role, | ||
| attack_strategy_name=attack_strategy_name, | ||
| attack_identifier=attack_identifier, | ||
| component_identifier=component_identifier, | ||
| objective_target_conversation_id=objective_target_conversation_id, | ||
| endpoint=endpoint, | ||
| component_name=component_name, | ||
| objective=objective, | ||
| ) | ||
| return ExecutionContextManager(context=context) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.