# 🔄 AI System Refactoring Plan ## תוכנית ריפקטור מערכת ה-AI **תאריך:** January 2026 **סטטוס:** 📋 Planning **גרסה:** 1.0 --- ## 📋 תוכן עניינים 1. [סקירת המצב הנוכחי](#-סקירת-המצב-הנוכחי) 2. [הבעיות שצריך לפתור](#-הבעיות-שצריך-לפתור) 3. [הארכיטקטורה החדשה](#-הארכיטקטורה-החדשה) 4. [פירוט הרכיבים](#-פירוט-הרכיבים) 5. [תוכנית מימוש](#-תוכנית-מימוש) 6. [מיגרציה והגירה](#-מיגרציה-והגירה) 7. [בדיקות](#-בדיקות) --- ## 🔴 סקירת המצב הנוכחי ### מבנה קיים ``` ┌─────────────────────────────────────────────────────────────────────┐ │ המצב הנוכחי (בלאגן) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ GameManager │ │ │ │ │ ▼ saves state to file │ │ current_state.json │ │ │ │ │ ▼ watches file changes │ │ play_with_prompts.py (background thread) │ │ │ │ │ ▼ calls │ │ generate_prompts_from_state.py │ │ │ │ │ ├──► generate_what_happened_message() (guesses from state!) │ │ │ │ │ ▼ saves │ │ prompt_N.json files │ │ │ │ │ ▼ watches files │ │ test_ai_live.py │ │ │ │ │ ▼ sends to │ │ LLM (Gemini) │ │ │ │ │ ▼ ??? │ │ How do responses get back to game? ← 🚨 BROKEN │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### קבצים קיימים | קובץ | מיקום | תפקיד | |------|-------|-------| | `prompt_manager.py` | `pycatan/ai/` | יצירת פרומפטים | | `llm_client.py` | `pycatan/ai/` | תקשורת עם Gemini | | `state_filter.py` | `pycatan/ai/` | סינון state לשחקן | | `prompt_templates.py` | `pycatan/ai/` | תבניות ו-schemas | | `config.py` | `pycatan/ai/` | קונפיגורציה | | `generate_prompts_from_state.py` | `examples/ai_testing/` | יצירת פרומפטים מקובץ | | `test_ai_live.py` | `examples/ai_testing/` | שליחה ל-LLM | | `play_with_prompts.py` | `examples/ai_testing/` | הרצת משחק עם AI | | `web_viewer.py` | `examples/ai_testing/` | צפייה בפרומפטים | | `user.py` | `pycatan/players/` | ממשק User מופשט | | `human_user.py` | `pycatan/players/` | מימוש לשחקן אנושי | --- ## 🚨 הבעיות שצריך לפתור ### בעיה 1: הפרדת אחריות חסרה ``` ❌ GameManager לא צריך לדעת על AI/פרומפטים ❌ יצירת פרומפטים מפוזרת במספר מקומות ❌ אין מקום מרכזי לניהול סוכני AI ``` ### בעיה 2: "מה קרה" מבוסס על ניחושים ``` ❌ generate_what_happened_message() מנסה לשחזר מה קרה מתוך state ❌ לא מדויק - חסר מידע על מה באמת התרחש ❌ GameManager יודע בדיוק מה קרה אבל המידע לא מועבר ``` ### בעיה 3: אין שליטה על מתי לשלוח פרומפטים ``` ❌ פרומפטים נשלחים על כל שינוי קובץ ❌ אין בדיקה אם כבר ממתינים לתשובה (pending) ❌ צ'אט לא מעורר שליחת פרומפטים ``` ### בעיה 4: תשובות לא חוזרות למשחק ``` ❌ אין מסלול ברור לתשובות לחזור ל-GameManager ❌ הכל עובד דרך קבצים - לא real-time ``` ### בעיה 5: זיכרון מפוזר ``` ❌ note_to_self נשמר בקבצים, לא במקום מרכזי ❌ אירועים (events) לא נשמרים לכל סוכן ❌ סיכומי צ'אט לא קיימים ``` --- ## 🟢 הארכיטקטורה החדשה ### תרשים מבנה ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ ארכיטקטורה חדשה │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ │ │ │ │ │ │ GameManager │◄──── Actions ──────│ AIManager │ │ │ │ │ │ │ │ │ │ • Game Loop │ │ • Creates Prompts │ │ │ │ • Rules │──── Events ───────►│ • Manages Agents │ │ │ │ • State │ (notify_all) │ • Handles Chat │ │ │ │ • Turn Flow │ │ • Tracks Pending │ │ │ │ │ │ • Sends to LLM │ │ │ └────────┬─────────┘ └──────────┬───────────┘ │ │ │ │ │ │ │ get_input() │ │ │ ▼ ▼ │ │ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ │ │ │ │ │ │ HumanUser │ │ AIUser (Wrapper) │ │ │ │ (CLI) │ │ │ │ │ │ │ │ • Delegates to │ │ │ │ │ │ AIManager │ │ │ └──────────────────┘ │ │ │ │ └──────────┬───────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────┐ │ │ │ │ │ │ │ AILogger │ │ │ │ │ │ │ │ • MD logs │ │ │ │ • JSON files │ │ │ │ • Web Viewer │ │ │ │ │ │ │ └──────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### עקרונות מנחים | עיקרון | פירוט | |--------|-------| | **הפרדת אחריות** | GameManager לא יודע על AI, AIManager לא יודע על חוקי המשחק | | **מקום אחד לפרומפטים** | כל יצירת הפרומפטים דרך AIManager | | **שליטה מרכזית** | `should_send_prompt()` מחליט מתי לשלוח | | **"מה קרה" מדויק** | Events מגיעים ישירות מ-GameManager | | **לוגים תואמים** | AILogger שומר על אותו פורמט קבצים | --- ## 📦 פירוט הרכיבים ### 1. AgentState - מצב סוכן יחיד ```python @dataclass class AgentState: """ מצב של סוכן AI יחיד. כל הזיכרון והמצב של סוכן ספציפי מנוהל כאן. """ # === זיהוי === player_name: str # שם השחקן ("dudu", "shon") player_id: int # מספר שחקן (0, 1, 2...) player_color: str # צבע ("Red", "Blue") # === סטטוס בקשה === pending_request: bool = False # האם ממתין לתשובה מ-LLM? last_request_time: Optional[float] = None # === זיכרון פרטי === memory: Optional[str] = None # note_to_self מתשובה אחרונה # === סיכומי צ'אט (לעתיד) === chat_summaries: List[str] = field(default_factory=list) # דוגמה: ["Turn 5: Dana agreed to trade wood for brick"] # === אירועים שקרו מאז הפרומפט האחרון === recent_events: List[Dict[str, Any]] = field(default_factory=list) # דוגמה: [{"type": "dice_roll", "message": "Rolled 6", "timestamp": 123}] # === מעקב שינויים === last_state_hash: Optional[str] = None last_prompt_time: Optional[float] = None # === סטטיסטיקות === total_requests: int = 0 total_tokens_used: int = 0 ``` **מיקום:** `pycatan/ai/agent_state.py` --- ### 2. AIManager - המנהל המרכזי ```python class AIManager: """ מנהל מרכזי לכל סוכני ה-AI. אחריות: - ניהול מצב כל הסוכנים - יצירת ושליחת פרומפטים - קבלת תשובות והמרה לפעולות - ניהול צ'אט והיסטוריה - החלטה מתי לשלוח פרומפטים """ def __init__( self, game_manager: GameManager, config: AIConfig = None, session_dir: Path = None ): """ Args: game_manager: הפניה ל-GameManager לקריאת state config: קונפיגורציה (ברירת מחדל אם None) session_dir: תיקיית session ללוגים """ self.game_manager = game_manager self.config = config or AIConfig() # רכיבים פנימיים self.prompt_manager = PromptManager(self.config) self.llm_client = self._create_llm_client() self.logger = AILogger(session_dir) # מצב self.agents: Dict[str, AgentState] = {} self.chat_history: List[Dict] = [] self.max_chat_history: int = 20 # === ניהול סוכנים === def register_agent(self, player_name: str, player_id: int, player_color: str = ""): """רושם סוכן AI חדש""" def unregister_agent(self, player_name: str): """מסיר סוכן (אם שחקן עזב)""" # === נקודת הכניסה הראשית === def process_agent_turn(self, player_name: str) -> Action: """ מעבד תור של סוכן AI. זו הפונקציה שנקראת מ-AIUser.get_input() Returns: Action: הפעולה שהסוכן בחר לבצע """ # === קבלת אירועים === def on_game_event(self, event_type: str, message: str, affected_players: List[int] = None): """ נקרא כשמתרחש אירוע במשחק. נקרא מ-AIUser.notify_game_event() עבור כל סוכן. """ def on_chat_message(self, from_player: str, message: str): """ נקרא כשסוכן שולח הודעת צ'אט. מוסיף להיסטוריה ושולח פרומפטים לסוכנים אחרים. """ # === לוגיקת שליחה === def should_send_prompt(self, player_name: str) -> bool: """ מחליט אם לשלוח פרומפט לסוכן. כללים: 1. אין בקשה תלויה (pending_request == False) 2. וגם אחד מאלה: - מצב המשחק השתנה (state_hash שונה) - הגיעה הודעת צ'אט חדשה """ # === פונקציות פנימיות === def _create_prompt(self, agent: AgentState, is_active_turn: bool) -> Dict: """יוצר פרומפט מלא לסוכן""" def _build_what_happened(self, agent: AgentState) -> str: """בונה תיאור 'מה קרה' מהאירועים האחרונים""" def _get_optimized_state(self) -> Dict: """מחזיר state ממוטב לשליחה ל-LLM""" def _hash_state(self) -> str: """יוצר hash של ה-state לזיהוי שינויים""" def _convert_to_action(self, parsed_response: Dict, player_id: int) -> Action: """ממיר תשובת LLM לאובייקט Action""" def _broadcast_chat(self, from_player: str, message: str): """משדר הודעת צ'אט לכל הסוכנים""" ``` **מיקום:** `pycatan/ai/ai_manager.py` --- ### 3. AIUser - Wrapper לממשק User ```python class AIUser(User): """ Wrapper שמחבר בין GameManager ל-AIManager. GameManager רואה את זה כ-User רגיל ומתקשר איתו דרך get_input() ו-notify_game_event(). """ def __init__(self, name: str, user_id: int, ai_manager: AIManager, color: str = ""): """ Args: name: שם השחקן user_id: מספר שחקן (0-based) ai_manager: הפניה ל-AIManager color: צבע השחקן """ super().__init__(name, user_id) self.ai_manager = ai_manager self.color = color # רושם את עצמו ב-AIManager ai_manager.register_agent(name, user_id, color) def get_input( self, game_state: GameState, prompt_message: str, allowed_actions: Optional[List[str]] = None ) -> Action: """ GameManager קורא לזה כשצריך פעולה. מעביר את הבקשה ל-AIManager לטיפול. """ return self.ai_manager.process_agent_turn(self.name) def notify_game_event( self, event_type: str, message: str, affected_players: Optional[List[int]] = None ): """ GameManager קורא לזה כשמתרחש אירוע. מעביר ל-AIManager לשמירה. """ self.ai_manager.on_game_event(event_type, message, affected_players) def notify_action(self, action: Action, success: bool, message: str = ""): """ GameManager קורא לזה אחרי ביצוע פעולה. """ # אפשר להוסיף לוגיקה אם צריך pass ``` **מיקום:** `pycatan/ai/ai_user.py` --- ### 4. AILogger - ניהול לוגים ```python class AILogger: """ מנהל לוגים עבור מערכת ה-AI. שומר על תאימות לפורמט הקיים: - player_X.md (לוגים קריאים) - prompt_N.json (פרומפטים) - response_N.json (תשובות) גם תומך ב-Web Viewer (לעתיד). """ def __init__(self, session_dir: Path = None): """ Args: session_dir: תיקיית session. אם None, יוצר אוטומטית. """ if session_dir is None: session_dir = self._create_session_dir() self.session_dir = session_dir self.session_dir.mkdir(parents=True, exist_ok=True) self.request_counters: Dict[str, int] = {} self.start_time = datetime.now() # יצירת header ללוגים self._init_session() # === Logging === def log_prompt( self, player_name: str, prompt: Dict, schema: Dict, is_active: bool = True ) -> Path: """ שומר פרומפט לקובץ. יוצר: - session/player_name/prompts/prompt_N.json - session/player_name/prompts/prompt_N.txt Returns: Path: נתיב לקובץ JSON """ def log_response( self, player_name: str, response: LLMResponse, parsed: Dict ): """ שומר תשובה ומעדכן MD log. יוצר: - session/player_name/responses/response_N.json - מעדכן session/player_name.md """ def log_chat(self, from_player: str, message: str): """שומר הודעת צ'אט ללוג""" def log_error(self, player_name: str, error: str): """שומר שגיאה ללוג""" # === MD Generation === def _init_player_md(self, player_name: str, model: str): """יוצר header לקובץ MD של שחקן""" def _append_request_to_md( self, player_name: str, num: int, prompt: Dict ): """מוסיף request section ל-MD""" def _append_response_to_md( self, player_name: str, num: int, response: LLMResponse, parsed: Dict ): """מוסיף response section ל-MD""" # === Utilities === def _create_session_dir(self) -> Path: """יוצר תיקיית session עם timestamp""" def get_session_path(self) -> Path: """מחזיר נתיב ה-session""" def save_agent_memories(self, agents: Dict[str, AgentState]): """שומר את הזיכרונות של כל הסוכנים לקובץ""" def save_chat_history(self, chat_history: List[Dict]): """שומר היסטוריית צ'אט לקובץ""" ``` **מיקום:** `pycatan/ai/ai_logger.py` --- ## 🔄 זרימות עבודה ### זרימה 1: תור של סוכן AI ``` ┌────────────────────────────────────────────────────────────────────┐ │ AGENT TURN FLOW │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ 1. GameManager._process_user_action() │ │ │ │ │ │ calls │ │ ▼ │ │ 2. AIUser.get_input(game_state, prompt_message, allowed_actions) │ │ │ │ │ │ delegates to │ │ ▼ │ │ 3. AIManager.process_agent_turn(player_name) │ │ │ │ │ ├─► Get agent state │ │ │ │ │ ├─► Build "what_happened" from recent_events │ │ │ │ │ ├─► Create prompt via PromptManager │ │ │ │ │ ├─► AILogger.log_prompt() → saves files │ │ │ │ │ ├─► Mark agent.pending_request = True │ │ │ │ │ ├─► LLMClient.generate(prompt) ──────► 🤖 Gemini │ │ │ │ │ │ │◄─── Response ◄──────────────────────────────┘ │ │ │ │ │ ├─► Mark agent.pending_request = False │ │ │ │ │ ├─► Parse response │ │ │ │ │ ├─► AILogger.log_response() → saves files + MD │ │ │ │ │ ├─► Update agent.memory (note_to_self) │ │ │ │ │ ├─► Clear agent.recent_events │ │ │ │ │ ├─► Handle chat_message if present │ │ │ │ │ ▼ │ │ 4. Return Action │ │ │ │ │ ▼ │ │ 5. GameManager.execute_action() │ │ │ │ │ ▼ │ │ 6. GameManager._notify_all_users() ──► AIUser.notify_game_event() │ │ │ │ │ ▼ │ │ AIManager.on_game_event() │ │ │ │ │ ▼ │ │ Save to agent.recent_events│ │ │ └────────────────────────────────────────────────────────────────────┘ ``` ### זרימה 2: הודעת צ'אט ``` ┌────────────────────────────────────────────────────────────────────┐ │ CHAT MESSAGE FLOW │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ 1. Agent A response includes chat_message: "Anyone want sheep?" │ │ │ │ │ ▼ │ │ 2. AIManager._broadcast_chat("A", "Anyone want sheep?") │ │ │ │ │ ├─► Add to chat_history (shared) │ │ │ │ │ ├─► AILogger.log_chat() │ │ │ │ │ ▼ │ │ 3. For each other agent (B, C, D): │ │ │ │ │ ├─► Check should_send_prompt(agent) │ │ │ │ │ │ │ ├─► if pending_request: SKIP (don't queue!) │ │ │ │ │ │ │ └─► if not pending: Send spectator prompt │ │ │ │ │ └─► Agents see chat in their next prompt │ │ │ └────────────────────────────────────────────────────────────────────┘ ``` ### זרימה 3: אירוע במשחק (Event) ``` ┌────────────────────────────────────────────────────────────────────┐ │ GAME EVENT FLOW │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ 1. GameManager: Something happens (dice roll, build, trade...) │ │ │ │ │ ▼ │ │ 2. GameManager._notify_all_users("dice_roll", "Rolled 6 (3+3)") │ │ │ │ │ │ For each user: │ │ ▼ │ │ 3. User.notify_game_event("dice_roll", "Rolled 6 (3+3)") │ │ │ │ │ ├─► HumanUser: prints to console │ │ │ │ │ └─► AIUser: delegates to AIManager │ │ │ │ │ ▼ │ │ 4. AIManager.on_game_event("dice_roll", "Rolled 6 (3+3)") │ │ │ │ │ ▼ │ │ 5. For each agent: │ │ │ │ │ └─► agent.recent_events.append({ │ │ "type": "dice_roll", │ │ "message": "Rolled 6 (3+3)", │ │ "timestamp": time.time() │ │ }) │ │ │ │ 6. When agent's turn comes, recent_events → "what_happened" │ │ │ └────────────────────────────────────────────────────────────────────┘ ``` --- ## 📁 מבנה קבצים חדש ``` pycatan/ ├── ai/ │ ├── __init__.py # exports │ │ │ │ # === NEW FILES === │ ├── ai_manager.py # 🆕 AIManager class │ ├── ai_user.py # 🆕 AIUser class (wrapper) │ ├── ai_logger.py # 🆕 AILogger class │ ├── agent_state.py # 🆕 AgentState dataclass │ │ │ │ # === EXISTING (minimal changes) === │ ├── prompt_manager.py # ✅ Keep as-is │ ├── llm_client.py # ✅ Keep as-is │ ├── state_filter.py # ✅ Keep as-is │ ├── prompt_templates.py # ✅ Keep as-is │ ├── response_parser.py # ✅ Keep as-is │ ├── schemas.py # ✅ Keep as-is │ └── config.py # ✅ Keep as-is │ ├── players/ │ ├── __init__.py │ ├── user.py # ✅ Keep as-is (abstract interface) │ └── human_user.py # ✅ Keep as-is │ ├── management/ │ └── game_manager.py # ✅ Keep as-is (no changes!) │ examples/ ├── ai_testing/ │ │ # === DEPRECATED (will be replaced) === │ ├── generate_prompts_from_state.py # ⚠️ DEPRECATED │ ├── test_ai_live.py # ⚠️ DEPRECATED │ ├── play_with_prompts.py # ⚠️ DEPRECATED │ │ │ │ # === NEW === │ ├── play_with_ai.py # 🆕 New unified entry point │ │ │ │ # === KEEP === │ ├── web_viewer.py # ✅ Keep (works with new log format) │ └── my_games/ # ✅ Keep (session storage) ``` --- ## 📋 תוכנית מימוש ### Phase 1: יצירת רכיבים חדשים (לא שוברים קוד קיים) #### Step 1.1: AgentState ``` 📁 Create: pycatan/ai/agent_state.py ⏱️ Estimated: 15 min 📝 Contents: - AgentState dataclass - Helper methods ``` #### Step 1.2: AILogger ``` 📁 Create: pycatan/ai/ai_logger.py ⏱️ Estimated: 45 min 📝 Contents: - AILogger class - log_prompt() - saves JSON + TXT - log_response() - saves JSON + updates MD - MD format matching existing player_X.md ``` #### Step 1.3: AIManager ``` 📁 Create: pycatan/ai/ai_manager.py ⏱️ Estimated: 1.5 hours 📝 Contents: - AIManager class - process_agent_turn() - on_game_event() - on_chat_message() - should_send_prompt() - _broadcast_chat() ``` #### Step 1.4: AIUser ``` 📁 Create: pycatan/ai/ai_user.py ⏱️ Estimated: 30 min 📝 Contents: - AIUser class extending User - get_input() delegation - notify_game_event() delegation ``` #### Step 1.5: Update __init__.py ``` 📁 Modify: pycatan/ai/__init__.py ⏱️ Estimated: 5 min 📝 Add exports for new classes ``` --- ### Phase 2: Entry Point חדש #### Step 2.1: Create play_with_ai.py ``` 📁 Create: examples/ai_testing/play_with_ai.py ⏱️ Estimated: 45 min 📝 Contents: - Creates GameManager with AIUsers - Creates AIManager - Runs game loop ``` --- ### Phase 3: בדיקות ווידוא #### Step 3.1: Test basic flow ``` ⏱️ Estimated: 30 min 📝 Run play_with_ai.py - Verify prompts generated - Verify responses logged - Verify MD files created - Verify actions returned to GameManager ``` #### Step 3.2: Test chat flow ``` ⏱️ Estimated: 20 min 📝 Test chat messages - Agent sends chat - Other agents receive in next prompt - Chat history saved ``` #### Step 3.3: Test Web Viewer ``` ⏱️ Estimated: 15 min 📝 Verify web_viewer.py works with new log format ``` --- ### Phase 4: Cleanup (אופציונלי) #### Step 4.1: Mark deprecated files ``` 📝 Add deprecation notices to: - generate_prompts_from_state.py - test_ai_live.py - play_with_prompts.py ``` #### Step 4.2: Update documentation ``` 📝 Update: - README - AI_ARCHITECTURE.md ``` --- ## ⏱️ סיכום זמנים | Phase | Task | Time | |-------|------|------| | 1.1 | AgentState | 15 min | | 1.2 | AILogger | 45 min | | 1.3 | AIManager | 1.5 hours | | 1.4 | AIUser | 30 min | | 1.5 | __init__.py | 5 min | | 2.1 | play_with_ai.py | 45 min | | 3.x | Testing | 1 hour | | **Total** | | **~5 hours** | --- ## ✅ Checklist לפני התחלה - [ ] מבנה הארכיטקטורה ברור - [ ] הבנת זרימת העבודה - [ ] הבנת אחריות כל רכיב - [ ] מוכן להתחיל Phase 1 --- ## 🔮 תוספות עתידיות (לא בריפקטור הנוכחי) 1. **סיכום צ'אט אוטומטי** - `_maybe_summarize_chat()` 2. **Web Viewer real-time** - WebSocket ל-AILogger 3. **Multi-LLM support** - OpenAI, Anthropic 4. **Agent personalities** - Different prompts per agent 5. **Replay system** - Play back from logs --- ## 📚 קבצים קשורים - [AI_ARCHITECTURE.md](.github/instructions/AI_ARCHITECTURE.md) - ארכיטקטורה כללית - [AI_AGENT_PRINCIPLES.md](.github/instructions/AI_AGENT_PRINCIPLES.md) - עקרונות עיצוב - [WORK_PLAN.md](.github/instructions/WORK_PLAN.md) - תוכנית עבודה כללית --- **מוכן להתחיל?** 🚀