mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
fixes for state updates
This commit is contained in:
parent
3d591a11d1
commit
1f154a7073
7 changed files with 44 additions and 213 deletions
|
|
@ -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}")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
}}
|
||||
17
lm_game.py
17
lm_game.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue