fixes for state updates

This commit is contained in:
sam-paech 2025-07-10 21:52:22 +10:00
parent 3d591a11d1
commit 1f154a7073
7 changed files with 44 additions and 213 deletions

View file

@ -917,9 +917,6 @@ class DiplomacyAgent:
logger.warning(f"[{power_name}] No summary available for previous phase {last_phase_name}. Skipping state update.")
return
# == Fix: Use board_state parameter ==
possible_orders = game.get_all_possible_orders()
# Get formatted diary for context
formatted_diary = self.format_private_diary_for_prompt()
@ -927,25 +924,19 @@ class DiplomacyAgent:
game=game,
board_state=board_state, # Use provided board_state parameter
power_name=power_name,
possible_orders=possible_orders, # Pass possible_orders
possible_orders=None, # don't include possible orders in the state update prompt
game_history=game_history, # Pass game_history
agent_goals=self.goals,
agent_goals=[], # pass empty goals to force model to regenerate goals each phase
agent_relationships=self.relationships,
agent_private_diary=formatted_diary, # Pass formatted diary
prompts_dir=self.prompts_dir,
include_messages=True,
display_phase=last_phase_name
)
# Add previous phase summary to the information provided to the LLM
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 = "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"
# 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"
@ -953,11 +944,9 @@ class DiplomacyAgent:
power_name=power_name,
current_year=current_year,
current_phase=last_phase_name, # Analyze the phase that just ended
board_state_str=board_state_str,
board_state_str=context,
phase_summary=last_phase_summary, # Use provided phase_summary
other_powers=str(other_powers), # Pass as string representation
current_goals="\n".join([f"- {g}" for g in self.goals]) if self.goals else "None",
current_relationships=str(self.relationships) if self.relationships else "None",
)
logger.debug(f"[{power_name}] State update prompt:\n{prompt}")

View file

@ -943,9 +943,12 @@ class OpenAIResponsesClient(BaseModelClient):
This client makes direct HTTP requests to the v1/responses endpoint.
"""
def __init__(self, model_name: str, prompts_dir: Optional[str] = None):
def __init__(self, model_name: str, prompts_dir: Optional[str] = None, api_key: Optional[str] = None):
super().__init__(model_name, prompts_dir=prompts_dir)
self.api_key = os.environ.get("OPENAI_API_KEY")
if api_key:
self.api_key = api_key
else:
self.api_key = os.environ.get("OPENAI_API_KEY")
if not self.api_key:
raise ValueError("OPENAI_API_KEY environment variable is required")
self.base_url = "https://api.openai.com/v1/responses"
@ -966,8 +969,8 @@ class OpenAIResponsesClient(BaseModelClient):
payload = {
"model": self.model_name,
"input": full_prompt,
"temperature": temperature,
"max_tokens": self.max_tokens,
#"temperature": temperature,
#"max_tokens": self.max_tokens,
}
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"}
@ -1036,7 +1039,7 @@ class OpenRouterClient(BaseModelClient):
logger.debug(f"[{self.model_name}] Initialized OpenRouter client")
async def generate_response(self, prompt: str, temperature: float = 0.0, inject_random_seed: bool = True) -> str:
async def generate_response(self, prompt: str, temperature: float = 0.5, inject_random_seed: bool = True) -> str:
"""Generate a response using OpenRouter with robust error handling."""
try:
# Append the call to action to the user's prompt

View file

@ -42,6 +42,7 @@ def build_context_prompt(
agent_private_diary: Optional[str] = None,
prompts_dir: Optional[str] = None,
include_messages: Optional[bool] = True,
display_phase: Optional[str] = None,
) -> str:
"""Builds the detailed context part of the prompt.
@ -79,10 +80,13 @@ def build_context_prompt(
# Decide which context builder to use.
_use_simple = config.SIMPLE_PROMPTS
if _use_simple:
possible_orders_context_str = generate_rich_order_context(game, power_name, possible_orders)
if possible_orders is None:
possible_orders_context_str = "(not relevant in this context)"
else:
possible_orders_context_str = generate_rich_order_context_xml(game, power_name, possible_orders)
if _use_simple:
possible_orders_context_str = generate_rich_order_context(game, power_name, possible_orders)
else:
possible_orders_context_str = generate_rich_order_context_xml(game, power_name, possible_orders)
if include_messages:
messages_this_round_text = game_history.get_messages_this_round(power_name=power_name, current_phase_name=year_phase)
@ -133,9 +137,12 @@ def build_context_prompt(
if "{order_history}" in context_template:
context_template = context_template.replace("{order_history}", order_history_str)
if display_phase is None:
display_phase = year_phase
context = context_template.format(
power_name=power_name,
current_phase=year_phase,
current_phase=display_phase,
all_unit_locations=units_repr,
all_supply_centers=centers_repr,
messages_this_round=messages_this_round_text,

View file

@ -4,7 +4,6 @@ Phase: {current_phase}
MESSAGES THIS ROUND
{messages_this_round}
{ignored_messages_context}
CURRENT STATUS
Goals:
@ -23,71 +22,15 @@ Analyze the negotiations, goals, relationships, and game state to:
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
RESPONSE FORMAT
Return ONLY a JSON object with this structure:
{
"negotiation_summary": "Key outcomes from negotiations",
"intent": "Strategic intent for upcoming orders",
"intent": "Specific intent for upcoming orders",
"updated_relationships": {
"POWER_NAME": "Enemy|Unfriendly|Neutral|Friendly|Ally"
}
}
Do not include any text outside the JSON. Reminder: If you need to quote something, only use single quotes in the actual messages so as not to interfere with the JSON structure.
EXAMPLES:
Scenario 1: As France, after discussing a joint move against Germany with England, while Italy seems to be posturing aggressively in Piedmont.
{
"negotiation_summary": "Reached a tentative agreement with England to support their fleet into Belgium (BEL) if they support my army into Ruhr (RUH). Italy's messages are vague but their army in Piedmont (PIE) is concerning; they claim it's defensive against Austria but it also threatens Marseilles (MAR). Russia remains silent. Austria and Turkey are focused on each other.",
"intent": "Secure Ruhr with English support. Hold Marseilles defensively. Probe Italy's intentions further. If England upholds their end, improve relations. If Italy moves on MAR, downgrade relations severely.",
"updated_relationships": {
"ENGLAND": "Friendly",
"GERMANY": "Enemy",
"ITALY": "Unfriendly",
"AUSTRIA": "Neutral",
"RUSSIA": "Neutral",
"TURKEY": "Neutral"
}
}
Scenario 2: As Turkey, after Germany proposed an alliance against Russia, but France also offered a non-aggression pact and hinted at concerns about Austria.
{
"negotiation_summary": "Germany is keen on an anti-Russian alliance, offering support into Sevastopol (SEV) if I attack. France proposed a mutual non-aggression pact and expressed worry about Austrian expansion in the Balkans, which aligns with my concerns. England is distant. Italy seems focused on France.",
"intent": "Prioritize securing Black Sea (BLA) and consider options against Russia, but German support needs to be concrete. Maintain neutrality with France for now, as their non-aggression pact could be useful if Austria becomes a larger threat. Try to confirm German commitment before moving on Russia. Delay any aggressive moves against Austria until my position is stronger.",
"updated_relationships": {
"GERMANY": "Friendly",
"RUSSIA": "Unfriendly",
"FRANCE": "Neutral",
"ENGLAND": "Neutral",
"ITALY": "Neutral",
"AUSTRIA": "Unfriendly"
}
}
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.

View file

@ -9,9 +9,6 @@ CURRENT STATUS
Game State:
{board_state_str}
Goals:
{agent_goals}
Relationships:
{agent_relationships}
@ -21,7 +18,7 @@ Write a concise diary note summarizing your orders.
RESPONSE FORMAT
Return ONLY a JSON object with this structure:
{
"order_summary": "Brief summary of orders and strategic intent"
"order_summary": "Brief summary of orders"
}
Do not include any text outside the JSON.

View file

@ -1,4 +1,4 @@
You are analyzing the results of a phase in Diplomacy for {power_name}.
You are analyzing the results of a phase in Diplomacy. Your power is {power_name}.
GAME STATE
Year: {current_year}
@ -6,14 +6,6 @@ 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.
@ -21,119 +13,18 @@ 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
5. Return ONLY valid JSON - no text before or after
4. Return ONLY valid JSON - no text before or after
Example Response Structure:
Response Structure:
{{
"reasoning": "Brief explanation of your analysis",
"reasoning": "Brief reasoning about the update",
"relationships": {{
"FRANCE": "Neutral",
"GERMANY": "Unfriendly",
"RUSSIA": "Enemy"
"POWER NAME": "Relationship Status",
...
}},
"goals": [
"Specific goal 1",
"Specific goal 2"
"Specific goal 2",
...
]
}}
EXAMPLE SCENARIOS
1. After Cooperation:
{{
"reasoning": "Austria helped take Warsaw. Russia attacked Prussia.",
"relationships": {{
"AUSTRIA": "Ally",
"RUSSIA": "Enemy",
"TURKEY": "Neutral",
"ITALY": "Unfriendly",
"FRANCE": "Neutral"
}},
"goals": [
"Hold Warsaw against Russia",
"Keep Austrian alliance",
"Block Italian expansion"
]
}}
2. After Betrayal:
{{
"reasoning": "France betrayed Channel agreement. Russia cooperating north.",
"relationships": {{
"FRANCE": "Enemy",
"RUSSIA": "Friendly",
"GERMANY": "Unfriendly",
"ITALY": "Neutral",
"AUSTRIA": "Neutral"
}},
"goals": [
"Counter French fleet",
"Secure Norway with Russia",
"Build London fleet"
]
}}
3. After Builds:
{{
"reasoning": "Naval buildup in north. Russia threatening.",
"relationships": {{
"RUSSIA": "Enemy",
"GERMANY": "Unfriendly",
"FRANCE": "Neutral",
"AUSTRIA": "Neutral",
"TURKEY": "Neutral"
}},
"goals": [
"Control northern waters",
"Take Denmark first",
"Find anti-Russia ally"
]
}}
4. As England, after a failed attack on Belgium (BEL) which was occupied by France, supported by Germany. Russia moved into Sweden (SWE) uncontested. Austria and Italy skirmished over Trieste (TRI). Turkey was quiet.
{{
"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."
]
}}
5. As Russia, after successfully negotiating passage through Black Sea (BLA) with Turkey to take Rumania (RUM). Germany moved into Silesia (SIL), threatening Warsaw (WAR). Austria and France exchanged hostile messages but made no direct moves against each other. England built a new fleet in London (LON). Italy seems focused west.
{{
"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."
]
}}
JSON FORMAT
Return a single JSON object with these exact keys:
- reasoning: String explaining your updates
- relationships: Object mapping power names to relationship values
- goals: Array of specific goal strings
RETURN JSON BELOW ONLY (DO NOT PREPEND WITH ```json or ``` or any other text)
}}

View file

@ -377,7 +377,7 @@ async def main():
# what we record for prompt/history purposes
submitted_orders_this_phase[p_name] = valid + invalid
# optional: diary entry only for the orders we tried to submit
# diary entry only for the orders we tried to submit
if valid or invalid:
await agents[p_name].generate_order_diary_entry(
game, valid + invalid, llm_log_file_path
@ -433,13 +433,14 @@ async def main():
await asyncio.gather(*consolidation_tasks, return_exceptions=True)
# Agent State Updates
current_board_state = game.get_state()
state_update_tasks = [
agent.analyze_phase_and_update_state(game, current_board_state, phase_summary, game_history, llm_log_file_path)
for agent in agents.values() if not game.powers[agent.power_name].is_eliminated()
]
if state_update_tasks:
await asyncio.gather(*state_update_tasks, return_exceptions=True)
if current_short_phase.endswith("M"):
current_board_state = game.get_state()
state_update_tasks = [
agent.analyze_phase_and_update_state(game, current_board_state, phase_summary, game_history, llm_log_file_path)
for agent in agents.values() if not game.powers[agent.power_name].is_eliminated()
]
if state_update_tasks:
await asyncio.gather(*state_update_tasks, return_exceptions=True)
# --- 4f. Save State At End of Phase ---
save_game_state(game, agents, game_history, game_file_path, run_config, completed_phase)