mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
context on ignored messaged
This commit is contained in:
parent
7fe6544667
commit
f22ef6c627
7 changed files with 292 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
46
experiments/SUMMARY_negotiation_improvements.md
Normal file
46
experiments/SUMMARY_negotiation_improvements.md
Normal 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.
|
||||
63
experiments/improve_negotiation_awareness.md
Normal file
63
experiments/improve_negotiation_awareness.md
Normal 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
68
test_ignored_messages.py
Executable 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue