DarshanScripts commited on
Commit
0eba6a4
·
verified ·
1 Parent(s): 98aec3b

Upload stratego\prompt_manager.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. stratego//prompt_manager.py +191 -0
stratego//prompt_manager.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PromptManager: Simple prompt versioning and updates.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ import os
7
+ import json
8
+ import shutil
9
+ from datetime import datetime
10
+
11
+
12
+ class PromptManager:
13
+ """
14
+ Manages prompt versions and updates.
15
+
16
+ Files:
17
+ prompts/current_prompt.txt (active prompt)
18
+ prompts/base_prompt.txt (original/fallback)
19
+ logs/fullgame_history.json (game log with prompts)
20
+ """
21
+
22
+ DEFAULT_PROMPT = """You are a strategic Stratego AI. The game rules are already provided.
23
+
24
+ STRATEGY FOCUS:
25
+ - Vary your moves - don't repeat the same move multiple times
26
+ - Use different pieces, not just one piece repeatedly
27
+ - Be cautious when attacking unknown pieces
28
+ - Protect high-value pieces (Marshal, General)
29
+ - Use Scouts to gather information before committing valuable pieces
30
+
31
+ OUTPUT:
32
+ - Respond with ONLY the move in format [SRC DST]
33
+ - No explanations, no reasoning, just the move
34
+ - Example: [D4 E4]"""
35
+
36
+ def __init__(self, prompts_dir: str, logs_dir: str = "logs"):
37
+ self.prompts_dir = prompts_dir
38
+ self.logs_dir = logs_dir
39
+ self.current_path = os.path.join(prompts_dir, "current_prompt.txt")
40
+ self.base_path = os.path.join(prompts_dir, "base_prompt.txt")
41
+ self.history_path = os.path.join(logs_dir, "fullgame_history.json")
42
+
43
+ os.makedirs(prompts_dir, exist_ok=True)
44
+ os.makedirs(logs_dir, exist_ok=True)
45
+ self._ensure_base_prompt()
46
+
47
+ def _ensure_base_prompt(self):
48
+ """Create base prompt if it doesn't exist."""
49
+ if not os.path.exists(self.base_path):
50
+ with open(self.base_path, "w", encoding="utf-8") as f:
51
+ f.write(self.DEFAULT_PROMPT)
52
+
53
+ def get_base_prompt(self) -> str:
54
+ """Get the base prompt (without any game-specific additions)."""
55
+ if os.path.exists(self.base_path):
56
+ with open(self.base_path, "r", encoding="utf-8") as f:
57
+ return f.read()
58
+ return self.DEFAULT_PROMPT
59
+
60
+ @staticmethod
61
+ def extract_improvements(prompt_text: str) -> list[str]:
62
+ """Pull the strategic improvements section (lines starting with bullets)."""
63
+ if not prompt_text:
64
+ return []
65
+ lines = prompt_text.splitlines()
66
+ improvements: list[str] = []
67
+ in_section = False
68
+ for line in lines:
69
+ if line.strip().startswith("--- STRATEGIC IMPROVEMENTS"):
70
+ in_section = True
71
+ continue
72
+ if in_section:
73
+ stripped = line.strip()
74
+ if stripped.startswith(("??", "-", "•", "*")):
75
+ cleaned = stripped.lstrip("??•-* ").strip()
76
+ if cleaned:
77
+ improvements.append(f"• {cleaned}")
78
+ return improvements
79
+
80
+ @staticmethod
81
+ def merge_improvements(existing: list[str], new: list[str], limit: int = 20) -> list[str]:
82
+ """Deduplicate improvements while keeping order; cap length to avoid prompt bloat."""
83
+ merged: list[str] = []
84
+ seen = set()
85
+
86
+ def _norm(s: str) -> str:
87
+ s_clean = s.lstrip("•-*? ").strip().lower()
88
+ return " ".join(s_clean.split())
89
+
90
+ for item in (existing or []) + (new or []):
91
+ if not item:
92
+ continue
93
+ key = _norm(item)
94
+ if key in seen:
95
+ continue
96
+ seen.add(key)
97
+ merged.append(item.strip())
98
+ if len(merged) >= limit:
99
+ break
100
+ return merged
101
+
102
+ @staticmethod
103
+ def build_prompt(base_prompt: str, improvements: list[str]) -> str:
104
+ """Reconstruct prompt with a merged improvements section."""
105
+ section = ""
106
+ if improvements:
107
+ section = "\n\n--- STRATEGIC IMPROVEMENTS (from past games) ---\n" + "\n".join(improvements)
108
+ return base_prompt.rstrip() + section
109
+
110
+ def get_current_prompt(self) -> str:
111
+ """Get the current active prompt (base + last game feedback)."""
112
+ if os.path.exists(self.current_path):
113
+ with open(self.current_path, "r", encoding="utf-8") as f:
114
+ return f.read()
115
+ return self.get_base_prompt()
116
+
117
+ def update_prompt(self, new_prompt: str, reason: str = "", models: list = None,
118
+ mistakes: list = None, game_duration_seconds: float = None,
119
+ total_turns: int = None, winner: int = None):
120
+ """
121
+ Update the current prompt and log the change.
122
+
123
+ Args:
124
+ new_prompt: The new prompt text
125
+ reason: Why the prompt was updated
126
+ models: List of models that played the game
127
+ mistakes: List of mistake strings that were added
128
+ game_duration_seconds: How long the game took in seconds
129
+ total_turns: Total number of turns in the game
130
+ winner: Player ID who won (0 or 1), None for draw
131
+ """
132
+ # Backup current
133
+ if os.path.exists(self.current_path):
134
+ backup = self.current_path.replace(".txt", "_prev.txt")
135
+ shutil.copy2(self.current_path, backup)
136
+
137
+ # Save new prompt
138
+ with open(self.current_path, "w", encoding="utf-8") as f:
139
+ f.write(new_prompt)
140
+
141
+ # Log the update
142
+ self._log_update(reason, new_prompt, models, mistakes, game_duration_seconds, total_turns, winner)
143
+
144
+ def _log_update(self, reason: str, prompt_text: str = "", models: list = None,
145
+ mistakes: list = None, game_duration_seconds: float = None,
146
+ total_turns: int = None, winner: int = None):
147
+ """Log game and prompt update to history with full details."""
148
+ history = []
149
+ if os.path.exists(self.history_path):
150
+ try:
151
+ with open(self.history_path, "r", encoding="utf-8") as f:
152
+ history = json.load(f)
153
+ except:
154
+ history = []
155
+
156
+ # Format duration nicely
157
+ duration_str = ""
158
+ if game_duration_seconds is not None:
159
+ minutes = int(game_duration_seconds // 60)
160
+ seconds = int(game_duration_seconds % 60)
161
+ duration_str = f"{minutes}m {seconds}s"
162
+
163
+ entry = {
164
+ "game_number": len(history) + 1,
165
+ "timestamp": datetime.now().isoformat(),
166
+ "models": models or [],
167
+ "winner": winner,
168
+ "total_turns": total_turns,
169
+ "game_duration": duration_str,
170
+ "game_duration_seconds": round(game_duration_seconds, 2) if game_duration_seconds else None,
171
+ "mistakes_detected": len(mistakes) if mistakes else 0,
172
+ "mistakes_added": mistakes or [],
173
+ "reason": reason,
174
+ "prompt_text": prompt_text,
175
+ "prompt_length": len(prompt_text)
176
+ }
177
+ history.append(entry)
178
+
179
+ # Keep last 50 entries
180
+ history = history[-50:]
181
+
182
+ with open(self.history_path, "w", encoding="utf-8") as f:
183
+ json.dump(history, f, indent=2)
184
+
185
+ def reset_to_base(self):
186
+ """Reset current prompt to base prompt."""
187
+ if os.path.exists(self.base_path):
188
+ shutil.copy2(self.base_path, self.current_path)
189
+ with open(self.base_path, "r", encoding="utf-8") as f:
190
+ base_text = f.read()
191
+ self._log_update("Reset to base prompt", base_text, [], [], None, None, None)