Source code for pyrit.message_normalizer.conversation_context_normalizer

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from typing import List

from pyrit.message_normalizer.message_normalizer import MessageStringNormalizer
from pyrit.models import Message, MessagePiece


[docs] class ConversationContextNormalizer(MessageStringNormalizer): """ Normalizer that formats conversation history as turn-based text. This is the standard format used by attacks like Crescendo and TAP for including conversation context in adversarial chat prompts. The output format is: Turn 1: User: <content> Assistant: <content> Turn 2: User: <content> ... """
[docs] async def normalize_string_async(self, messages: List[Message]) -> str: """ Normalize a list of messages into a turn-based context string. Args: messages: The list of Message objects to normalize. Returns: A formatted string with turn numbers and role prefixes. Raises: ValueError: If the messages list is empty. """ if not messages: raise ValueError("Messages list cannot be empty") context_parts: List[str] = [] turn_number = 0 for message in messages: for piece in message.message_pieces: # Skip system messages in context formatting if piece.role == "system": continue # Start a new turn when we see a user message if piece.role == "user": turn_number += 1 context_parts.append(f"Turn {turn_number}:") # Format the piece content content = self._format_piece_content(piece) role_label = "User" if piece.role == "user" else "Assistant" context_parts.append(f"{role_label}: {content}") return "\n".join(context_parts)
def _format_piece_content(self, piece: MessagePiece) -> str: """ Format a single message piece into a content string. For text pieces, shows original and converted values (if different). For non-text pieces, uses context_description metadata or a placeholder. Args: piece: The message piece to format. Returns: The formatted content string. """ data_type = piece.converted_value_data_type or piece.original_value_data_type # For non-text pieces, use metadata description or placeholder if data_type != "text": if piece.prompt_metadata and "context_description" in piece.prompt_metadata: description = piece.prompt_metadata["context_description"] return f"[{data_type.capitalize()} - {description}]" else: return f"[{data_type.capitalize()}]" # For text pieces, include both original and converted if different original = piece.original_value converted = piece.converted_value if original != converted: return f"{converted} (original: {original})" else: return converted