# MapMorph AI Agents Architecture ## System Overview MapMorph uses a **multi-agent orchestration system** with specialized AI agents that work together to modify map styles through natural language. --- ## High-Level Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ USER INTERFACE (Gradio) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │ Chat Input │ │Theme Dropdown│ │ Lang Dropdown│ │ Map View │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └────▲───────┘ │ └─────────┼──────────────────┼──────────────────┼───────────────┼─────────┘ │ │ │ │ │ └──────┬───────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────┐ │ │ │ update_map() │ │ │ │ - Preserve mods │ │ │ │ - Apply to fresh │ │ │ │ style │ │ │ └──────────┬───────────┘ │ │ │ │ ▼ ▼ │ ┌─────────────────────────────────────────────────────────┐ │ │ ORCHESTRATION LAYER │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ MapMorphOrchestrator │ │ │ │ │ - route_request() (LLM-based routing) │ │ │ │ │ - process_request() (Coordination) │ │ │ │ │ - Re-apply modifications to fresh styles │ │ │ │ └──────────┬──────────────────────┬───────────────────┘ │ │ │ │ │ │ │ └─────────────┼──────────────────────┼──────────────────────┘ │ │ │ │ ┌─────────┴────────┐ ┌────────┴──────────┐ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ ColorAgent │ │LanguageAgent │ │ UNCLEAR │ │ │ │ │ │ │ (Help Msg) │ │ │ Tools: │ │ Tools: │ └──────────────┘ │ │ - create_ │ │ - create_ │ │ │ color_mod │ │ dual_lang_ │ │ │ - list_ │ │ mod │ │ │ layers │ │ │ │ │ - suggest_ │ │ │ │ │ color │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ Returns: │ Returns: │ │ Modification │ Modification │ │ Dict │ Dict │ │ │ │ └──────────┬────────┘ │ │ │ ▼ │ ┌─────────────────────────────────────────────────────────┐ │ │ STATE MANAGEMENT │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ MapState │ │ │ │ │ - base_theme: str │ │ │ │ │ - base_language: str │ │ │ │ │ - modifications: List[Modification] │ │ │ │ │ - current_style: Dict (MapLibre style.json) │ │ │ │ │ │ │ │ │ │ Methods: │ │ │ │ │ - add_modification() │ │ │ │ │ - to_dict() / from_dict() (BrowserState compat) │ │ │ │ └────────────────────────────────────────────────────┘ │ │ └──────────────────────┬──────────────────────────────────┘ │ │ │ ▼ │ ┌─────────────────────────────────────────────────────────┐ │ │ STYLE APPLICATION LAYER │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ ModificationApplicator │ │ │ │ │ - apply_all(style, modifications) │ │ │ │ │ │ │ │ │ │ For each modification: │ │ │ │ │ 1. ColorModification → Update layer fill-color │ │ │ │ │ with zoom interpolation │ │ │ │ │ 2. LanguageModification → Update text-field │ │ │ │ │ with dual-language expression │ │ │ │ └────────────────────────────────────────────────────┘ │ │ └──────────────────────┬──────────────────────────────────┘ │ │ │ ▼ │ ┌─────────────────────────────────────────────────────────┐ │ │ MAP RENDERING LAYER │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ render_map(modified_style) │ │ │ │ │ - Create MapLibre Map object │ │ │ │ │ - Export to HTML iframe │ │ │ │ └────────────────────────────────────────────────────┘ │ │ └──────────────────────┬──────────────────────────────────┘ │ │ │ └───────────────────────────────────────┘ ``` --- ## Agent Communication Flow ### Request Processing Pipeline ``` ┌────────────────────────────────────────────────────────────────┐ │ STEP 1: USER REQUEST │ │ User: "Make water darker blue" │ └────────────────┬───────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 2: ROUTING (Orchestrator.route_request) │ │ │ │ System Prompt: "Analyze request. Return COLOR/LANGUAGE/UNCLEAR"│ │ Model: gpt-4o-mini (temp: 0.1) │ │ │ │ Input: "Make water darker blue" │ │ Output: "COLOR" │ └────────────────┬───────────────────────────────────────────────┘ │ ┌────────┴────────────────────────────────┐ │ │ "COLOR" "LANGUAGE" │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────┐ │ STEP 3a: ColorAgent │ │ STEP 3b: │ │ │ │ LanguageAgent │ │ System: │ │ │ │ "You are color │ │ System: │ │ expert..." │ │ "You are language │ │ │ │ expert..." │ │ Tools Available: │ │ │ │ [create_color_mod, │ │ Tools Available: │ │ list_layers, │ │ [create_dual_lang] │ │ suggest_color] │ │ │ │ │ │ │ │ LLM Selects: │ │ LLM Selects: │ │ create_color_mod │ │ create_dual_lang_mod │ │ │ │ │ │ Arguments: │ │ Arguments: │ │ { │ │ { │ │ layer_id: "water", │ │ primary: "en", │ │ color: "#0066CC", │ │ secondary: "es" │ │ description: "..." │ │ } │ │ } │ │ │ └──────────┬───────────┘ └──────────┬───────────┘ │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────┐ │ STEP 4a: Tool Exec │ │ STEP 4b: Tool Exec │ │ │ │ │ │ create_color_mod() │ │ create_dual_lang() │ │ └─> ColorMod object │ │ └─> LangMod object │ │ { │ │ { │ │ type: "color", │ │ type: "dual_ │ │ layer: "water",│ │ language", │ │ color: "#...", │ │ primary: "en", │ │ zoom_stops: {} │ │ secondary:"es" │ │ } │ │ } │ └──────────┬───────────┘ └──────────┬───────────┘ │ │ └────────────┬────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 5: STATE UPDATE │ │ │ │ MapState.add_modification(modification) │ │ modifications = [ColorMod] or [LangMod] │ └────────────────┬───────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 6: STYLE APPLICATION │ │ │ │ 1. Load fresh style: load_style(theme, language) │ │ 2. Apply all mods: ModificationApplicator.apply_all() │ │ - For ColorMod: Update layer["paint"]["fill-color"] │ │ with zoom interpolation │ │ - For LangMod: Update layer["layout"]["text-field"] │ │ with dual-language expression │ │ 3. Return modified_style │ └────────────────┬───────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 7: RENDERING & PERSISTENCE │ │ │ │ render_map(modified_style) → HTML │ │ MapState.to_dict() → BrowserState │ │ Return: (message, html, state, theme, lang) │ └────────────────┬───────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 8: UI UPDATE │ │ │ │ - Chat shows: "✅ Changed water to darker blue" │ │ - Map re-renders with blue water │ │ - State persisted for next interaction │ └────────────────────────────────────────────────────────────────┘ ``` --- ## Data Models Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ MapState │ │ (Root State) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ - base_theme: str (e.g., "light") │ │ │ │ - base_language: str (e.g., "en") │ │ │ │ - modifications: List[Modification] │ │ │ │ - current_style: Dict (MapLibre JSON) │ │ │ │ │ │ │ │ Methods: │ │ │ │ - add_modification(mod) │ │ │ │ - remove_modification(id) │ │ │ │ - to_dict() → Dict (Serialization) │ │ │ │ - from_dict(data) → MapState (Deserialization) │ │ │ └────────────────────────────────────────────────────────┘ │ └───────────────────────┬─────────────────────────────────────┘ │ │ contains │ ┌───────────────┴──────────────┐ │ │ ▼ ▼ ┌───────────────────┐ ┌───────────────────────┐ │ Modification │ │ │ │ (Base Class) │ │ │ │ ┌──────────────┐ │ │ │ │ │ - id: str │ │ │ │ │ │ - type: Enum │ │ │ │ │ │ - desc: str │ │ │ │ │ └──────────────┘ │ │ │ └────────┬──────────┘ │ │ │ │ │ │ subclasses │ │ ┌────┴────┐ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────┐ ┌─────────────────────┐ │ │ColorMod │ │ LanguageModification│ │ │ │ │ │ │ │- layer_id│ │ - primary_language │ │ │- base_ │ │ - secondary_lang │ │ │ color │ │ - affected_layers │ │ │- zoom_ │ │ (List) │ │ │ stops │ │ │ │ │ (Dict) │ │ Type: │ │ │ │ │ - DUAL_LANGUAGE │ │ │Type: │ └─────────────────────┘ │ │- COLOR │ │ └──────────┘ │ │ Serialization │ ↓ │ ┌────────────────────────────────────────┐ │ │ BrowserState (Gradio) │ │ │ { │ │ │ "base_theme": "light", │ │ │ "base_language": "en", │ │ │ "modifications": [ │ │ │ { │ │ │ "id": "uuid-123", │ │ │ "type": "color", │ │ │ "layer_id": "water", │ │ │ "base_color": "#0066CC", │ │ │ "zoom_stops": { │ │ │ "0": "#002244", │ │ │ "4": "#003366", │ │ │ ... │ │ │ } │ │ │ } │ │ │ ], │ │ │ "current_style": {...} │ │ │ } │ │ └────────────────────────────────────────┘ │ │ ``` --- ## Tool Execution Pattern ``` ┌────────────────────────────────────────────────────────────┐ │ Agent Tool System │ └────────────────────────────────────────────────────────────┘ Agent Definition: ───────────────── self.tools = [ { "type": "function", "function": { "name": "create_color_modification", "description": "Create a color modification...", "parameters": { "type": "object", "properties": { "layer_id": { "type": "string", "enum": ["water", "earth", "landuse", ...], "description": "The map layer to modify" }, "color": { "type": "string", "description": "Hex color (e.g., #0066CC)" }, "description": {...} }, "required": ["layer_id", "color"] } } } ] Tool Execution Flow: ──────────────────── ┌─────────────────────────────────────────────────────┐ │ 1. LLM Call with Tools │ │ │ │ response = client.chat.completions.create( │ │ model="gpt-4o-mini", │ │ messages=[...], │ │ tools=self.tools, ← Tools defined │ │ tool_choice="auto", ← LLM decides │ │ temperature=0.1 ← Deterministic │ │ ) │ └──────────────────┬──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 2. LLM Response │ │ │ │ message = response.choices[0].message │ │ │ │ if message.tool_calls: │ │ tool_call = message.tool_calls[0] │ │ function_name = "create_color_modification" │ │ arguments = { │ │ "layer_id": "water", │ │ "color": "#0066CC", │ │ "description": "Make water darker blue" │ │ } │ └──────────────────┬──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 3. Tool Function Execution │ │ │ │ if function_name == "create_color_modification": │ │ modification = create_color_modification( │ │ layer_id="water", │ │ color="#0066CC", │ │ description="Make water darker blue" │ │ ) │ │ │ │ Returns: │ │ - Response message: "✅ Changed water..." │ │ - Modification dict: {...} │ └─────────────────────────────────────────────────────┘ Tool Implementation (tools.py): ──────────────────────────────── def create_color_modification( layer_id: str, color: str, description: str ) -> Dict[str, Any]: """Create a color modification.""" # Generate zoom stops (5 levels) zoom_stops = generate_zoom_stops(color, num_stops=5) # Create modification object modification = ColorModification( layer_id=layer_id, base_color=color, zoom_stops=zoom_stops, description=description ) # Return as dict for serialization return modification.to_dict() ``` --- ## State Preservation Architecture ### The Problem This Solves ``` OLD APPROACH (Without State Preservation): ─────────────────────────────────────────── 1. User: "Make water blue" → Water turns blue ✅ 2. User changes language to Spanish → Water resets to default ❌ 3. User: "Make water blue" again → Frustrating! 😡 NEW APPROACH (With State Preservation): ──────────────────────────────────────── 1. User: "Make water blue" → Water turns blue ✅ 2. User changes language to Spanish → Water STAYS blue ✅ 3. Labels now in Spanish, water still blue → Perfect! 😊 ``` ### Implementation ``` ┌──────────────────────────────────────────────────────────────┐ │ update_map(theme, language, current_state_dict) │ │ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ STEP 1: Extract Modifications ││ │ │ ││ │ │ current_state = MapState.from_dict(current_state_dict) ││ │ │ modifications = current_state.modifications ││ │ │ ││ │ │ # Preserve: [ColorMod(water, blue), LangMod(en, es)] ││ │ └─────────────────────────────────────────────────────────┘│ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ STEP 2: Load Fresh Style ││ │ │ ││ │ │ fresh_style = load_style(theme="dark", language="pt") ││ │ │ ││ │ │ # New style: Portuguese labels, dark theme ││ │ └─────────────────────────────────────────────────────────┘│ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ STEP 3: Re-apply ALL Modifications ││ │ │ ││ │ │ modified_style = ModificationApplicator.apply_all( ││ │ │ fresh_style, ││ │ │ modifications # ← OLD mods on NEW style! ││ │ │ ) ││ │ │ ││ │ │ # Result: Portuguese + Dark + Blue water + Bilingual! ││ │ └─────────────────────────────────────────────────────────┘│ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ STEP 4: Create New State (Preserve Mods) ││ │ │ ││ │ │ new_state = MapState( ││ │ │ base_theme="dark", ││ │ │ base_language="pt", ││ │ │ modifications=modifications, # ← PRESERVED! ││ │ │ current_style=modified_style ││ │ │ ) ││ │ └─────────────────────────────────────────────────────────┘│ │ ↓ │ │ return map_html, new_state.to_dict(), theme, language │ └──────────────────────────────────────────────────────────────┘ ``` --- ## Agent Capabilities Matrix | Capability | Orchestrator | ColorAgent | LanguageAgent | |-----------|-------------|-----------|---------------| | **Routing** | ✅ LLM-based | ❌ | ❌ | | **Color Modification** | ❌ | ✅ Creates ColorMod | ❌ | | **Language Modification** | ❌ | ❌ | ✅ Creates LangMod | | **Layer Identification** | ❌ | ✅ 5 layers | ✅ Symbol layers | | **Color Generation** | ❌ | ✅ Hex colors | ❌ | | **Zoom Shading** | ❌ | ✅ 5 stops | ❌ | | **Language Validation** | ❌ | ❌ | ✅ 40+ languages | | **State Management** | ✅ Applies mods | ❌ | ❌ | | **Style Application** | ✅ Coordinates | ❌ | ❌ | | **Multi-user Isolation** | ✅ BrowserState | ❌ | ❌ | --- ## Configuration & Settings ### LLM Configuration ```python # constants/llm_config.py LLM_MODEL = "gpt-4o-mini" LLM_TEMPERATURE = 0.1 # Low for deterministic outputs LLM_MAX_TOKENS = 4000 LLM_RESPONSE_FORMAT = {"type": "json_object"} ``` ### Available Layers (ColorAgent) ```python AVAILABLE_LAYERS = [ "water", # Water bodies (oceans, lakes, rivers) "earth", # Land areas "landuse", # Parks, forests, urban areas "buildings", # Building structures "roads" # Road networks ] ``` ### Supported Languages (LanguageAgent) ```python # 40+ languages supported via Protomaps en, es, fr, de, ja, zh-Hans, zh-Hant, ar, ru, pt, it, nl, pl, tr, vi, th, ko, hi, id, fa, uk, cs, sv, no, da, fi, el, he, ro, hu, sk, bg, hr, sr, sl, lt, lv, et, is, ga, cy ``` --- ## Key Architectural Patterns ### 1. **Separation of Concerns** - **Orchestrator**: Routing & coordination - **Agents**: Domain-specific logic - **Tools**: Modification creation - **Applicator**: Style transformation - **Renderer**: HTML generation ### 2. **Tool-Based Agent Pattern** - Agents don't hardcode logic - Tools define capabilities - LLM decides which tool to call - Extensible: Add tools = Add features ### 3. **Stateless Design** - No global state - BrowserState for per-user isolation - Modifications stored as serializable list - Safe for concurrent users ### 4. **Modification Preservation** - Modifications stored separately from style - Re-applied to fresh styles on change - Survives theme/language switches - Cumulative effect (all mods applied) ### 5. **LLM for Routing** - Natural language understanding - No hardcoded keywords - Flexible: "darker blue" vs "navy" vs "ocean color" - Deterministic with low temperature --- ## Integration Points ### Gradio Interface ```python # app.py chat_interface = gr.ChatInterface( fn=chat_wrapper, type="messages" ) map_state = gr.BrowserState( default_value=create_fresh_map_state(...).to_dict() ) ``` ### Event Handlers ```python # Language change preserves color mods language_dropdown.change( fn=update_map, inputs=[theme, language, map_state], # map_state carries mods outputs=[map_display, map_state, ...] ) ``` ### State Flow ``` User Action → Gradio Event → Function Call → MapState Update → Modification Application → Style Rendering → UI Update → BrowserState Persistence ``` --- ## Deployment Architecture ``` ┌────────────────────────────────────────────┐ │ Gradio Application (app.py) │ │ │ │ Environment Variables: │ │ - PROTOMAPS_API_KEY │ │ - OPENAI_API_KEY │ └────────────┬───────────────────────────────┘ │ ┌─────┴──────┐ │ │ ▼ ▼ ┌───────────┐ ┌────────────┐ │ Protomaps │ │ OpenAI API │ │ API │ │ (gpt-4o │ │ (Tiles) │ │ -mini) │ └───────────┘ └────────────┘ Deployment Target: HuggingFace Spaces State Management: BrowserState (per-user) No Database: Stateless design ``` --- ## Summary This architecture demonstrates a **well-designed multi-agent system** with: ✅ **Clear Separation**: Each component has a single responsibility ✅ **Scalability**: Stateless, multi-user safe ✅ **Extensibility**: Easy to add new agents/tools ✅ **Maintainability**: Structured, documented code ✅ **Reliability**: Deterministic LLM outputs ✅ **UX**: Natural language + instant visual feedback ✅ **State Preservation**: Modifications survive theme/language changes The innovation of **preserving modifications across state changes** while maintaining **clean agent separation** creates a robust foundation for AI-powered map customization.