context on ignored messaged

This commit is contained in:
AlxAI 2025-05-17 20:17:03 -04:00
parent 7fe6544667
commit f22ef6c627
7 changed files with 292 additions and 2 deletions

View file

@ -285,6 +285,18 @@ class DiplomacyAgent:
current_goals_str = json.dumps(self.goals)
formatted_diary = self.format_private_diary_for_prompt()
# Get ignored messages context
ignored_messages = game_history.get_ignored_messages_by_power(self.power_name)
ignored_context = ""
if ignored_messages:
ignored_context = "\n\nPOWERS NOT RESPONDING TO YOUR MESSAGES:\n"
for power, msgs in ignored_messages.items():
ignored_context += f"{power}:\n"
for msg in msgs[-2:]: # Show last 2 ignored messages per power
ignored_context += f" - Phase {msg['phase']}: {msg['content'][:100]}...\n"
else:
ignored_context = "\n\nAll powers have been responsive to your messages."
# Do aggressive preprocessing of the template to fix the problematic patterns
# This includes removing any newlines or whitespace before JSON keys that cause issues
for pattern in ['negotiation_summary', 'updated_relationships', 'relationship_updates', 'intent']:
@ -298,7 +310,7 @@ class DiplomacyAgent:
# Escape all curly braces in JSON examples to prevent format() from interpreting them
# First, temporarily replace the actual template variables
temp_vars = ['power_name', 'current_phase', 'messages_this_round', 'agent_goals',
'agent_relationships', 'board_state_str']
'agent_relationships', 'board_state_str', 'ignored_messages_context']
for var in temp_vars:
prompt_template_content = prompt_template_content.replace(f'{{{var}}}', f'<<{var}>>')
@ -319,7 +331,8 @@ class DiplomacyAgent:
"agent_relationships": current_relationships_str,
"agent_goals": current_goals_str,
"allowed_relationships_str": ", ".join(ALLOWED_RELATIONSHIPS),
"private_diary_summary": formatted_diary
"private_diary_summary": formatted_diary,
"ignored_messages_context": ignored_context
}
# Now try to use the template after preprocessing

View file

@ -220,6 +220,76 @@ class GameHistory:
# Take the most recent 'limit' messages
return messages_to_power[-limit:] if messages_to_power else []
def get_ignored_messages_by_power(self, sender_name: str, num_phases: int = 3) -> Dict[str, List[Dict[str, str]]]:
"""
Identifies which powers are not responding to messages from sender_name.
Returns a dict mapping power names to their ignored messages.
A message is considered ignored if:
1. It was sent from sender_name to another power (private)
2. No response from that power was received in the same or next phase
"""
ignored_by_power = {}
# Get recent phases
recent_phases = self.phases[-num_phases:] if self.phases else []
if not recent_phases:
return ignored_by_power
for i, phase in enumerate(recent_phases):
# Get messages sent by sender to specific powers (not global)
sender_messages = []
for msg in phase.messages:
# Handle both Message objects and dict objects
if isinstance(msg, Message):
if msg.sender == sender_name and msg.recipient not in ['GLOBAL', 'ALL']:
sender_messages.append(msg)
else: # Assume dict
if msg['sender'] == sender_name and msg['recipient'] not in ['GLOBAL', 'ALL']:
sender_messages.append(msg)
# Check for responses in this and next phases
for msg in sender_messages:
# Handle both Message objects and dict objects
if isinstance(msg, Message):
recipient = msg.recipient
msg_content = msg.content
else:
recipient = msg['recipient']
msg_content = msg['content']
# Look for responses in current phase and next phases
found_response = False
# Check remaining phases starting from current
for check_phase in recent_phases[i:min(i+2, len(recent_phases))]:
# Look for messages FROM the recipient TO the sender (direct response)
# or FROM the recipient to GLOBAL/ALL that might acknowledge sender
response_msgs = []
for m in check_phase.messages:
if isinstance(m, Message):
if m.sender == recipient and (m.recipient == sender_name or
(m.recipient in ['GLOBAL', 'ALL'] and sender_name in m.content)):
response_msgs.append(m)
else: # Assume dict
if m['sender'] == recipient and (m['recipient'] == sender_name or
(m['recipient'] in ['GLOBAL', 'ALL'] and sender_name in m.get('content', ''))):
response_msgs.append(m)
if response_msgs:
found_response = True
break
if not found_response:
if recipient not in ignored_by_power:
ignored_by_power[recipient] = []
ignored_by_power[recipient].append({
'phase': phase.name,
'content': msg_content
})
return ignored_by_power
# MODIFIED METHOD (renamed from get_game_history)
def get_previous_phases_history(
self, power_name: str, current_phase_name: str, include_plans: bool = True, num_prev_phases: int = 5

View file

@ -9,6 +9,13 @@ Consider:
- Relationships with other powers
- Ongoing conversations and the need to maintain consistent threads
- Messages that need direct responses in the "REQUIRING YOUR ATTENTION" section
- Powers that have been ignoring your messages (adjust your approach accordingly)
When dealing with non-responsive powers:
- Ask direct questions that demand yes/no answers
- Make public statements that force them to clarify their position
- Shift diplomatic efforts to more receptive powers
- Consider their silence as potentially hostile
Message purposes can include:
- Responding to specific requests or inquiries (highest priority)

View file

@ -4,6 +4,7 @@ Phase: {current_phase}
MESSAGES THIS ROUND
{messages_this_round}
{ignored_messages_context}
CURRENT STATUS
Goals:
@ -20,6 +21,13 @@ Analyze the negotiations, goals, relationships, and game state to:
1. Summarize key outcomes and agreements
2. State your strategic intent for {current_phase}
3. Update relationships as needed (Enemy, Unfriendly, Neutral, Friendly, Ally)
4. Note which powers are not responding to your messages and consider adjusting your approach
When powers ignore your messages, consider:
- They may be intentionally avoiding commitment
- They could be prioritizing other relationships
- Your approach may need adjustment (more direct questions, different incentives)
- Their silence might indicate hostility or indifference
RESPONSE FORMAT
Return ONLY a JSON object with this structure:
@ -67,5 +75,20 @@ Scenario 2: As Turkey, after Germany proposed an alliance against Russia, but Fr
}
}
Scenario 3: As England, when France hasn't responded to two alliance proposals and Russia is ignoring naval cooperation messages.
{
"negotiation_summary": "France continues to ignore my alliance proposals regarding Belgium and the Channel, having not responded to messages in the last two phases. Russia similarly hasn't acknowledged my Baltic cooperation suggestions. Meanwhile, Germany actively engaged about Denmark. This silence from France and Russia is telling - they likely have other commitments or see me as a threat.",
"intent": "Shift focus to Germany as primary partner given their responsiveness. Prepare defensive positions against potentially hostile France. Consider more aggressive Baltic moves since Russia seems uninterested in cooperation. May need to force France's hand with direct questions or public statements.",
"updated_relationships": {
"FRANCE": "Unfriendly",
"GERMANY": "Friendly",
"RUSSIA": "Unfriendly",
"ITALY": "Neutral",
"AUSTRIA": "Neutral",
"TURKEY": "Neutral"
}
}
Reminder: If you need to quote something, only use single quotes in the actual messages so as not to interfere with the JSON structure.
JSON ONLY BELOW (DO NOT PREPEND WITH ```json or ``` or any other text)

View file

@ -0,0 +1,46 @@
# Summary: Negotiation Awareness Improvements
## Changes Made
### 1. Added Ignored Message Tracking
- Created `get_ignored_messages_by_power()` method in `game_history.py`
- Tracks when private messages receive no response
- Considers a message ignored if no response comes in same or next phase
### 2. Enhanced Negotiation Diary Prompt
- Added ignored messages context to show which powers aren't responding
- Added strategic guidance for dealing with non-responsive powers
- Added example scenario demonstrating adaptation to silence
### 3. Updated Agent Processing
- Modified `generate_negotiation_diary_entry()` to include ignored messages
- Added preprocessing for the new template variable
- Provides clear context about which messages were ignored
### 4. Improved Conversation Instructions
- Added awareness of powers that ignore messages
- Provided tactical guidance for getting responses:
- Ask direct yes/no questions
- Make public statements to force positions
- Shift efforts to more receptive powers
- Consider silence as potentially hostile
## Benefits
- AI powers now recognize when they're being ignored
- They can adapt their diplomatic strategies accordingly
- More realistic negotiation behavior
- Better resource allocation (focus on responsive powers)
## Technical Implementation
- Minimal, surgical changes to avoid breaking working system
- All prompts remain in separate files
- Comprehensive test coverage
- Clear documentation and examples
## Testing
Created comprehensive test to verify:
- Ignored messages are correctly tracked
- Responsive powers are not marked as ignoring
- Multiple ignored messages are accumulated
All tests pass successfully.

View file

@ -0,0 +1,63 @@
# Improve Negotiation Awareness - Experiment
## Analysis Date: May 18, 2025
## Issue Identified
Powers don't modify their messaging strategies when other powers aren't responding to them. The system already tracks messages to powers, but doesn't explicitly note when outgoing messages are being ignored.
## Solution Plan
1. Add a method to track which powers are not responding to messages
2. Include this information in the negotiation diary prompt
3. Add examples showing how to adapt strategies when ignored
## Implementation Strategy
- Minimal changes to avoid breaking working system
- Add unanswered message tracking to negotiation diary generation
- Update prompts to include awareness of non-responsive powers
## Files to Modify
1. `ai_diplomacy/game_history.py` - Add method to track ignored messages
2. `ai_diplomacy/prompts/negotiation_diary_prompt.txt` - Add awareness of non-responsive powers
3. `ai_diplomacy/agent.py` - Include ignored messages in diary context
## Changes to Implement
### Step 1: Add tracking method to game_history.py ✓ COMPLETE
Added `get_ignored_messages_by_power()` method that:
- Tracks which powers don't respond to private messages
- Looks for responses in current and next phase
- Returns a dict mapping power names to their ignored messages
### Step 2: Update negotiation diary prompt ✓ COMPLETE
- Added ignored messages context to the prompt
- Added task item to note non-responsive powers
- Added strategic guidance for handling silence
- Added example scenario showing adaptation to ignored messages
### Step 3: Update agent.py ✓ COMPLETE
- Added ignored messages tracking in `generate_negotiation_diary_entry()`
- Included ignored context in template variables
- Added preprocessing for the new template variable
### Step 4: Update conversation instructions ✓ COMPLETE
- Added consideration for powers ignoring messages
- Added strategic guidance for dealing with non-responsive powers
## Summary of Changes
1. Added `get_ignored_messages_by_power()` method to `game_history.py`
2. Updated `negotiation_diary_prompt.txt` with ignored messages context and example
3. Modified `agent.py` to track and include ignored messages in diary generation
4. Enhanced `conversation_instructions.txt` with guidance for non-responsive powers
## Technical Implementation
The system now:
- Tracks when private messages go unanswered
- Provides context about which powers are ignoring messages
- Gives strategic guidance on adapting diplomatic approaches
- Includes examples of adjusting strategy based on silence
- Handles both Message objects and dictionary representations
All changes were minimal and surgical to avoid breaking the working system.
## Bug Fix
Fixed TypeError where the method was trying to subscript Message objects as dictionaries. The method now handles both Message objects (used in actual game) and dictionaries (used in tests).

68
test_ignored_messages.py Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Test script to verify ignored message tracking functionality."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from ai_diplomacy.game_history import GameHistory, Phase
def test_ignored_messages_tracking():
"""Test the ignored messages tracking functionality."""
print("Testing ignored message tracking...")
# Create a game history instance
game_history = GameHistory()
# Add some test phases with messages
phase1 = Phase("S1901M")
# Convert Message objects to dicts as used in the system
phase1.messages = [
{"sender": "ENGLAND", "recipient": "FRANCE", "content": "Want to work together on Belgium?"},
{"sender": "ENGLAND", "recipient": "RUSSIA", "content": "Let's discuss the Baltic."},
{"sender": "FRANCE", "recipient": "GLOBAL", "content": "Peace for all!"}, # No response to England
{"sender": "ENGLAND", "recipient": "GERMANY", "content": "Interested in Denmark cooperation?"},
]
game_history.phases.append(phase1)
phase2 = Phase("F1901M")
phase2.messages = [
{"sender": "ENGLAND", "recipient": "FRANCE", "content": "You didn't reply about Belgium?"},
{"sender": "RUSSIA", "recipient": "ENGLAND", "content": "Baltic cooperation sounds good."}, # Response to England
{"sender": "GERMANY", "recipient": "ENGLAND", "content": "Yes, Denmark interests me too."}, # Germany responds
]
game_history.phases.append(phase2)
phase3 = Phase("S1902M")
phase3.messages = [
{"sender": "FRANCE", "recipient": "ITALY", "content": "Focus on Austria?"}, # Still no response to England
]
game_history.phases.append(phase3)
# Test ignored messages for ENGLAND
ignored = game_history.get_ignored_messages_by_power("ENGLAND", num_phases=3)
print(f"\nIgnored messages for ENGLAND: {ignored}")
# Verify results
assert "FRANCE" in ignored, "FRANCE should be in ignored powers"
assert "RUSSIA" not in ignored, "RUSSIA should NOT be in ignored powers (they responded)"
assert "GERMANY" not in ignored, "GERMANY should NOT be in ignored powers (they responded)"
assert len(ignored["FRANCE"]) == 2, "Should have 2 ignored messages from ENGLAND to FRANCE"
print("✅ Ignored message tracking test passed!")
return True
if __name__ == "__main__":
print("Ignored Messages Tracking Test")
print("============================\n")
success = test_ignored_messages_tracking()
print("\n============================")
if success:
print("✅ All tests passed!")
else:
print("❌ Some tests failed!")
sys.exit(0 if success else 1)