AI_Diplomacy/ai_diplomacy/prompt_constructor.py
2025-06-22 18:05:07 +10:00

154 lines
No EOL
6.1 KiB
Python

"""
Module for constructing prompts for LLM interactions in the Diplomacy game.
"""
import logging
from typing import Dict, List, Optional, Any # Added Any for game type placeholder
from .utils import load_prompt
from .possible_order_context import generate_rich_order_context
from .game_history import GameHistory # Assuming GameHistory is correctly importable
# placeholder for diplomacy.Game to avoid circular or direct dependency if not needed for typehinting only
# from diplomacy import Game # Uncomment if 'Game' type hint is crucial and available
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Or inherit from parent logger
def build_context_prompt(
game: Any, # diplomacy.Game object
board_state: dict,
power_name: str,
possible_orders: Dict[str, List[str]],
game_history: GameHistory,
agent_goals: Optional[List[str]] = None,
agent_relationships: Optional[Dict[str, str]] = None,
agent_private_diary: Optional[str] = None,
prompts_dir: Optional[str] = None,
) -> str:
"""Builds the detailed context part of the prompt.
Args:
game: The game object.
board_state: Current state of the board.
power_name: The name of the power for whom the context is being built.
possible_orders: Dictionary of possible orders.
game_history: History of the game (messages, etc.).
agent_goals: Optional list of agent's goals.
agent_relationships: Optional dictionary of agent's relationships with other powers.
agent_private_diary: Optional string of agent's private diary.
prompts_dir: Optional path to the prompts directory.
Returns:
A string containing the formatted context.
"""
context_template = load_prompt("context_prompt.txt", prompts_dir=prompts_dir)
# === Agent State Debug Logging ===
if agent_goals:
logger.debug(f"Using goals for {power_name}: {agent_goals}")
if agent_relationships:
logger.debug(f"Using relationships for {power_name}: {agent_relationships}")
if agent_private_diary:
logger.debug(f"Using private diary for {power_name}: {agent_private_diary[:200]}...")
# ================================
# Get our units and centers (not directly used in template, but good for context understanding)
# units_info = board_state["units"].get(power_name, [])
# centers_info = board_state["centers"].get(power_name, [])
# Get the current phase
year_phase = board_state["phase"] # e.g. 'S1901M'
possible_orders_context_str = generate_rich_order_context(game, power_name, possible_orders)
messages_this_round_text = game_history.get_messages_this_round(
power_name=power_name,
current_phase_name=year_phase
)
if not messages_this_round_text.strip():
messages_this_round_text = "\n(No messages this round)\n"
# Separate active and eliminated powers for clarity
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()]
# Build units representation with power status
units_lines = []
for p, u in board_state["units"].items():
if game.powers[p].is_eliminated():
units_lines.append(f" {p}: {u} [ELIMINATED]")
else:
units_lines.append(f" {p}: {u}")
units_repr = "\n".join(units_lines)
# Build centers representation with power status
centers_lines = []
for p, c in board_state["centers"].items():
if game.powers[p].is_eliminated():
centers_lines.append(f" {p}: {c} [ELIMINATED]")
else:
centers_lines.append(f" {p}: {c}")
centers_repr = "\n".join(centers_lines)
context = context_template.format(
power_name=power_name,
current_phase=year_phase,
all_unit_locations=units_repr,
all_supply_centers=centers_repr,
messages_this_round=messages_this_round_text,
possible_orders=possible_orders_context_str,
agent_goals="\n".join(f"- {g}" for g in agent_goals) if agent_goals else "None specified",
agent_relationships="\n".join(f"- {p}: {s}" for p, s in agent_relationships.items()) if agent_relationships else "None specified",
agent_private_diary=agent_private_diary if agent_private_diary else "(No diary entries yet)",
)
return context
def construct_order_generation_prompt(
system_prompt: str,
game: Any, # diplomacy.Game object
board_state: dict,
power_name: str,
possible_orders: Dict[str, List[str]],
game_history: GameHistory,
agent_goals: Optional[List[str]] = None,
agent_relationships: Optional[Dict[str, str]] = None,
agent_private_diary_str: Optional[str] = None,
prompts_dir: Optional[str] = None,
) -> str:
"""Constructs the final prompt for order generation.
Args:
system_prompt: The base system prompt for the LLM.
game: The game object.
board_state: Current state of the board.
power_name: The name of the power for whom the prompt is being built.
possible_orders: Dictionary of possible orders.
game_history: History of the game (messages, etc.).
agent_goals: Optional list of agent's goals.
agent_relationships: Optional dictionary of agent's relationships with other powers.
agent_private_diary_str: Optional string of agent's private diary.
prompts_dir: Optional path to the prompts directory.
Returns:
A string containing the complete prompt for the LLM.
"""
# Load prompts
_ = load_prompt("few_shot_example.txt", prompts_dir=prompts_dir) # Loaded but not used, as per original logic
instructions = load_prompt("order_instructions.txt", prompts_dir=prompts_dir)
# Build the context prompt
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,
prompts_dir=prompts_dir,
)
final_prompt = system_prompt + "\n\n" + context + "\n\n" + instructions
return final_prompt