first attempt at two part orders, unformatted -> formatted

This commit is contained in:
AlxAI 2025-06-29 23:05:48 +02:00
parent b5a84867a1
commit 6e4821735d
32 changed files with 2510 additions and 801 deletions

View file

@ -14,6 +14,7 @@ from .utils import load_prompt, run_llm_and_log, log_llm_response
from .prompt_constructor import build_context_prompt # Added import
from .clients import GameHistory
from diplomacy import Game
from .formatter import format_with_gemini_flash, FORMAT_ORDER_DIARY, FORMAT_NEGOTIATION_DIARY, FORMAT_STATE_UPDATE
logger = logging.getLogger(__name__)
@ -425,16 +426,19 @@ class DiplomacyAgent:
success_status = "Failure: Initialized" # Default
try:
# Load the template file but safely preprocess it first
prompt_template_content = _load_prompt_file('negotiation_diary_prompt.txt', prompts_dir=self.prompts_dir)
# Load the unformatted template file
prompt_template_content = _load_prompt_file('unformatted/negotiation_diary_prompt.txt', prompts_dir=self.prompts_dir)
if not prompt_template_content:
logger.error(f"[{self.power_name}] Could not load negotiation_diary_prompt.txt. Skipping diary entry.")
logger.error(f"[{self.power_name}] Could not load unformatted/negotiation_diary_prompt.txt. Skipping diary entry.")
success_status = "Failure: Prompt file not loaded"
return # Exit early if prompt can't be loaded
# Prepare context for the prompt
board_state_dict = game.get_state()
board_state_str = f"Units: {board_state_dict.get('units', {})}, Centers: {board_state_dict.get('centers', {})}"
# Create readable board state string
units_str = "\n".join([f" {p}: {', '.join(u)}" for p, u in board_state_dict.get('units', {}).items()])
centers_str = "\n".join([f" {p}: {', '.join(c)}" for p, c in board_state_dict.get('centers', {}).items()])
board_state_str = f"Units:\n{units_str}\n\nSupply Centers:\n{centers_str}"
messages_this_round = game_history.get_messages_this_round(
power_name=self.power_name,
@ -526,7 +530,15 @@ class DiplomacyAgent:
parsed_data = None
try:
parsed_data = self._extract_json_from_text(raw_response)
# Format the natural language response into JSON
formatted_response = await format_with_gemini_flash(
raw_response,
FORMAT_NEGOTIATION_DIARY,
power_name=self.power_name,
phase=game.current_short_phase,
log_file_path=log_file_path
)
parsed_data = self._extract_json_from_text(formatted_response)
logger.debug(f"[{self.power_name}] Parsed diary data: {parsed_data}")
success_status = "Success: Parsed diary data"
except json.JSONDecodeError as e:
@ -560,7 +572,7 @@ class DiplomacyAgent:
# Fix 2: Be more robust about extracting relationship updates
new_relationships = None
for key in ['relationship_updates', 'updated_relationships', 'relationships']:
for key in ['current_relationships', 'relationship_updates', 'updated_relationships', 'relationships']:
if key in parsed_data and isinstance(parsed_data[key], dict):
new_relationships = parsed_data[key]
logger.info(f"[{self.power_name}] Successfully extracted '{key}' for relationship updates.")
@ -590,7 +602,7 @@ class DiplomacyAgent:
if success_status == "Success: Parsed diary data": # If only parsing was successful before
success_status = "Success: Parsed, no valid relationship updates"
elif new_relationships is not None: # It was provided but not a dict
logger.warning(f"[{self.power_name}] 'updated_relationships' from diary LLM was not a dictionary: {type(new_relationships)}")
logger.warning(f"[{self.power_name}] 'current_relationships' from diary LLM was not a dictionary: {type(new_relationships)}")
# Add the generated (or fallback) diary entry
self.add_diary_entry(diary_entry_text, game.current_short_phase)
@ -626,14 +638,17 @@ class DiplomacyAgent:
"""
logger.info(f"[{self.power_name}] Generating order diary entry for {game.current_short_phase}...")
# Load the template but we'll use it carefully with string interpolation
prompt_template = _load_prompt_file('order_diary_prompt.txt', prompts_dir=self.prompts_dir)
# Load the unformatted template for better content generation
prompt_template = _load_prompt_file('unformatted/order_diary_prompt.txt', prompts_dir=self.prompts_dir)
if not prompt_template:
logger.error(f"[{self.power_name}] Could not load order_diary_prompt.txt. Skipping diary entry.")
logger.error(f"[{self.power_name}] Could not load unformatted/order_diary_prompt.txt. Skipping diary entry.")
return
board_state_dict = game.get_state()
board_state_str = f"Units: {board_state_dict.get('units', {})}, Centers: {board_state_dict.get('centers', {})}"
# Create readable board state string
units_str = "\n".join([f" {p}: {', '.join(u)}" for p, u in board_state_dict.get('units', {}).items()])
centers_str = "\n".join([f" {p}: {', '.join(c)}" for p, c in board_state_dict.get('centers', {}).items()])
board_state_str = f"Units:\n{units_str}\n\nSupply Centers:\n{centers_str}"
orders_list_str = "\n".join([f"- {o}" for o in orders]) if orders else "No orders submitted."
@ -700,7 +715,15 @@ class DiplomacyAgent:
if raw_response:
try:
response_data = self._extract_json_from_text(raw_response)
# Format the natural language response into JSON
formatted_response = await format_with_gemini_flash(
raw_response,
FORMAT_ORDER_DIARY,
power_name=self.power_name,
phase=game.current_short_phase,
log_file_path=log_file_path
)
response_data = self._extract_json_from_text(formatted_response)
if response_data:
# Directly attempt to get 'order_summary' as per the prompt
diary_text_candidate = response_data.get("order_summary")
@ -874,10 +897,10 @@ class DiplomacyAgent:
self.log_state(f"Before State Update ({current_phase})")
try:
# 1. Construct the prompt using the dedicated state update prompt file
prompt_template = _load_prompt_file('state_update_prompt.txt', prompts_dir=self.prompts_dir)
# 1. Construct the prompt using the unformatted state update prompt file
prompt_template = _load_prompt_file('unformatted/state_update_prompt.txt', prompts_dir=self.prompts_dir)
if not prompt_template:
logger.error(f"[{power_name}] Could not load state_update_prompt.txt. Skipping state update.")
logger.error(f"[{power_name}] Could not load unformatted/state_update_prompt.txt. Skipping state update.")
return
# Get previous phase safely from history
@ -916,12 +939,15 @@ class DiplomacyAgent:
other_powers = [p for p in game.powers if p != power_name]
# Create a readable board state string from the board_state dict
board_state_str = f"Board State:\n"
for p_name, power_data in board_state.get('powers', {}).items():
# Get units and centers from the board state
units = power_data.get('units', [])
centers = power_data.get('centers', [])
board_state_str += f" {p_name}: Units={units}, Centers={centers}\n"
board_state_str = "Units:\n"
for p_name, units in board_state.get('units', {}).items():
units_str = ", ".join(units) if units else "None"
board_state_str += f" {p_name}: {units_str}\n"
board_state_str += "\nSupply Centers:\n"
for p_name, centers in board_state.get('centers', {}).items():
centers_str = ", ".join(centers) if centers else "None"
board_state_str += f" {p_name}: {centers_str}\n"
# Extract year from the phase name (e.g., "S1901M" -> "1901")
current_year = last_phase_name[1:5] if len(last_phase_name) >= 5 else "unknown"
@ -956,7 +982,15 @@ class DiplomacyAgent:
if response is not None and response.strip(): # Check if response is not None and not just whitespace
try:
update_data = self._extract_json_from_text(response)
# Format the natural language response into JSON
formatted_response = await format_with_gemini_flash(
response,
FORMAT_STATE_UPDATE,
power_name=power_name,
phase=current_phase,
log_file_path=log_file_path
)
update_data = self._extract_json_from_text(formatted_response)
logger.debug(f"[{power_name}] Successfully parsed JSON: {update_data}")
# Ensure update_data is a dictionary

View file

@ -23,6 +23,7 @@ from .game_history import GameHistory
from .utils import load_prompt, run_llm_and_log, log_llm_response, generate_random_seed
# Import DiplomacyAgent for type hinting if needed, but avoid circular import if possible
from .prompt_constructor import construct_order_generation_prompt, build_context_prompt
from .formatter import format_with_gemini_flash, FORMAT_ORDERS, FORMAT_CONVERSATION
# set logger back to just info
logger = logging.getLogger("client")
@ -121,8 +122,17 @@ class BaseModelClient:
f"[{self.model_name}] Raw LLM response for {power_name} orders:\n{raw_response}"
)
# Attempt to parse the final "orders" from the LLM
move_list = self._extract_moves(raw_response, power_name)
# Format the natural language response into structured format
formatted_response = await format_with_gemini_flash(
raw_response,
FORMAT_ORDERS,
power_name=power_name,
phase=phase,
log_file_path=log_file_path
)
# Attempt to parse the final "orders" from the formatted response
move_list = self._extract_moves(formatted_response, power_name)
if not move_list:
logger.warning(
@ -454,18 +464,37 @@ class BaseModelClient:
agent_relationships: Optional[Dict[str, str]] = None,
agent_private_diary_str: Optional[str] = None, # Added
) -> str:
instructions = load_prompt("conversation_instructions.txt", prompts_dir=self.prompts_dir)
instructions = load_prompt("unformatted/conversation_instructions.txt", prompts_dir=self.prompts_dir)
context = build_context_prompt(
game,
board_state,
power_name,
possible_orders,
game_history,
agent_goals=agent_goals,
agent_relationships=agent_relationships,
agent_private_diary=agent_private_diary_str, # Pass diary string
prompts_dir=self.prompts_dir,
# Load conversation-specific context template
context_template = load_prompt("unformatted/conversation_context.txt", prompts_dir=self.prompts_dir)
# Get phase info
current_phase = game.get_current_phase()
# Get active powers
active_powers = [p for p in game.powers.keys() if not game.powers[p].is_eliminated()]
eliminated_powers = [p for p in game.powers.keys() if game.powers[p].is_eliminated()]
eliminated_status = f"Eliminated: {', '.join(eliminated_powers)}" if eliminated_powers else "No powers eliminated yet"
# Get messages this round
messages_this_round = game_history.get_messages_this_round(
power_name=power_name,
current_phase_name=game.current_short_phase
)
if not messages_this_round.strip():
messages_this_round = "(No messages exchanged yet this round)"
# Format the context
context = context_template.format(
power_name=power_name,
current_phase=current_phase,
agent_goals="\n".join(f"- {g}" for g in agent_goals) if agent_goals else "- Survive and expand",
agent_relationships="\n".join(f"- {p}: {r}" for p, r in agent_relationships.items()) if agent_relationships else "- All powers: Neutral",
recent_private_diary=agent_private_diary_str[-500:] if agent_private_diary_str else "(No recent diary entries)", # Last 500 chars
messages_this_round=messages_this_round,
active_powers=", ".join(active_powers),
eliminated_status=eliminated_status
)
# Get recent messages targeting this power to prioritize responses
@ -574,10 +603,34 @@ class BaseModelClient:
)
logger.debug(f"[{self.model_name}] Raw LLM response for {power_name}:\n{raw_response}")
# Format the natural language response into structured JSON
formatted_response = await format_with_gemini_flash(
raw_response,
FORMAT_CONVERSATION,
power_name=power_name,
phase=game_phase,
log_file_path=log_file_path
)
parsed_messages = []
json_blocks = []
json_decode_error_occurred = False
# For formatted response, we expect a clean JSON array
try:
data = json.loads(formatted_response)
if isinstance(data, list):
parsed_messages = data
json_blocks = [json.dumps(item) for item in data if isinstance(item, dict)]
else:
logger.warning(f"[{self.model_name}] Formatted response is not a list")
except json.JSONDecodeError:
logger.warning(f"[{self.model_name}] Failed to parse formatted response as JSON, falling back to regex")
# Fall back to original parsing logic using formatted_response
raw_response = formatted_response
# Original parsing logic as fallback
if not parsed_messages:
# Attempt to find blocks enclosed in {{...}}
double_brace_blocks = re.findall(r'\{\{(.*?)\}\}', raw_response, re.DOTALL)
if double_brace_blocks:
@ -602,68 +655,42 @@ class BaseModelClient:
# If no markdown block, fall back to regex for any JSON object in the response
json_blocks = re.findall(r'\{.*?\}', raw_response, re.DOTALL)
if not json_blocks:
logger.warning(f"[{self.model_name}] No JSON message blocks found in response for {power_name}. Raw response:\n{raw_response}")
success_status = "Success: No JSON blocks found"
# messages_to_return remains empty
else:
# Process json_blocks if we have them from fallback parsing
if not parsed_messages and json_blocks:
for block_index, block in enumerate(json_blocks):
try:
cleaned_block = block.strip()
# Attempt to fix common JSON issues like trailing commas before parsing
cleaned_block = re.sub(r',\s*([\}\]])', r'\1', cleaned_block)
parsed_message = json.loads(cleaned_block)
if isinstance(parsed_message, dict) and "message_type" in parsed_message and "content" in parsed_message:
# Further validation, e.g., recipient for private messages
if parsed_message["message_type"] == "private" and "recipient" not in parsed_message:
logger.warning(f"[{self.model_name}] Private message missing recipient for {power_name} in block {block_index}. Skipping: {cleaned_block}")
continue # Skip this message
parsed_messages.append(parsed_message)
else:
logger.warning(f"[{self.model_name}] Invalid message structure or missing keys in block {block_index} for {power_name}: {cleaned_block}")
except json.JSONDecodeError as jde:
# Try to fix unescaped newlines and retry parsing
try:
# Fix unescaped newlines and other control characters in JSON strings
def escape_json_string(match):
# Get the string content (without quotes)
string_content = match.group(1)
# Escape newlines, tabs, and carriage returns
string_content = string_content.replace('\n', '\\n')
string_content = string_content.replace('\r', '\\r')
string_content = string_content.replace('\t', '\\t')
# Return with quotes
return '"' + string_content + '"'
# Apply escaping to all string values in the JSON
fixed_block = re.sub(r'"([^"]*)"', escape_json_string, cleaned_block)
# Try parsing again with fixed block
parsed_message = json.loads(fixed_block)
if isinstance(parsed_message, dict) and "message_type" in parsed_message and "content" in parsed_message:
# Further validation, e.g., recipient for private messages
if parsed_message["message_type"] == "private" and "recipient" not in parsed_message:
logger.warning(f"[{self.model_name}] Private message missing recipient for {power_name} in block {block_index}. Skipping: {fixed_block}")
continue # Skip this message
parsed_messages.append(parsed_message)
logger.info(f"[{self.model_name}] Successfully parsed JSON block {block_index} for {power_name} after fixing escape sequences")
else:
logger.warning(f"[{self.model_name}] Invalid message structure or missing keys in block {block_index} for {power_name} after escape fix: {fixed_block}")
except json.JSONDecodeError as jde2:
except json.JSONDecodeError as e:
logger.warning(f"[{self.model_name}] Failed to parse JSON block {block_index} for {power_name}: {e}")
json_decode_error_occurred = True
logger.warning(f"[{self.model_name}] Failed to decode JSON block {block_index} for {power_name} even after escape fixes. Error: {jde}. Block content:\n{block}")
if not parsed_messages:
logger.warning(f"[{self.model_name}] No valid messages found in response for {power_name}")
success_status = "Success: No messages found"
# messages_to_return remains empty
else:
# Validate parsed messages
validated_messages = []
for msg in parsed_messages:
if isinstance(msg, dict) and "message_type" in msg and "content" in msg:
if msg["message_type"] == "private" and "recipient" not in msg:
logger.warning(f"[{self.model_name}] Private message missing recipient for {power_name}")
continue
validated_messages.append(msg)
else:
logger.warning(f"[{self.model_name}] Invalid message structure for {power_name}")
parsed_messages = validated_messages
# Set final status and return value
if parsed_messages:
success_status = "Success: Messages extracted"
messages_to_return = parsed_messages
elif json_decode_error_occurred:
success_status = "Failure: JSONDecodeError during block parsing"
messages_to_return = []
else: # JSON blocks found, but none were valid messages
success_status = "Success: No valid messages extracted from JSON blocks"
else:
success_status = "Success: No valid messages"
messages_to_return = []
logger.debug(f"[{self.model_name}] Validated conversation replies for {power_name}: {messages_to_return}")

489
ai_diplomacy/formatter.py Normal file
View file

@ -0,0 +1,489 @@
"""
Formatter module for converting natural language LLM responses to structured JSON.
Uses Gemini 2.5 Flash to extract and format information from reasoning-focused responses.
"""
import json
import logging
import os
from typing import Dict, Any, Optional
import google.generativeai as genai
from pathlib import Path
from typing import Optional
# Import logging function
from .utils import log_llm_response
logger = logging.getLogger(__name__)
# Configure Gemini
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", os.getenv("GOOGLE_API_KEY"))
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
# Format type constants
FORMAT_STATE_UPDATE = "state_update"
FORMAT_CONVERSATION = "conversation"
FORMAT_NEGOTIATION_DIARY = "negotiation_diary"
FORMAT_ORDERS = "orders"
FORMAT_INITIAL_STATE = "initial_state"
FORMAT_ORDER_DIARY = "order_diary"
async def format_with_gemini_flash(
raw_response: str,
format_type: str,
power_name: Optional[str] = None,
phase: Optional[str] = None,
log_file_path: Optional[str] = None
) -> str:
"""
Convert a natural language response to the required JSON format using Gemini Flash.
Args:
raw_response: The natural language response from the primary LLM
format_type: One of the FORMAT_* constants indicating desired output format
power_name: Optional power name for logging
phase: Optional phase for logging
log_file_path: Optional path to CSV log file
Returns:
JSON string in the expected format for the given type
Raises:
Exception: If formatting fails or format_type is invalid
"""
logger.info(f"[FORMATTER] Called format_with_gemini_flash for format_type: {format_type}")
logger.debug(f"[FORMATTER] Raw response preview: {raw_response[:200]}...")
# Load the appropriate formatting prompt
prompts_dir = Path(__file__).parent / "prompts" / "formatting"
prompt_file = prompts_dir / f"format_{format_type}.txt"
if not prompt_file.exists():
logger.error(f"[FORMATTER] Format prompt file not found: {prompt_file}")
raise ValueError(f"Unknown format type: {format_type}")
with open(prompt_file, 'r') as f:
format_prompt = f.read()
# Replace placeholder with actual response
format_prompt = format_prompt.replace("[RAW_RESPONSE]", raw_response)
# Don't log the request separately - we'll log once after we get the response
# Use Gemini Flash for formatting
model = genai.GenerativeModel('gemini-2.5-flash')
generation_config = {
"temperature": 0, # Deterministic formatting
"max_output_tokens": 4096,
}
try:
logger.info(f"[FORMATTER] Calling Gemini Flash for {format_type} formatting")
response = model.generate_content(
format_prompt,
generation_config=generation_config
)
formatted_response = response.text.strip()
logger.debug(f"[FORMATTER] Gemini Flash response: {formatted_response[:200]}...")
# Remove markdown code blocks if present
if formatted_response.startswith("```json"):
formatted_response = formatted_response[7:] # Remove ```json
elif formatted_response.startswith("```"):
formatted_response = formatted_response[3:] # Remove ```
if formatted_response.endswith("```"):
formatted_response = formatted_response[:-3] # Remove trailing ```
formatted_response = formatted_response.strip()
# For orders format, wrap in PARSABLE OUTPUT if needed
if format_type == FORMAT_ORDERS and "PARSABLE OUTPUT:" not in formatted_response:
# Extract just the JSON part and wrap it
if formatted_response.startswith("{"):
formatted_response = f"PARSABLE OUTPUT:\n{formatted_response}"
logger.info(f"[FORMATTER] Successfully formatted {format_type} response")
# Log the successful formatting response
if log_file_path:
log_llm_response(
log_file_path=log_file_path,
model_name="gemini-2.5-flash",
power_name=power_name,
phase=phase or "unknown",
response_type=f"format_{format_type}",
raw_input_prompt=format_prompt,
raw_response=formatted_response,
success="TRUE"
)
return formatted_response
except Exception as e:
# If Gemini fails, return the original response
# The existing parsing logic will attempt to handle it
logger.error(f"[FORMATTER] Gemini Flash formatting failed for {format_type}: {str(e)}")
logger.error(f"[FORMATTER] Returning original response as fallback")
# Log the failed formatting attempt
if log_file_path:
log_llm_response(
log_file_path=log_file_path,
model_name="gemini-2.5-flash",
power_name=power_name,
phase=phase or "unknown",
response_type=f"format_{format_type}",
raw_input_prompt=format_prompt,
raw_response=str(e), # Log the error
success=f"FAILURE: {type(e).__name__}"
)
return raw_response
def create_formatting_prompts():
"""Create the formatting prompt files if they don't exist."""
prompts_dir = Path(__file__).parent / "prompts" / "formatting"
prompts_dir.mkdir(parents=True, exist_ok=True)
# Format prompts with examples moved from original prompts
format_prompts = {
FORMAT_ORDER_DIARY: """Extract the order summary from the response below and format it as JSON.
Required JSON format:
{
"order_summary": "Brief summary of orders and strategic intent"
}
Instructions:
- Extract the summary of what orders were given and why
- Keep it concise but informative
- Focus on the strategic intent behind the orders
Response to format:
[RAW_RESPONSE]
Return ONLY the JSON object, no other text.""",
FORMAT_INITIAL_STATE: """Extract the initial goals and relationships from the response below and format as JSON.
Required JSON format:
{
"initial_goals": [
"Specific goal 1",
"Specific goal 2",
"Specific goal 3"
],
"initial_relationships": {
"AUSTRIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ENGLAND": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"FRANCE": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"GERMANY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ITALY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"RUSSIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"TURKEY": "Enemy|Unfriendly|Neutral|Friendly|Ally"
}
}
Instructions:
- Extract 3-5 specific strategic goals from the reasoning
- For relationships, use ONLY: Enemy, Unfriendly, Neutral, Friendly, or Ally
- Include all 7 powers (remove yourself from the list)
Response to format:
[RAW_RESPONSE]
Return ONLY the JSON object, no other text.""",
FORMAT_NEGOTIATION_DIARY: """Extract the negotiation analysis and format as JSON.
Required JSON format:
{
"negotiation_summary": "Key outcomes from negotiations - what was discussed and agreed",
"intent": "Strategic intent for upcoming orders based on negotiations",
"updated_relationships": {
"POWER_NAME": "Enemy|Unfriendly|Neutral|Friendly|Ally"
}
}
Example scenarios:
Scenario 1 - Alliance forming:
{
"negotiation_summary": "Reached agreement with Italy for DMZ in Piedmont and mutual support against Austria. England remains non-committal about channel.",
"intent": "Will honor DMZ with Italy and support their move to Trieste while securing Belgium",
"updated_relationships": {
"ITALY": "Friendly",
"ENGLAND": "Neutral",
"AUSTRIA": "Unfriendly"
}
}
Scenario 2 - Detecting deception:
{
"negotiation_summary": "Germany claims they'll support me into Belgium but also told England they'd help them. Russia suspiciously quiet.",
"intent": "Assume Germany is unreliable, prepare defensive positions",
"updated_relationships": {
"GERMANY": "Unfriendly",
"RUSSIA": "Neutral"
}
}
Scenario 3 - Coordinated attack:
{
"negotiation_summary": "Coordinated joint attack on Turkey with Austria. Russia agrees to DMZ Black Sea.",
"intent": "Execute agreed plan: Army Greece to Bulgaria, Fleet Aegean to Eastern Med",
"updated_relationships": {
"AUSTRIA": "Ally",
"RUSSIA": "Friendly",
"TURKEY": "Enemy"
}
}
Instructions:
- Summarize what was actually discussed and agreed (or disagreed) upon
- State clear intent for upcoming moves based on negotiations
- Only include powers whose relationships have changed
- Use ONLY: Enemy, Unfriendly, Neutral, Friendly, or Ally
Response to format:
[RAW_RESPONSE]
Return ONLY the JSON object, no other text.""",
FORMAT_STATE_UPDATE: """Extract the state update information and format as JSON.
Required JSON format:
{
"reasoning": "Brief explanation of your analysis",
"relationships": {
"AUSTRIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ENGLAND": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"FRANCE": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"GERMANY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ITALY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"RUSSIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"TURKEY": "Enemy|Unfriendly|Neutral|Friendly|Ally"
},
"goals": [
"Specific goal 1",
"Specific goal 2",
"Specific goal 3"
]
}
Example scenarios:
Scenario 1 - Early game position:
{
"reasoning": "France moved to Channel despite promises. Germany supporting me as agreed. Focus shifting to defending homeland.",
"relationships": {
"AUSTRIA": "Neutral",
"ENGLAND": "Neutral",
"FRANCE": "Enemy",
"GERMANY": "Friendly",
"ITALY": "Neutral",
"RUSSIA": "Neutral",
"TURKEY": "Neutral"
},
"goals": [
"Defend London from French fleet in Channel",
"Secure Norway before Russia",
"Coordinate with Germany against France"
]
}
Scenario 2 - Mid-game betrayal:
{
"reasoning": "Italy broke our alliance and took Marseilles. Need new allies urgently. Germany looking strong.",
"relationships": {
"AUSTRIA": "Unfriendly",
"ENGLAND": "Neutral",
"FRANCE": "Neutral",
"GERMANY": "Unfriendly",
"ITALY": "Enemy",
"RUSSIA": "Friendly",
"TURKEY": "Ally"
},
"goals": [
"Retake Marseilles from Italy",
"Fortify Alpine positions",
"Support Turkey against Austria"
]
}
Instructions:
- Extract the strategic reasoning from the analysis
- Include ALL 7 powers in relationships (remove yourself)
- Use ONLY: Enemy, Unfriendly, Neutral, Friendly, or Ally
- Extract 2-4 specific, actionable goals
Response to format:
[RAW_RESPONSE]
Return ONLY the JSON object, no other text.""",
FORMAT_ORDERS: """Extract the orders from the response and format them properly.
Required format:
PARSABLE OUTPUT:
{{
"orders": ["order1", "order2", "order3"]
}}
Order format examples:
- Hold: "A PAR H"
- Move: "A PAR - MAR", "F BRE - MAO"
- Support: "A MAR S A PAR - BUR", "F MAO S F BRE - ENG"
- Convoy: "F MAO C A BRE - LON"
- Build: "A PAR B", "F BRE B"
- Disband: "A PAR D"
- Retreat: "A PAR - BUR"
- Dual-coast: "F STP/SC" (south coast), "F SPA/NC" (north coast)
Example 1 - France Spring 1901:
If the response mentions:
"I'll move army from Paris to Burgundy, fleet from Brest to Mid-Atlantic, and hold Marseilles"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A PAR - BUR",
"F BRE - MAO",
"A MAR H"
]
}}
Example 2 - Italy with supports:
If the response mentions:
"Venice attacks Trieste with support from Apulia and Ionian Sea"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A VEN - TRI",
"A APU S A VEN - TRI",
"F ION S A VEN - TRI"
]
}}
Example 3 - Build phase:
If the response mentions:
"Build army in Paris and fleet in Marseilles"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A PAR B",
"F MAR B"
]
}}
Instructions:
- Extract all orders mentioned in the response
- Use exact 3-letter province codes
- Format each order exactly as shown in examples
- Include ALL units that were given orders
- Pay attention to support orders - they must reference exact moves
Response to format:
[RAW_RESPONSE]
Return in this exact format with double braces:
PARSABLE OUTPUT:
{{
"orders": [list of order strings]
}}""",
FORMAT_CONVERSATION: """Extract the messages from the response and format as JSON array.
Required JSON format:
[
{
"message_type": "global",
"content": "Message text for all powers"
},
{
"message_type": "private",
"recipient": "POWER_NAME",
"content": "Private message text"
}
]
Example 1 - Multiple messages:
If the response mentions:
"Send a global message: 'I propose we all work together against the leader'
Tell Germany privately: 'I'll support you into Denmark if you help me with Belgium'
Message Russia: 'Are you still interested in the Black Sea DMZ?'"
Extract as:
[
{
"message_type": "global",
"content": "I propose we all work together against the leader"
},
{
"message_type": "private",
"recipient": "GERMANY",
"content": "I'll support you into Denmark if you help me with Belgium"
},
{
"message_type": "private",
"recipient": "RUSSIA",
"content": "Are you still interested in the Black Sea DMZ?"
}
]
Example 2 - Single private message:
If the response mentions:
"Reply to Italy: 'I accept your proposal for Piedmont DMZ'"
Extract as:
[
{
"message_type": "private",
"recipient": "ITALY",
"content": "I accept your proposal for Piedmont DMZ"
}
]
Example 3 - No messages:
If the response indicates no messages to send:
Extract as:
[]
Instructions:
- Extract each message the player wants to send
- Identify if it's meant for everyone (global) or specific power (private)
- For private messages, identify the recipient power (use uppercase: AUSTRIA, ENGLAND, FRANCE, GERMANY, ITALY, RUSSIA, TURKEY)
- Preserve the key diplomatic points but clean up formatting
- Use proper JSON string escaping for quotes
- Return empty array [] if no messages
Response to format:
[RAW_RESPONSE]
Return ONLY the JSON array, no other text."""
}
# Write all formatting prompts
for format_type, prompt_content in format_prompts.items():
prompt_file = prompts_dir / f"format_{format_type}.txt"
with open(prompt_file, 'w') as f:
f.write(prompt_content)
print(f"Created {len(format_prompts)} formatting prompts in {prompts_dir}")
if __name__ == "__main__":
# Create the formatting prompts when module is run directly
create_formatting_prompts()

View file

@ -9,9 +9,10 @@ if False: # TYPE_CHECKING
from diplomacy.models.game import GameHistory
from .agent import DiplomacyAgent
from .agent import ALL_POWERS, ALLOWED_RELATIONSHIPS
from .agent import ALL_POWERS, ALLOWED_RELATIONSHIPS, _load_prompt_file
from .utils import run_llm_and_log, log_llm_response
from .prompt_constructor import build_context_prompt
from .formatter import format_with_gemini_flash, FORMAT_INITIAL_STATE
logger = logging.getLogger(__name__)
@ -32,14 +33,18 @@ async def initialize_agent_state_ext(
success_status = "Failure: Initialized" # Default status
try:
# Use a simplified prompt for initial state generation
# Load the unformatted prompt template
allowed_labels_str = ", ".join(ALLOWED_RELATIONSHIPS)
initial_prompt = f"You are the agent for {power_name} in a game of Diplomacy at the very start (Spring 1901). " \
f"Analyze the initial board position and suggest 2-3 strategic high-level goals for the early game. " \
f"Consider your power's strengths, weaknesses, and neighbors. " \
f"Also, provide an initial assessment of relationships with other powers. " \
f"IMPORTANT: For each relationship, you MUST use exactly one of the following labels: {allowed_labels_str}. " \
f"Format your response as a JSON object with two keys: 'initial_goals' (a list of strings) and 'initial_relationships' (a dictionary mapping power names to one of the allowed relationship strings)."
initial_prompt_template = _load_prompt_file('unformatted/initial_state_prompt.txt', prompts_dir=prompts_dir)
if not initial_prompt_template:
logger.error(f"[{power_name}] Could not load unformatted/initial_state_prompt.txt. Cannot initialize.")
return
# Format the prompt with variables
initial_prompt = initial_prompt_template.format(
power_name=power_name,
allowed_labels_str=allowed_labels_str
)
board_state = game.get_state() if game else {}
possible_orders = game.get_all_possible_orders() if game else {}
@ -75,7 +80,15 @@ async def initialize_agent_state_ext(
parsed_successfully = False
try:
update_data = agent._extract_json_from_text(response)
# Format the natural language response into JSON
formatted_response = await format_with_gemini_flash(
response,
FORMAT_INITIAL_STATE,
power_name=power_name,
phase=current_phase,
log_file_path=log_file_path
)
update_data = agent._extract_json_from_text(formatted_response)
logger.debug(f"[{power_name}] Successfully parsed JSON: {update_data}")
parsed_successfully = True
except json.JSONDecodeError as e:

View file

@ -184,16 +184,16 @@ def construct_order_generation_prompt(
"""
# Load prompts
_ = load_prompt("few_shot_example.txt", prompts_dir=prompts_dir) # Loaded but not used, as per original logic
# Pick the phase-specific instruction file
# Pick the phase-specific instruction file (using unformatted versions)
phase_code = board_state["phase"][-1] # 'M' (movement), 'R', or 'A' / 'B'
if phase_code == "M":
instructions_file = "order_instructions_movement_phase.txt"
instructions_file = "unformatted/order_instructions_movement_phase.txt"
elif phase_code in ("A", "B"): # builds / adjustments
instructions_file = "order_instructions_adjustment_phase.txt"
instructions_file = "unformatted/order_instructions_adjustment_phase.txt"
elif phase_code == "R": # retreats
instructions_file = "order_instructions_retreat_phase.txt"
instructions_file = "unformatted/order_instructions_retreat_phase.txt"
else: # unexpected default to movement rules
instructions_file = "order_instructions_movement_phase.txt"
instructions_file = "unformatted/order_instructions_movement_phase.txt"
instructions = load_prompt(instructions_file, prompts_dir=prompts_dir)
_use_simple = os.getenv("SIMPLE_PROMPTS", "0").lower() in {"1", "true", "yes"}
@ -212,10 +212,15 @@ def construct_order_generation_prompt(
include_messages=not _use_simple, # include only when *not* simple
)
final_prompt = system_prompt + "\n\n" + context + "\n\n" + instructions
# Append goals at the end for focus
goals_section = ""
if agent_goals:
goals_section = "\n\nYOUR STRATEGIC GOALS:\n" + "\n".join(f"- {g}" for g in agent_goals) + "\n\nKeep these goals in mind when choosing your orders."
final_prompt = system_prompt + "\n\n" + context + "\n\n" + instructions + goals_section
# Make the power names more LLM friendly
final_prompt = final_prompt.replace('AUSTRIA', 'Austria').replace('ENGLAND', "England").replace('FRANCE', 'France').replace('GERMANY', 'Germany').replace('ITALY', "Italy").replace('RUSSIA', 'Russia').replace('TURKEY', 'Turkey')
print(final_prompt)
logger.debug(f"Final order generation prompt preview for {power_name}: {final_prompt[:500]}...")
return final_prompt

View file

@ -0,0 +1,82 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract diplomatic messages from the response below and format them as JSON.
The response may contain strategic analysis, order suggestions, or other content - IGNORE all of that. ONLY extract actual messages intended to be sent to other powers.
If the response contains NO messages to other powers (only strategy discussion or orders), return an empty array: []
Required JSON format:
[
{
"message_type": "global",
"content": "Message text for all powers"
},
{
"message_type": "private",
"recipient": "POWER_NAME",
"content": "Private message text"
}
]
Example 1 - Multiple messages:
If the response mentions:
"Send a global message: 'I propose we all work together against the leader'
Tell Germany privately: 'I'll support you into Denmark if you help me with Belgium'
Message Russia: 'Are you still interested in the Black Sea DMZ?'"
Extract as:
[
{
"message_type": "global",
"content": "I propose we all work together against the leader"
},
{
"message_type": "private",
"recipient": "GERMANY",
"content": "I'll support you into Denmark if you help me with Belgium"
},
{
"message_type": "private",
"recipient": "RUSSIA",
"content": "Are you still interested in the Black Sea DMZ?"
}
]
Example 2 - Single private message:
If the response mentions:
"Reply to Italy: 'I accept your proposal for Piedmont DMZ'"
Extract as:
[
{
"message_type": "private",
"recipient": "ITALY",
"content": "I accept your proposal for Piedmont DMZ"
}
]
Example 3 - No messages:
If the response indicates no messages to send:
Extract as:
[]
Instructions:
- ONLY extract actual diplomatic messages (communications to other powers)
- Do NOT extract strategic thoughts, order discussions, or analysis
- Look for phrases like "Tell X", "Message to Y", "Propose to Z", "I suggest we", etc.
- If the response only contains strategy/orders with NO messages, return []
- For each message found:
- Identify if it's global (to all) or private (to specific power)
- For private messages, identify the recipient (AUSTRIA, ENGLAND, FRANCE, GERMANY, ITALY, RUSSIA, TURKEY)
- Extract the actual message content
- Use proper JSON escaping for quotes
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
REMEMBER: You are ONLY formatting messages, not creating them. If there are no messages in the response above, return an empty array [].
Return ONLY the JSON array, no other text.
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,85 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract initial strategic goals and relationship assessments from the response below and format them as JSON.
The response contains strategic analysis about a Diplomacy game starting position. Extract the goals and relationships.
Required JSON format:
{
"initial_goals": [
"Specific goal 1",
"Specific goal 2",
"Specific goal 3"
],
"initial_relationships": {
"AUSTRIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ENGLAND": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"FRANCE": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"GERMANY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ITALY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"RUSSIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"TURKEY": "Enemy|Unfriendly|Neutral|Friendly|Ally"
}
}
Example 1 - Russia's opening:
If the response mentions:
"My goals are to secure the Western Front by preventing German expansion, control the Black Sea to limit Turkey, and neutralize Austria who is my traditional rival. Germany is unfriendly, Austria is an enemy, Turkey could go either way."
Extract as:
{
"initial_goals": [
"Secure the Western Front by preventing German expansion",
"Control the Black Sea to limit Turkey",
"Neutralize Austria who is my traditional rival"
],
"initial_relationships": {
"AUSTRIA": "Enemy",
"ENGLAND": "Neutral",
"FRANCE": "Neutral",
"GERMANY": "Unfriendly",
"ITALY": "Neutral",
"TURKEY": "Neutral"
}
}
Example 2 - England's opening:
If the response mentions:
"I need to secure control of the seas, prevent France from taking the Channel, and expand into Scandinavia. France is my main concern, while Germany could be a useful partner against them."
Extract as:
{
"initial_goals": [
"Secure control of the North Sea and English Channel",
"Prevent French expansion into the Channel",
"Expand into Scandinavia"
],
"initial_relationships": {
"AUSTRIA": "Neutral",
"FRANCE": "Unfriendly",
"GERMANY": "Friendly",
"ITALY": "Neutral",
"RUSSIA": "Neutral",
"TURKEY": "Neutral"
}
}
Instructions:
- Goals: Look for strategic objectives, expansion plans, or priorities mentioned
- Common phrases: "My goals are", "I need to", "Focus on", "Secure", "Expand into"
- Extract 3-5 specific goals
- Relationships: Look for assessments of other powers
- Common phrases: "X is a threat", "Y could be an ally", "Z is neutral"
- Use ONLY these labels: Enemy, Unfriendly, Neutral, Friendly, or Ally
- Include all 7 powers (remove the player's own power)
- If a power isn't mentioned, default to "Neutral"
- Map natural language to labels:
- "threat", "rival", "must eliminate" → Enemy or Unfriendly
- "potential partner", "could work with" → Friendly
- "ally", "alliance" → Ally
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
Return ONLY the JSON object, no other text.
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,73 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract negotiation outcomes and analysis from the response below and format them as JSON.
The response contains a player's reflection on diplomatic negotiations that just occurred.
Required JSON format:
{
"negotiation_summary": "Key outcomes from negotiations - what was discussed and agreed",
"intent": "Strategic intent for upcoming orders based on negotiations",
"current_relationships": {
"AUSTRIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ENGLAND": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"FRANCE": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"GERMANY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ITALY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"RUSSIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"TURKEY": "Enemy|Unfriendly|Neutral|Friendly|Ally"
}
}
Example scenarios:
Scenario 1 - Alliance forming:
{
"negotiation_summary": "Reached agreement with Italy for DMZ in Piedmont and mutual support against Austria. England remains non-committal about channel.",
"intent": "Will honor DMZ with Italy and support their move to Trieste while securing Belgium",
"current_relationships": {
"ITALY": "Friendly",
"ENGLAND": "Neutral",
"AUSTRIA": "Unfriendly"
}
}
Scenario 2 - Detecting deception:
{
"negotiation_summary": "Germany claims they'll support me into Belgium but also told England they'd help them. Russia suspiciously quiet.",
"intent": "Assume Germany is unreliable, prepare defensive positions",
"current_relationships": {
"GERMANY": "Unfriendly",
"RUSSIA": "Neutral"
}
}
Scenario 3 - Coordinated attack:
{
"negotiation_summary": "Coordinated joint attack on Turkey with Austria. Russia agrees to DMZ Black Sea.",
"intent": "Execute agreed plan: Army Greece to Bulgaria, Fleet Aegean to Eastern Med",
"current_relationships": {
"AUSTRIA": "Ally",
"RUSSIA": "Friendly",
"TURKEY": "Enemy"
}
}
Instructions:
- negotiation_summary: What was discussed with other powers?
- Look for: agreements made, proposals received, rejections, promises
- Common phrases: "agreed to", "proposed", "rejected", "promised"
- intent: What will the player do based on these negotiations?
- Look for: planned moves, strategies, responses to agreements
- Common phrases: "I will", "plan to", "intend to", "based on this"
- current_relationships: Your assessment of ALL powers after negotiations
- Include ALL 7 powers (remove yourself from the list)
- Reflect any changes from negotiations
- Use ONLY: Enemy, Unfriendly, Neutral, Friendly, or Ally
- For powers not involved in negotiations, maintain previous assessment
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
Return ONLY the JSON object, no other text.
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,52 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract a summary of orders from the response below and format it as JSON.
The response contains a player's reflection on the orders they just submitted.
Required JSON format:
{
"order_summary": "Brief summary of orders and strategic intent"
}
Example 1 - Movement phase:
If the response mentions:
"I ordered my army in Paris to Burgundy to secure the neutral supply center, fleet from Brest to Mid-Atlantic Ocean to prepare for Iberian operations, and held in Marseilles to defend against Italian aggression."
Extract as:
{
"order_summary": "Moved A PAR to BUR for neutral SC, F BRE to MAO for Iberian positioning, held MAR against Italy"
}
Example 2 - Support orders:
If the response mentions:
"All units supported the attack on Munich - armies from Bohemia and Tyrolia supported Berlin's move into Munich."
Extract as:
{
"order_summary": "Coordinated three-unit attack on Munich with BOH and TYR supporting BER"
}
Example 3 - Build phase:
If the response mentions:
"Built fleets in London and Edinburgh to strengthen naval presence, no builds in Liverpool."
Extract as:
{
"order_summary": "Built F LON and F EDI for naval dominance, waived LVP build"
}
Instructions:
- Look for descriptions of what orders were submitted
- Common phrases: "I ordered", "moved to", "supported", "held in"
- Summarize both WHAT was ordered and WHY (strategic intent)
- Keep it concise (1-2 sentences)
- Use standard 3-letter province codes when mentioned
- Focus on the strategic purpose, not just the mechanical moves
- If the response doesn't contain order information, summarize the strategic discussion
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
Return ONLY the JSON object, no other text.
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,121 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract Diplomacy game orders from the response below and format them properly.
The response will contain strategic analysis and order suggestions. Look for the actual orders (movements, holds, supports, etc.).
Required format:
PARSABLE OUTPUT:
{{
"orders": ["order1", "order2", "order3"]
}}
Order format examples:
- Hold: "A PAR H"
- Move: "A PAR - MAR", "F BRE - MAO"
- Support: "A MAR S A PAR - BUR", "F MAO S F BRE - ENG"
- Convoy: "F MAO C A BRE - LON"
- Build: "A PAR B", "F BRE B"
- Disband: "A PAR D"
- Retreat: "A PAR - BUR"
- Dual-coast: "F STP/SC" (south coast), "F SPA/NC" (north coast)
Example 1 - France Spring 1901:
If the response mentions:
"I'll move army from Paris to Burgundy, fleet from Brest to Mid-Atlantic, and hold Marseilles"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A PAR - BUR",
"F BRE - MAO",
"A MAR H"
]
}}
Example 2 - Italy with supports:
If the response mentions:
"Venice attacks Trieste with support from Apulia and Ionian Sea"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A VEN - TRI",
"A APU S A VEN - TRI",
"F ION S A VEN - TRI"
]
}}
Example 3 - Build phase:
If the response mentions:
"Build army in Paris and fleet in Marseilles"
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A PAR B",
"F MAR B"
]
}}
Example 4 - Germany Spring 1901:
If the response mentions:
"Denmark (DEN) and Holland (HOL) are key neutral centers for early German expansion. Need to secure Berlin (BER) and Munich (MUN) against potential French or Russian incursions. Kiel (KIE) fleet is best positioned for DEN, while an army from Ruhr (RUH) can take HOL."
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A BER H",
"A MUN H",
"F KIE - DEN",
"A RUH - HOL",
"A SIL - WAR",
"F HEL H"
]
}}
Example 5 - Italy Autumn 1902:
If the response mentions:
"My primary goal is to take Trieste (TRI) from Austria. Army in Venice (VEN) will attack, supported by Army in Apulia (APU). Fleet in Ionian Sea (ION) will support the attack on Trieste from the sea. Army in Rome (ROM) will hold to protect the capital. Fleet in Naples (NAP) will move to Tyrrhenian Sea (TYS) to defend against a potential French move."
Extract as:
PARSABLE OUTPUT:
{{
"orders": [
"A VEN - TRI",
"A APU S A VEN - TRI",
"F ION S A VEN - TRI",
"A ROM H",
"F NAP - TYS"
]
}}
Instructions:
- Look for lines that describe unit movements (e.g., "A BER - KIE", "Move Berlin to Kiel")
- Convert natural language to standard format:
- "Move army from Berlin to Kiel" → "A BER - KIE"
- "Fleet in Kiel moves to Denmark" → "F KIE - DEN"
- "Hold in Munich" → "A MUN H"
- Use exact 3-letter province codes (BER, KIE, MUN, etc.)
- Include ALL units that were given orders
- If you see "Order:" followed by a properly formatted order, use it directly
- Common patterns to look for:
- "A/F [PROVINCE] - [PROVINCE]" (movement)
- "A/F [PROVINCE] H" (hold)
- "A/F [PROVINCE] S A/F [PROVINCE] - [PROVINCE]" (support)
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
REMEMBER: Extract the actual game orders from the strategic discussion above. Look for specific unit movements.
Return in this exact format with double braces:
PARSABLE OUTPUT:
{{
"orders": [list of order strings]
}}
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,140 @@
IMPORTANT: You are a formatting assistant. Your ONLY job is to extract state update information (analysis, relationships, and goals) from the response below and format them as JSON.
The response contains a player's analysis of the current game state after seeing the results of a turn.
Required JSON format:
{
"reasoning": "Brief explanation of your analysis",
"relationships": {
"AUSTRIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ENGLAND": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"FRANCE": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"GERMANY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"ITALY": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"RUSSIA": "Enemy|Unfriendly|Neutral|Friendly|Ally",
"TURKEY": "Enemy|Unfriendly|Neutral|Friendly|Ally"
},
"goals": [
"Specific goal 1",
"Specific goal 2",
"Specific goal 3"
]
}
Example scenarios:
Scenario 1 - Early game position:
{
"reasoning": "France moved to Channel despite promises. Germany supporting me as agreed. Focus shifting to defending homeland.",
"relationships": {
"AUSTRIA": "Neutral",
"ENGLAND": "Neutral",
"FRANCE": "Enemy",
"GERMANY": "Friendly",
"ITALY": "Neutral",
"RUSSIA": "Neutral",
"TURKEY": "Neutral"
},
"goals": [
"Defend London from French fleet in Channel",
"Secure Norway before Russia",
"Coordinate with Germany against France"
]
}
Scenario 2 - Mid-game betrayal:
{
"reasoning": "Italy broke our alliance and took Marseilles. Need new allies urgently. Germany looking strong.",
"relationships": {
"AUSTRIA": "Unfriendly",
"ENGLAND": "Neutral",
"FRANCE": "Neutral",
"GERMANY": "Unfriendly",
"ITALY": "Enemy",
"RUSSIA": "Friendly",
"TURKEY": "Ally"
},
"goals": [
"Retake Marseilles from Italy",
"Fortify Alpine positions",
"Support Turkey against Austria"
]
}
Scenario 3 - After Cooperation:
{
"reasoning": "Austria helped take Warsaw. Russia attacked Prussia.",
"relationships": {
"AUSTRIA": "Ally",
"RUSSIA": "Enemy",
"TURKEY": "Neutral",
"ITALY": "Unfriendly",
"FRANCE": "Neutral",
"ENGLAND": "Neutral",
"GERMANY": "Neutral"
},
"goals": [
"Hold Warsaw against Russia",
"Keep Austrian alliance",
"Block Italian expansion"
]
}
Scenario 4 - England after failed Belgium attack:
{
"reasoning": "My attack on Belgium was decisively repulsed due to Franco-German cooperation, marking them as a significant threat bloc. Russia's acquisition of Sweden is concerning for my northern position. The Austro-Italian conflict seems localized for now, and Turkey's inactivity makes them an unknown variable, potentially open to diplomacy.",
"relationships": {
"FRANCE": "Enemy",
"GERMANY": "Enemy",
"RUSSIA": "Unfriendly",
"AUSTRIA": "Neutral",
"ITALY": "Neutral",
"TURKEY": "Neutral"
},
"goals": [
"Break the Franco-German alliance or find a way to counter their combined strength.",
"Secure North Sea (NTH) and prevent further Russian expansion towards Norway (NWY).",
"Seek dialogue with Turkey or Austria/Italy to create a counterweight to the dominant bloc."
]
}
Scenario 5 - Russia after Black Sea negotiation:
{
"reasoning": "Securing Rumania via Turkish agreement is a major success. This improves my southern position and Turkey is now a provisional ally. Germany's move into Silesia is a direct and immediate threat to Warsaw; they are now my primary adversary. Austria and France are posturing, but their conflict doesn't directly affect me yet, keeping them neutral. England's new fleet is a long-term concern but not immediate. Italy's westward focus means they are not a current threat or priority.",
"relationships": {
"GERMANY": "Enemy",
"AUSTRIA": "Neutral",
"TURKEY": "Ally",
"ITALY": "Neutral",
"FRANCE": "Neutral",
"ENGLAND": "Unfriendly"
},
"goals": [
"Defend Warsaw against Germany, possibly by moving Lvn-War or Mos-War.",
"Solidify alliance with Turkey, potentially coordinating further moves in the south or against Austria if Germany allies with them.",
"Monitor English fleet movements and prepare for a potential northern threat in future turns.",
"Explore diplomatic options with France or Austria to counter German aggression."
]
}
Instructions:
- reasoning: Extract the key strategic analysis
- Look for: what happened, what changed, new threats/opportunities
- Keep it brief (1-2 sentences)
- relationships: Current view of ALL other powers
- Must include all 7 powers (remove the player's own power)
- Use ONLY: Enemy, Unfriendly, Neutral, Friendly, or Ally
- Look for relationship indicators in the analysis
- If a power isn't mentioned, check if there's a previous relationship to maintain
- goals: Updated strategic objectives
- Look for: new priorities, adjusted plans, responses to events
- Extract 2-4 specific, actionable goals
- Common phrases: "need to", "must", "priority is", "focus on"
<content_to_format>
[RAW_RESPONSE]
</content_to_format>
Return ONLY the JSON object, no other text.
Now format the content within the <content_to_format> tags above.

View file

@ -0,0 +1,22 @@
DIPLOMATIC CONTEXT
You are {power_name} in a game of Diplomacy.
Current Phase: {current_phase}
YOUR STRATEGIC FRAMEWORK
Goals:
{agent_goals}
Current Relationships:
{agent_relationships}
DIPLOMATIC SITUATION
Recent Negotiations:
{recent_private_diary}
Messages This Round:
{messages_this_round}
OTHER POWERS' STATUS
Active Powers: {active_powers}
{eliminated_status}

View file

@ -0,0 +1,35 @@
DIPLOMATIC MESSAGING TASK
You need to compose diplomatic messages to other powers in this negotiation phase.
IMPORTANT: This is about WRITING MESSAGES to other powers, not analyzing strategy or choosing orders.
GUIDELINES
- Respond to messages in "RECENT MESSAGES REQUIRING YOUR ATTENTION" section
- Propose deals, alliances, or coordination
- Share (or mislead about) your intentions
- Build trust or sow discord as needed
- You can send multiple messages
- You can choose to ignore certain powers
RESPOND IN TWO PARTS:
1. REASONING: Explain your diplomatic approach:
- Which powers are you prioritizing for communication?
- What messages need responses?
- What deals or coordination are you proposing?
- Are you being honest or deceptive?
- Who are you deliberately not messaging and why?
2. MESSAGES: List the actual messages to send:
- For EACH message specify:
* Type: "global" (all see it) or "private" (only recipient sees)
* Recipient: If private, which power (AUSTRIA, ENGLAND, FRANCE, GERMANY, ITALY, RUSSIA, TURKEY)
* Content: The actual message text
- Be specific and diplomatic in your wording
- Examples:
* Private to FRANCE: "I'm planning to move to the Channel. Will you support me?"
* Global: "I propose we all respect current borders this turn."
* Private to RUSSIA: "If you stay out of Galicia, I'll support you into Rumania."
REMEMBER: You are writing diplomatic messages, not explaining your overall strategy or orders.

View file

@ -0,0 +1,7 @@
You are the agent for {power_name} in a game of Diplomacy at the very start (Spring 1901). Analyze the initial board position and suggest 2-3 strategic high-level goals for the early game. Consider your power's strengths, weaknesses, and neighbors. Also, provide an initial assessment of relationships with other powers. IMPORTANT: For each relationship, you MUST use exactly one of the following labels: {allowed_labels_str}.
Please respond in two parts:
1. REASONING: First, explain your strategic analysis of the starting position.
2. STRATEGY: Then, provide your 2-3 strategic high-level goals and your initial assessment of relationships with other powers.

View file

@ -0,0 +1,39 @@
NEGOTIATION SUMMARY REQUEST
Power: {power_name}
Phase: {current_phase}
MESSAGES THIS ROUND
{messages_this_round}
{ignored_messages_context}
CURRENT STATUS
Goals:
{agent_goals}
Relationships:
{agent_relationships}
Game State:
{board_state_str}
TASK
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
Please respond in two parts:
1. REASONING: First, explain your analysis of the negotiations. What did each power communicate or fail to communicate? What do their messages (or silence) reveal about their intentions? How does this affect your strategic position?
2. NEGOTIATION SUMMARY: Then provide:
- A summary of key outcomes from the negotiations
- Your strategic intent for upcoming orders based on these negotiations
- Any relationship updates based on the negotiations (only include powers whose relationships have changed)

View file

@ -0,0 +1,34 @@
ORDER DIARY ENTRY - POST-ORDER REFLECTION
You have ALREADY SUBMITTED the following orders for this turn:
{orders_list_str}
Power: {power_name}
Phase: {current_phase}
GAME CONTEXT (state BEFORE orders were executed):
{board_state_str}
Your Strategic Framework:
Goals: {agent_goals}
Relationships: {agent_relationships}
IMPORTANT TASK
You have ALREADY SUBMITTED your orders (listed above). Now write a diary entry reflecting on WHY you chose these specific orders.
This is NOT about choosing new orders - it's about documenting your reasoning for the orders you ALREADY SUBMITTED.
RESPOND IN TWO PARTS:
1. REASONING: Explain your strategic thinking:
- What threats were you responding to when you chose these orders?
- What opportunities were you trying to seize?
- How did your relationships and recent negotiations influence these choices?
- What calculated risks did you decide to take?
2. ORDER SUMMARY: Provide a concise summary (1-2 sentences):
- Briefly restate the key moves you made (e.g., "Moved armies to secure BUR and supported the attack on MUN")
- Explain the strategic intent (e.g., "to block French expansion while gaining a foothold in Germany")
- Note any contingencies or backup plans
REMEMBER: Focus on explaining the orders SHOWN ABOVE that you ALREADY SUBMITTED.

View file

@ -0,0 +1,67 @@
PRIMARY OBJECTIVE
Control 18 supply centers. Nothing else will do.
CRITICAL ADJUSTMENT PHASE RULES
1. Only use orders from the provided possible_orders list
2. You can only build in unoccupied HOME supply centers you currently control
3. Build orders format: '[UnitType] [Location] B'
- UnitType: 'A' (Army) or 'F' (Fleet)
- Example: 'A PAR B', 'F LON B'
4. Disband orders format: '[UnitType] [Location] D'
- Example: 'A PAR D', 'F LON D'
5. Dual-coast provinces require coast specification for fleet builds:
- Format: 'F [PROVINCE]/[COAST] B' where [COAST] = NC, SC, EC, or WC
- Example: 'F STP/NC B', 'F SPA/SC B'
HOME SUPPLY CENTERS
Austria
- Budapest
- Trieste
- Vienna
England
- Edinburgh
- Liverpool
- London
France
- Brest
- Marseilles
- Paris
Germany
- Berlin
- Kiel
- Munich
Italy
- Naples
- Rome
- Venice
Russia
- Moscow
- Saint Petersburg
- Sevastopol
- Warsaw
Turkey
- Ankara
- Constantinople
- Smyrna
ADJUSTMENT DECISION PROCESS
1. CALCULATE
- Count your supply centers
- Count your current units
- Determine builds or disbands needed
2. STRATEGIZE
- Where to build for maximum strategic impact
- Which units to disband if necessary
- Balance between armies and fleets
3. PRIORITIZE
- Build in threatened home centers first
- Build units that support your strategic goals
- Disband isolated or least useful units
Please respond in two parts:
1. REASONING: First, explain your adjustment analysis. How many supply centers do you control? How many units do you have? Where will you build and why? If disbanding, which units are least valuable?
2. ADJUSTMENT ORDERS: Then, list all your build (B) or disband (D) orders. Be precise with unit types (A/F) and locations.

View file

@ -0,0 +1,67 @@
PRIMARY OBJECTIVE
Control 18 supply centers. Nothing else will do.
CRITICAL RULES
1. Only use orders from the provided possible_orders list
2. Support orders must match actual moves (e.g., 'A PAR S F PIC - ENG' needs 'F PIC - ENG')
3. Build orders (build phase only):
- Format: '[UnitType] [Location3LetterCode] B'
- UnitType: 'A' (Army) or 'F' (Fleet)
- Example: 'A PAR B', 'F LON B'
- NOTE YOU CAN ONLY BUILD UNITS IN YOUR HOME CENTER THAT ARE EMPTY, THE ONES YOU STARTED WITH, IF YOU LOSE THESE YOU CANNOT BUILD UNITS SO THEY ARE CRITICAL
Austria
- Budapest
- Trieste
- Vienna
England
- Edinburgh
- Liverpool
- London
France
- Brest
- Marseilles
- Paris
Germany
- Berlin
- Kiel
- Munich
Italy
- Naples
- Rome
- Venice
Russia
- Moscow
- Saint Petersburg
- Sevastopol
- Warsaw
Turkey
- Ankara
- Constantinople
- Smyrna
ORDER SUBMISSION PROCESS
1. ANALYZE
- Review game state, orders, messages, and other powers' motivations
- Focus on expansion and capturing supply centers
- Be aggressive, not passive
- Take calculated risks for significant gains
- Find alternative paths if blocked
2. REASON
- Write out your strategic thinking
- Explain goals and move choices
- Consider supports and holds
3. FORMAT
- Dual-coast provinces (STP, SPA, BUL):
* Specify coast when needed: 'F [PROVINCE]/[COAST_CODE]'
* Example: 'F STP/NC B', 'A MAR S F SPA/SC - WES'
* Coast codes: NC (North), SC (South), EC (East), WC (West)
- All orders resolve simultaneously
- Submit orders only, no messages
Please respond in two parts:
1. REASONING: First, explain your detailed strategic analysis. What are your immediate objectives? Which supply centers are you targeting? How will you deal with threats? What coordinated moves are you planning? Consider all your units and their best uses.
2. ORDERS: Then, list each order you want to submit, one per line. Be precise with unit types (A/F) and location codes.

View file

@ -0,0 +1,39 @@
PRIMARY OBJECTIVE
Control 18 supply centers. Nothing else will do.
CRITICAL RETREAT PHASE RULES
1. The possible orders section shows where your dislodged units can retreat
2. Units cannot retreat to:
- The province they were dislodged from
- A province occupied after this turn's moves
- A province where a standoff occurred
3. If no valid retreat exists, the unit must disband
4. Retreat format: '[UnitType] [From] - [To]'
- Example: 'A PAR - BUR', 'F BRE - ENG'
5. Disband format: '[UnitType] [Location] D'
- Example: 'A PAR D', 'F BRE D'
6. Dual-coast provinces require coast specification for fleet retreats:
- Format: 'F [PROVINCE]/[COAST] - [DESTINATION]'
- Example: 'F SPA/SC - MAO', 'F BUL/EC - BLA'
- Coast codes: NC (North), SC (South), EC (East), WC (West)
RETREAT DECISION PROCESS
1. ASSESS
- Which of your units are dislodged
- What retreat options are available
- Strategic value of each dislodged unit
2. PRIORITIZE
- Retreat units that can still contribute to your strategy
- Disband units that have no good retreat options
- Consider future positioning for retreated units
3. EXECUTE
- Choose optimal retreat destinations
- Accept disbands when necessary
Please respond in two parts:
1. REASONING: First, explain your retreat decisions. Which units are dislodged? What are their retreat options? Why are you choosing to retreat or disband each unit?
2. RETREAT ORDERS: Then, provide a retreat or disband order for each dislodged unit. Be precise with unit types (A/F) and locations.

View file

@ -0,0 +1,34 @@
You are analyzing the results of a phase in Diplomacy for {power_name}.
GAME STATE
Year: {current_year}
Phase: {current_phase}
Board State:
{board_state_str}
PHASE SUMMARY ({current_phase}):
{phase_summary}
CURRENT STATUS
Goals:
{current_goals}
Relationships with other powers ({other_powers}):
{current_relationships}
TASK
Analyze the phase summary and game state to update your relationships and goals.
IMPORTANT RULES
1. Update relationships for ALL powers in {other_powers}
2. Use ONLY these relationship values: Enemy, Unfriendly, Neutral, Friendly, Ally
3. Make goals specific and actionable
4. Base analysis on actual events, not assumptions
Please respond in two parts:
1. REASONING: First, explain your analysis of what happened this phase. Which powers acted as expected? Who surprised you? What new threats or opportunities have emerged? How do the results affect your strategic position?
2. UPDATES: Then provide:
- Your updated assessment of relationships with ALL other powers
- Your updated goals (2-4 specific, actionable goals based on the current situation)

View file

@ -0,0 +1,22 @@
DIPLOMATIC CONTEXT
You are {power_name} in a game of Diplomacy.
Current Phase: {current_phase}
YOUR STRATEGIC FRAMEWORK
Goals:
{agent_goals}
Current Relationships:
{agent_relationships}
DIPLOMATIC SITUATION
Recent Negotiations:
{recent_private_diary}
Messages This Round:
{messages_this_round}
OTHER POWERS' STATUS
Active Powers: {active_powers}
{eliminated_status}

View file

@ -0,0 +1,15 @@
NEGOTIATION MESSAGES
TASK
Generate one or more (preferably several) strategic messages to advance your interests.
Always prioritize responding to the messages in the "RECENT MESSAGES REQUIRING YOUR ATTENTION" section.
Maintain consistent conversation threads (unless you are choosing to ignore).
Please respond in two parts:
1. REASONING: First, explain your diplomatic strategy for this round. Who are you trying to influence and why? Which messages require responses? What deals are you proposing or accepting? Who might you be deliberately ignoring and why?
2. MESSAGES: Then, list each message you want to send. For each message, clearly indicate:
- Whether it's a global message (visible to all) or private (to a specific power)
- If private, who the recipient is
- The content of your message

View file

@ -0,0 +1,7 @@
You are the agent for {power_name} in a game of Diplomacy at the very start (Spring 1901). Analyze the initial board position and suggest 2-3 strategic high-level goals for the early game. Consider your power's strengths, weaknesses, and neighbors. Also, provide an initial assessment of relationships with other powers. IMPORTANT: For each relationship, you MUST use exactly one of the following labels: {allowed_labels_str}.
Please respond in two parts:
1. REASONING: First, explain your strategic analysis of the starting position.
2. STRATEGY: Then, provide your 2-3 strategic high-level goals and your initial assessment of relationships with other powers.

View file

@ -0,0 +1,39 @@
NEGOTIATION SUMMARY REQUEST
Power: {power_name}
Phase: {current_phase}
MESSAGES THIS ROUND
{messages_this_round}
{ignored_messages_context}
CURRENT STATUS
Goals:
{agent_goals}
Relationships:
{agent_relationships}
Game State:
{board_state_str}
TASK
Analyze the negotiations, goals, relationships, and game state to:
1. Summarize key outcomes and agreements
2. State your specific intents for {current_phase}, including moves you have agreed to in negotiations and whether you intend to fulfil them.
3. Update relationships as needed (Enemy, Unfriendly, Neutral, Friendly, Ally)
4. Important: You will not see the full negotiation log in the order decision phase, so you must transmit key information about the negotiations to your future self via this summary.
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
Please respond in two parts:
1. REASONING: First, explain your analysis of the negotiations. What did each power communicate or fail to communicate? What do their messages (or silence) reveal about their intentions? How does this affect your strategic position?
2. NEGOTIATION SUMMARY: Then provide:
- A summary of key outcomes from the negotiations
- Your strategic intent for upcoming orders (be specific about agreed moves and whether you'll honor them)
- Your current assessment of relationships with all powers (reflecting any changes from negotiations)

View file

@ -0,0 +1,25 @@
ORDER DIARY ENTRY
Power: {power_name}
Phase: {current_phase}
ORDERS ISSUED
{orders_list_str}
CURRENT STATUS
Game State:
{board_state_str}
Goals:
{agent_goals}
Relationships:
{agent_relationships}
TASK
Write a diary entry analyzing your orders for this turn.
Please respond in two parts:
1. REASONING: First, explain your strategic analysis of the current situation. What threats and opportunities do you see? How do your relationships with other powers influence your decisions? What are you trying to achieve this turn?
2. ORDER SUMMARY: Then, provide a clear summary of the orders you submitted and explain why you chose these specific moves. How do they advance your strategic goals? What risks are you taking?

View file

@ -0,0 +1,25 @@
# Primary Objective
Control 18 supply centers. Nothing else will do.
# Critical Rules
1. The possible orders section shows your units' allowed adjustment orders
2. Dual-coast provinces (STP, SPA, BUL) require coast specification:
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
- Example: 'F STP/NC B'
- Only fleet builds need coast specification.
# Adjustment Phase Orders:
You have two main order types in the adjustment phase:
Build: '[UnitType] [Location] B'
e.g. 'A PAR B', 'F LON B'
Disband: '[UnitType] [Location] D'
e.g. 'A PAR D', 'F LON D'
Your Task:
Analyze the adjustment situation and decide which units to build or disband.
Please respond in two parts:
1. REASONING: First, explain your adjustment decisions. What is your unit count vs supply center count? Where should you build for maximum strategic impact? Which units should be disbanded if necessary?
2. ADJUSTMENT ORDERS: Then, provide all build/disband orders needed.

View file

@ -0,0 +1,18 @@
# Primary Objective
Control 18 supply centers. Nothing else will do.
# Critical Rules
1. The possible orders section shows your units' allowed moves & supports of your own units.
2. The possible orders section does *not* list possible supports for other powers' units; you can work these out yourself by looking at the units that are adjacent to your own.
3. If your goal is to *take* a province, give exactly one move order on that province and any additional support from other units must be properly formatted support orders.
4. Dual-coast provinces (STP, SPA, BUL) require coast specification:
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
- Example: 'F SPA/SC - MAO'
- Only fleets need coast specification.
Your Task:
Please respond in two parts:
1. REASONING: First, provide comprehensive reasoning about your move decisions. What are your immediate objectives? Which supply centers are you targeting? How will you deal with threats? What coordinated moves are you planning? Consider all your units and their best uses.
2. ORDERS: Then, list each order you want to submit, one per line. Be precise with unit types (A/F) and location codes. Aim to return an order for each of your units.

View file

@ -0,0 +1,23 @@
# Primary Objective
Control 18 supply centers. Nothing else will do.
# Critical Rules
1. The possible orders section shows where your dislodged units can retreat.
2. Units cannot retreat to:
- The province they were dislodged from
- A province occupied after this turn's moves
- A province where a standoff occurred
3. If no valid retreat exists, the unit must disband.
4. Dual-coast provinces (STP, SPA, BUL) require coast specification:
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
- Example: 'F SPA/SC - MAO'
- Only fleet retreat orders need coast specification.
Your Task:
Analyze the retreat situation and decide on the best retreat or disband orders for your dislodged units.
Please respond in two parts:
1. REASONING: First, explain your retreat decisions. Which units are dislodged? Where can they retreat? Is it better to retreat or disband? Consider the strategic implications of each choice.
2. RETREAT ORDERS: Then, provide a retreat or disband order for each dislodged unit.

View file

@ -0,0 +1,32 @@
You are analyzing the results of a phase in Diplomacy for {power_name}.
GAME STATE
Year: {current_year}
Phase: {current_phase}
Board State:
{board_state_str}
PHASE SUMMARY ({current_phase}):
{phase_summary}
CURRENT STATUS
Relationships with other powers ({other_powers}):
{current_relationships}
TASK
Analyze the phase summary and game state to update your relationships and goals.
IMPORTANT RULES
1. Update relationships for ALL powers in {other_powers}
2. Use ONLY these relationship values: Enemy, Unfriendly, Neutral, Friendly, Ally
3. Make goals specific and actionable
4. Base analysis on actual events, not assumptions
Please respond in two parts:
1. REASONING: First, explain your analysis of what happened this phase. Which powers acted as expected? Who surprised you? What new threats or opportunities have emerged? How do the results affect your strategic position?
2. UPDATES: Then provide:
- Your updated assessment of relationships with ALL other powers
- Your updated goals (2-4 specific, actionable goals based on the current situation)

View file

@ -76,13 +76,13 @@ def assign_models_to_powers() -> Dict[str, str]:
# TEST MODELS
return {
"AUSTRIA": "openrouter-google/gemini-2.5-flash-preview-05-20",
"ENGLAND": "openrouter-google/gemini-2.5-flash-preview-05-20",
"FRANCE": "openrouter-google/gemini-2.5-flash-preview-05-20",
"GERMANY": "openrouter-google/gemini-2.5-flash-preview-05-20",
"ITALY": "openrouter-google/gemini-2.5-flash-preview-05-20",
"RUSSIA": "openrouter-google/gemini-2.5-flash-preview-05-20",
"TURKEY": "openrouter-google/gemini-2.5-flash-preview-05-20",
"AUSTRIA": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"ENGLAND": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"FRANCE": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"GERMANY": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"ITALY": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"RUSSIA": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
"TURKEY": "openrouter-mistralai/mistral-small-3.2-24b-instruct",
}
@ -298,7 +298,12 @@ def load_prompt(filename: str, prompts_dir: Optional[str] = None) -> str:
if os.path.isabs(filename): # rule 1
prompt_path = filename
elif os.path.dirname(filename): # rule 2 (has slash)
prompt_path = filename # relative but already complete
# If it's a relative path with directory, join with prompts_dir if provided
if prompts_dir:
prompt_path = os.path.join(prompts_dir, filename)
else:
default_dir = os.path.join(os.path.dirname(__file__), "prompts")
prompt_path = os.path.join(default_dir, filename)
elif prompts_dir: # rule 3
prompt_path = os.path.join(prompts_dir, filename)
else: # rule 4
@ -333,13 +338,15 @@ def log_llm_response(
if log_dir: # Ensure log_dir is not empty (e.g., if path is just a filename)
os.makedirs(log_dir, exist_ok=True)
# Check if file exists to write header
file_exists = os.path.isfile(log_file_path)
# Check if file exists and has content to determine if we need headers
file_exists = os.path.isfile(log_file_path) and os.path.getsize(log_file_path) > 0
with open(log_file_path, "a", newline="", encoding="utf-8") as csvfile:
# Added "raw_input" to fieldnames
fieldnames = ["model", "power", "phase", "response_type", "raw_input", "raw_response", "success"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer = csv.DictWriter(csvfile, fieldnames=fieldnames,
quoting=csv.QUOTE_ALL, # Quote all fields to handle commas and newlines
escapechar='\\') # Use backslash for escaping
if not file_exists:
writer.writeheader() # Write header only if file is new

View file

@ -20,6 +20,7 @@ dependencies = [
"pytest-xdist>=3.7.0",
"python-dateutil>=2.9.0.post0",
"pytz>=2025.2",
"scipy>=1.16.0",
"seaborn>=0.13.2",
"sphinx>=8.2.3",
"sphinx-copybutton>=0.5.2",

1350
uv.lock generated

File diff suppressed because it is too large Load diff