44import logging
55import uuid
66from dataclasses import dataclass , field
7- from typing import Dict , List , Optional
7+ from typing import Dict , List , Optional , Sequence
88
99from pyrit .memory import CentralMemory
1010from pyrit .models import ChatMessageRole , Message , MessagePiece , Score
1818logger = logging .getLogger (__name__ )
1919
2020
21+ def mark_messages_as_simulated (messages : Sequence [Message ]) -> List [Message ]:
22+ """
23+ Mark assistant messages as simulated_assistant for traceability.
24+
25+ This function converts all assistant roles to simulated_assistant in the
26+ provided messages. This is useful when loading conversations from YAML files
27+ or other sources where the responses are not from actual targets.
28+
29+ Args:
30+ messages (Sequence[Message]): The messages to mark as simulated.
31+
32+ Returns:
33+ List[Message]: The same messages with assistant roles converted to simulated_assistant.
34+ Modifies the messages in place and also returns them for convenience.
35+ """
36+ result = list (messages )
37+ for message in result :
38+ for piece in message .message_pieces :
39+ if piece ._role == "assistant" :
40+ piece ._role = "simulated_assistant"
41+ return result
42+
43+
2144def format_conversation_context (messages : List [Message ]) -> str :
2245 """
2346 Format a list of messages into a context string for adversarial chat system prompts.
@@ -55,17 +78,22 @@ def format_conversation_context(messages: List[Message]) -> str:
5578 piece = message .get_piece ()
5679
5780 # Skip system messages - they're handled separately
58- if piece .role == "system" :
81+ if piece .api_role == "system" :
5982 continue
6083
6184 # Start a new turn when we see a user message
62- if piece .role == "user" :
85+ if piece .api_role == "user" :
6386 turn_number += 1
6487 context_parts .append (f"Turn { turn_number } :" )
6588
6689 # Format the piece content
6790 content = _format_piece_content (piece )
68- role_label = "User" if piece .role == "user" else "Assistant"
91+ if piece .api_role == "user" :
92+ role_label = "User"
93+ elif piece .is_simulated :
94+ role_label = "Assistant (simulated)"
95+ else :
96+ role_label = "Assistant"
6997 context_parts .append (f"{ role_label } : { content } " )
7098
7199 return "\n " .join (context_parts )
@@ -175,7 +203,7 @@ def get_last_message(
175203 if role :
176204 for m in reversed (conversation ):
177205 piece = m .get_piece ()
178- if piece .role == role :
206+ if piece .api_role == role :
179207 return piece
180208 return None
181209
@@ -288,7 +316,7 @@ async def update_conversation_state_async(
288316 # Determine if we should exclude the last message (if it's a user message in multi-turn context)
289317 last_message = valid_requests [- 1 ].message_pieces [0 ]
290318 is_multi_turn = max_turns is not None
291- should_exclude_last = is_multi_turn and last_message .role == "user"
319+ should_exclude_last = is_multi_turn and last_message .api_role == "user"
292320
293321 # Process all messages except potentially the last one
294322 for i , request in enumerate (valid_requests ):
@@ -351,9 +379,9 @@ async def _apply_role_specific_converters_async(
351379 for piece in request .message_pieces :
352380 applicable_converters : Optional [List [PromptConverterConfiguration ]] = None
353381
354- if piece .role == "user" and request_converters :
382+ if piece .api_role == "user" and request_converters :
355383 applicable_converters = request_converters
356- elif piece .role == "assistant" and response_converters :
384+ elif piece .api_role == "assistant" and response_converters :
357385 applicable_converters = response_converters
358386 # System messages get no converters (applicable_converters remains None)
359387
@@ -429,7 +457,7 @@ def _process_piece(
429457 is_multi_turn = max_turns is not None
430458
431459 # Only assistant messages count as turns
432- if piece .role == "assistant" and is_multi_turn :
460+ if piece .api_role == "assistant" and is_multi_turn :
433461 conversation_state .turn_count += 1
434462
435463 if conversation_state .turn_count > max_turns :
@@ -466,11 +494,11 @@ async def _populate_conversation_state_async(
466494 return # Nothing to extract from empty history
467495
468496 # Extract the last user message and assistant message scores from the last message
469- if last_message .role == "user" :
497+ if last_message .api_role == "user" :
470498 conversation_state .last_user_message = last_message .converted_value
471499 logger .debug (f"Extracted last user message: { conversation_state .last_user_message [:50 ]} ..." )
472500
473- elif last_message .role == "assistant" :
501+ elif last_message .api_role == "assistant" :
474502 # Get scores for the last assistant message based off of the original id
475503 conversation_state .last_assistant_message_scores = list (
476504 self ._memory .get_prompt_scores (prompt_ids = [str (last_message .original_prompt_id )])
@@ -482,7 +510,7 @@ async def _populate_conversation_state_async(
482510 return
483511
484512 # Check assumption that there will be a user message preceding the assistant message
485- if len (prepended_conversation ) > 1 and prepended_conversation [- 2 ].get_piece ().role == "user" :
513+ if len (prepended_conversation ) > 1 and prepended_conversation [- 2 ].get_piece ().api_role == "user" :
486514 conversation_state .last_user_message = prepended_conversation [- 2 ].get_value ()
487515 logger .debug (f"Extracted preceding user message: { conversation_state .last_user_message [:50 ]} ..." )
488516 else :
@@ -533,11 +561,11 @@ async def prepend_to_adversarial_chat_async(
533561 for message in prepended_conversation :
534562 for piece in message .message_pieces :
535563 # Skip system messages - adversarial chat has its own system prompt
536- if piece .role == "system" :
564+ if piece .api_role == "system" :
537565 continue
538566
539567 # Create a new piece with swapped role for adversarial chat
540- swapped_role = role_swap .get (piece .role , piece .role )
568+ swapped_role = role_swap .get (piece .api_role , piece .api_role )
541569
542570 adversarial_piece = MessagePiece (
543571 id = uuid .uuid4 (),
0 commit comments