Interaction Generation
This guide covers the multi-turn dialogue generation system in PersonaGym.
Overview
The interaction generation stage simulates realistic user-AI conversations:
User sends initial query (optionally with noise)
Assistant responds based on conversation context
User provides feedback or follow-up
Loop continues until satisfaction or max turns
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ InteractionGenerator │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ AssistantModel│ │UserFeedback │ │ Distractor │ │
│ │ │ │ Model │ │ (optional) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Interaction │ │
│ │ - Messages (user/assistant turns) │ │
│ │ - Noisy versions (if distractor enabled) │ │
│ │ - Metadata (model info, timestamps) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Configuration
interaction_generation:
min_turns: 2 # Minimum conversation turns
max_turns: 5 # Maximum conversation turns
max_workers: 20 # Parallel workers for batch generation
max_retries: 3 # Retries per failed interaction
retry_delay: 2 # Seconds between retries
max_supplement_rounds: 3 # Extra rounds for failed personas
# Assistant model
assistant_model:
provider: "openai"
model: "gpt-4o-mini"
temperature: 0.7
max_completion_tokens: 1024
# User feedback model
user_model:
provider: "openai"
model: "gpt-4o-mini"
temperature: 0.8
max_completion_tokens: 256
system_prompt_template: "prompts/user_feedback.txt"
feedback_probability: 0.6
Conversation Flow
1. Initial Query
# User query (potentially with style transfer and noise)
initial_query = "hey can u help me write some python code"
# Becomes first message
messages = [
Message(
role='user',
content=initial_query,
metadata={'is_initial': True, 'was_noised': True}
)
]
2. Assistant Response
# Assistant responds using model pool
response = assistant_model.respond(
user_query=initial_query,
conversation_history=[]
)
messages.append(
Message(
role='assistant',
content=response,
metadata={'model': 'gpt-4o-mini', 'provider': 'openai'}
)
)
3. User Feedback
# User decides to continue or end
feedback = user_model.generate_feedback(
persona_features=features,
conversation_history=messages,
assistant_response=response,
current_turn=1,
target_turns=3
)
if feedback:
# User has follow-up
messages.append(Message(role='user', content=feedback))
else:
# User is satisfied, end conversation
pass
Model Pool
Weighted Model Selection
interaction_generation:
assistant_model:
model_pool:
- provider: openai
model: gpt-4o-mini
weight: 0.2
- provider: openai
model: gpt-4o
weight: 0.2
- provider: openrouter
model: anthropic/claude-3.5-haiku
weight: 0.3
- provider: openrouter
model: google/gemini-2.0-flash-exp
weight: 0.3
Model Locking
The same model is used throughout a conversation:
class AssistantModel:
def lock_model(self):
"""Lock model for current conversation (thread-safe)."""
chosen = random.choices(self.model_pool, weights=weights)[0]
self._thread_local.locked_client = chosen['client']
def unlock_model(self):
"""Release model lock after conversation."""
self._thread_local.locked_client = None
Interaction Data Structure
@dataclass
class Interaction:
interaction_id: str # Unique identifier
persona_id: str # Associated persona
persona_features: Dict # Persona features
original_query: str # Query before style transfer
initial_query: str # Query after style transfer
messages: List[Message] # Conversation messages
num_turns: int # Number of turns
metadata: Dict # Additional metadata
Output Format
{
"interaction_id": "interaction_persona_001_20260206_103000",
"persona_id": "persona_001",
"persona_features": {
"role": "engineer",
"communication_style": "casual"
},
"original_query": "Help me write Python code",
"initial_query": "hey can u help me write some python code",
"messages": [
{
"role": "user",
"content": "hey can u help me write some python code",
"timestamp": "2026-02-06T10:30:00",
"metadata": {
"is_initial": true,
"was_noised": false
}
},
{
"role": "assistant",
"content": "Sure! What kind of Python code would you like help with?",
"timestamp": "2026-02-06T10:30:05",
"metadata": {
"model": "gpt-4o-mini",
"provider": "openai"
}
},
{
"role": "user",
"content": "a script to process csv files",
"timestamp": "2026-02-06T10:30:30",
"metadata": {
"is_followup": true
}
},
{
"role": "assistant",
"content": "Here's a Python script to process CSV files:\n```python\nimport pandas as pd\n...",
"timestamp": "2026-02-06T10:30:45"
}
],
"num_turns": 2,
"metadata": {
"created_at": "2026-02-06T10:30:00",
"distractor_applied": true,
"assistant_model": {
"model": "gpt-4o-mini",
"provider": "openai"
}
}
}
Programmatic Usage
Single Interaction
from src.interaction_generator import InteractionGenerator
generator = InteractionGenerator(config, distractor=distractor)
interaction = generator.generate_interaction(
persona_id="persona_001",
persona_features={'role': 'engineer', 'style': 'casual'},
initial_query="help me write python code",
original_query="Help me write Python code",
target_turns=3
)
print(f"Generated {interaction.num_turns} turns")
Batch Generation
# Prepare persona-query pairs
personas_with_queries = [
{
'persona_id': 'persona_001',
'persona_features': features1,
'queries': [
{'adapted_query': 'query1', 'original_query': 'Query 1'},
{'adapted_query': 'query2', 'original_query': 'Query 2'}
]
},
...
]
# Generate with parallel workers
interactions = generator.generate_interactions_batch(
personas_with_queries,
show_progress=True,
max_workers=10,
storage=interaction_storage # Optional: save immediately
)
print(f"Generated {len(interactions)} interactions")
Interaction Storage
Incremental Saving
from src.interaction_generator import InteractionStorage
storage = InteractionStorage("output/interactions", collector=training_collector)
# Interactions are saved immediately after generation
for interaction in interactions:
filepath = storage.save(interaction)
print(f"Saved: {filepath}")
Index File
output/interactions/index.json:
{
"interactions": [
{
"interaction_id": "interaction_persona_001_...",
"persona_id": "persona_001",
"filepath": "output/interactions/interaction_persona_001_....json",
"num_turns": 3,
"created_at": "2026-02-06T10:30:00",
"query_id": "query_001"
}
],
"count": 100,
"last_updated": "2026-02-06T12:00:00"
}
Distractor Integration
Real-time Noise Application
Noise is applied during interaction generation:
generator = InteractionGenerator(config, distractor=semantic_distractor)
# During generation:
# 1. Apply noise to initial query
noisy_query = distractor.apply_noise(initial_query, persona_features)
# 2. Apply noise to follow-up feedback
noisy_feedback = distractor.apply_noise(feedback, persona_features)
Metadata Tracking
{
"metadata": {
"distractor_applied": true,
"distractor_type": "semantic",
"noisy_versions": {
"initial_query": {
"clean_query": "Help me write code",
"noisy_versions": [
{
"noisy_text": "help me writ code plz",
"noise_type": "surface_noise",
"applied_strategies": ["typo_misspelling", "colloquial_speech"]
}
]
}
}
}
}
Error Handling
Retry Logic
def _generate_with_retry(self, task, max_retries=3, retry_delay=2):
for attempt in range(max_retries + 1):
try:
interaction = self._generate_single_interaction(task)
if interaction is not None:
return interaction
except Exception as e:
if attempt < max_retries:
time.sleep(retry_delay)
else:
logging.error(f"All attempts failed: {e}")
return None
Supplement Rounds
If some interactions fail, new queries are generated:
# After initial round
for persona, needed in deficient_personas:
# Generate new queries
new_queries = query_generator.generate_queries_for_persona(
persona['features'],
num_queries=needed
)
# Retry with new queries
Concurrent Execution
Thread Safety
class AssistantModel:
def __init__(self):
self._thread_local = threading.local()
def lock_model(self):
# Thread-local storage ensures thread safety
self._thread_local.locked_client = selected_client
Worker Configuration
interaction_generation:
max_workers: 20 # Parallel workers
# Reduce for rate-limited APIs
max_workers: 5
API Reference
InteractionGenerator
class InteractionGenerator:
"""Generates multi-turn interactions."""
def __init__(self, config: Dict, distractor: Optional[DistractorModel] = None):
"""Initialize with config and optional distractor."""
def generate_interaction(
self,
persona_id: str,
persona_features: Dict,
initial_query: str,
system_prompt: Optional[str] = None,
original_query: Optional[str] = None,
target_turns: int = 3,
query_id: Optional[str] = None
) -> Optional[Interaction]:
"""Generate a single interaction."""
def generate_interactions_batch(
self,
personas_with_queries: List[Dict],
show_progress: bool = True,
max_workers: int = 5,
storage: Optional[InteractionStorage] = None
) -> List[Interaction]:
"""Generate interactions for multiple personas."""
AssistantModel
class AssistantModel:
"""Simulates AI assistant responses."""
def respond(self, user_query: str, conversation_history: List[Message]) -> str:
"""Generate response to user query."""
def lock_model(self) -> None:
"""Lock model for current conversation."""
def unlock_model(self) -> None:
"""Release model lock."""
UserFeedbackModel
class UserFeedbackModel:
"""Simulates user feedback based on persona."""
def generate_feedback(
self,
persona_features: Dict,
conversation_history: List[Message],
assistant_response: str,
current_turn: int,
target_turns: int
) -> Optional[str]:
"""Generate user feedback or None if satisfied."""
Best Practices
1. Configure Appropriate Turn Limits
interaction_generation:
min_turns: 2 # At least 2 turns for meaningful data
max_turns: 5 # Cap to avoid infinite loops
2. Use Model Pool for Diversity
Different models produce different response styles.
3. Enable Incremental Storage
storage = InteractionStorage(output_dir, collector=collector)
generator.generate_interactions_batch(..., storage=storage)
4. Monitor Success Rate
success_rate = len(interactions) / total_tasks * 100
print(f"Success rate: {success_rate:.1f}%")
See Also
Distractor System - Noise injection details
Training Data - Converting interactions to training samples
Interaction API - Detailed API documentation