Spaces:
Build error
Build error
Refactor chat interface for per-user session management - Removed global session and conversation history management, transitioning to a per-user model using `gr.State`. Updated message processing to include user-specific session IDs, enhancing multi-user safety and improving fallback handling. This update aims to streamline user interactions and maintain session context effectively.
9610835 | # Multi-User Session Handling - Design Document | |
| **Date**: January 2025 | |
| **Author**: BeatDebate Team | |
| **Status**: β **COMPLETED** | |
| **Review Status**: Pending | |
| --- | |
| ## 1. Problem Statement | |
| **Objective**: Implement secure multi-user session handling for BeatDebate to prevent context bleeding and ensure proper session isolation when deployed on HuggingFace Spaces or any multi-user environment. | |
| **Current State**: BeatDebate has a critical concurrency vulnerability where: | |
| - `SessionManagerService` uses a shared in-memory dictionary (`self.session_store = {}`) for all users | |
| - `BeatDebateChatInterface` maintains a single global session ID (`self.session_id = str(uuid.uuid4())`) | |
| - Multiple users can experience context bleeding, session collision, and data corruption | |
| **Critical Issues**: | |
| 1. **Context Bleeding**: Users receive recommendations based on other users' conversation histories | |
| 2. **Session Collision**: Follow-up queries may fail due to contaminated session context | |
| 3. **Data Corruption**: Concurrent access to shared session state leads to unpredictable behavior | |
| 4. **Security Risk**: User preferences and interaction patterns leak between sessions | |
| **Value Proposition**: | |
| - **Multi-User Safety**: Each user gets isolated, private session context | |
| - **Reliable Follow-ups**: "More tracks" and "similar to these" work correctly per user | |
| - **Production Ready**: Safe deployment to HuggingFace Spaces and other shared environments | |
| - **User Experience**: Consistent, predictable behavior for concurrent users | |
| --- | |
| ## 2. Goals & Non-Goals | |
| ### β In Scope | |
| - **Frontend Session Management**: Use `gr.State` to maintain per-user session IDs | |
| - **Session ID Propagation**: Pass user-specific session IDs from frontend to backend | |
| - **Session Context Isolation**: Ensure each user's conversation history stays separate | |
| - **Follow-up Query Support**: Maintain proper context for "more tracks" type queries | |
| - **Backwards Compatibility**: Existing backend session logic remains functional | |
| - **HuggingFace Spaces Compatibility**: Works correctly in shared deployment environments | |
| ### β Out of Scope (v1) | |
| - **Persistent Session Storage**: Sessions still expire when browser is closed | |
| - **User Authentication**: No login system, relying on browser session isolation | |
| - **Session Migration**: No cross-device session continuity | |
| - **Advanced Session Analytics**: No detailed session usage tracking | |
| - **Session Sharing**: Users cannot share session contexts | |
| --- | |
| ## 3. Architecture Overview | |
| ### 3.1 Current Architecture (Problematic) | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β HuggingFace Spaces β | |
| β β | |
| β βββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ | |
| β β User A β β User B ββ | |
| β β Browser β β Browser ββ | |
| β β β β ββ | |
| β βββββββββββ¬ββββββββ ββββββββββββ¬ββββββββββββββββββββββββ | |
| β β β β | |
| β βββββββββββΌββββββββββββββββββββββββΌββββββββββββββββββββββ β | |
| β β Gradio Interface β β | |
| β β β SHARED session_id = "global-123" β β | |
| β β β SHARED conversation_history = [] β β | |
| β βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ β | |
| β β β | |
| β βββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ β | |
| β β FastAPI Backend β β | |
| β β β β | |
| β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β | |
| β β β SessionManagerService β β β | |
| β β β β SHARED session_store = { β β β | |
| β β β "global-123": { β β β | |
| β β β // Mixed context from Users A & B β β β | |
| β β β } β β β | |
| β β β } β β β | |
| β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β | |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| ### 3.2 Proposed Architecture (Secure) | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β HuggingFace Spaces β | |
| β β | |
| β βββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ | |
| β β User A β β User B ββ | |
| β β Browser β β Browser ββ | |
| β β gr.State: β β gr.State: ββ | |
| β β "user-a-456" β β "user-b-789" ββ | |
| β βββββββββββ¬ββββββββ ββββββββββββ¬ββββββββββββββββββββββββ | |
| β β β β | |
| β βββββββββββΌββββββββββββββββββββββββΌββββββββββββββββββββββ β | |
| β β Gradio Interface β β | |
| β β β ISOLATED session management per user β β | |
| β β β Session ID passed with each request β β | |
| β βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ β | |
| β β β | |
| β βββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ β | |
| β β FastAPI Backend β β | |
| β β β β | |
| β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β | |
| β β β SessionManagerService β β β | |
| β β β β ISOLATED session_store = { β β β | |
| β β β "user-a-456": { /* User A context */ }, β β β | |
| β β β "user-b-789": { /* User B context */ } β β β | |
| β β β } β β β | |
| β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β | |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| --- | |
| ## 4. Technical Design | |
| ### 4.1 Frontend Changes (Gradio Interface) | |
| #### 4.1.1 Session State Management | |
| **File**: `src/ui/chat_interface.py` | |
| **Key Changes**: | |
| 1. Remove global `self.session_id` from class initialization | |
| 2. Add `gr.State` component to store per-user session ID | |
| 3. Update all message processing functions to accept and return session ID | |
| 4. Initialize each new user session with unique UUID | |
| ```python | |
| class BeatDebateChatInterface: | |
| def __init__(self, backend_url: str = "http://localhost:8000"): | |
| self.backend_url = backend_url | |
| self.response_formatter = ResponseFormatter() | |
| self.planning_display = PlanningDisplay() | |
| # β REMOVE: self.session_id = str(uuid.uuid4()) | |
| # β REMOVE: self.conversation_history = [] # This is also global state | |
| # Initialize fallback service | |
| self.fallback_service = None | |
| self._initialize_fallback_service() | |
| async def process_message( | |
| self, | |
| message: str, | |
| history: List[Tuple[str, str]], | |
| session_id: str # β ADD: Accept session_id as parameter | |
| ) -> Tuple[str, List[Tuple[str, str]], str, str]: # β ADD: Return updated session_id | |
| """Process message with isolated session context.""" | |
| # Pass session_id to backend calls | |
| recommendations_response = await self._get_recommendations(message, session_id) | |
| # Handle fallback with session isolation | |
| if should_fallback: | |
| recommendations_response = await self._get_fallback_recommendations( | |
| message, trigger_reason, session_id | |
| ) | |
| # Get potentially updated session_id from backend | |
| updated_session_id = recommendations_response.get("session_id", session_id) | |
| return "", updated_history, lastfm_player_html, updated_session_id | |
| def create_interface(self) -> gr.Blocks: | |
| with gr.Blocks(...) as interface: | |
| # β ADD: Per-user session state | |
| session_id_state = gr.State(value=str(uuid.uuid4())) | |
| # UI components... | |
| chatbot = gr.Chatbot(...) | |
| msg_input = gr.Textbox(...) | |
| send_btn = gr.Button(...) | |
| player_display = gr.HTML(...) | |
| # β MODIFY: Event handlers with session state | |
| async def handle_message(message, history, session_id): | |
| return await self.process_message(message, history, session_id) | |
| send_btn.click( | |
| fn=handle_message, | |
| inputs=[msg_input, chatbot, session_id_state], # Include session state | |
| outputs=[msg_input, chatbot, player_display, session_id_state] # Update session state | |
| ) | |
| msg_input.submit( | |
| fn=handle_message, | |
| inputs=[msg_input, chatbot, session_id_state], | |
| outputs=[msg_input, chatbot, player_display, session_id_state] | |
| ) | |
| ``` | |
| #### 4.1.2 Backend Request Updates | |
| All backend requests must include the user-specific session ID: | |
| ```python | |
| async def _get_recommendations(self, query: str, session_id: str) -> Optional[Dict]: | |
| request_data = { | |
| "query": query, | |
| "session_id": session_id, # β Pass user-specific session ID | |
| "max_recommendations": 10, | |
| "include_previews": True, | |
| "chat_context": self._get_chat_context() # Note: This still needs refactoring | |
| } | |
| async def _get_fallback_recommendations( | |
| self, | |
| query: str, | |
| trigger_reason: FallbackTrigger, | |
| session_id: str # β Accept session ID | |
| ) -> Optional[Dict[str, Any]]: | |
| fallback_request = FallbackRequest( | |
| query=query, | |
| session_id=session_id, # β Include in fallback request | |
| chat_context=self._get_chat_context(), | |
| trigger_reason=trigger_reason, | |
| max_recommendations=10 | |
| ) | |
| ``` | |
| ### 4.2 Backend Verification (SessionManagerService) | |
| **File**: `src/services/session_manager_service.py` | |
| **Current Implementation Analysis**: β Already session-safe! | |
| The backend `SessionManagerService` is already properly designed for multi-user scenarios: | |
| ```python | |
| class SessionManagerService: | |
| def __init__(self, cache_manager=None): | |
| self.session_store = {} # This dictionary is keyed by session_id | |
| async def create_or_update_session(self, session_id: str, ...): | |
| # β Uses session_id as key - already isolated | |
| if session_id not in self.session_store: | |
| self.session_store[session_id] = { ... } | |
| async def get_session_context(self, session_id: str): | |
| # β Returns only the specific session's context | |
| return self.session_store.get(session_id) | |
| ``` | |
| **No Changes Required**: The backend is already session-safe. The issue was entirely in the frontend's global session management. | |
| ### 4.3 Context Handler Integration | |
| **File**: `src/services/components/context_handler.py` | |
| **Verification**: β Already session-aware! | |
| The context handler properly uses session-specific data: | |
| ```python | |
| async def process_conversation_history(self, request) -> List[Dict]: | |
| # β Already retrieves session-specific history | |
| if hasattr(request, 'session_id') and request.session_id: | |
| session_context = await self.session_manager.get_session_context(request.session_id) | |
| ``` | |
| **No Changes Required**: Context handler already works correctly with proper session IDs. | |
| --- | |
| ## 5. Implementation Plan | |
| ### 5.1 Phase 1: Frontend Session Management (Priority: Critical) | |
| **Tasks**: | |
| 1. **Remove Global State** from `BeatDebateChatInterface` | |
| - Remove `self.session_id` from constructor | |
| - Remove global `self.conversation_history` (separate issue, lower priority) | |
| 2. **Implement gr.State Session Management** | |
| - Add `session_id_state = gr.State(value=str(uuid.uuid4()))` to interface | |
| - Update `process_message` signature to accept/return session ID | |
| - Update all event handlers to pass session state | |
| 3. **Update Backend Calls** | |
| - Modify `_get_recommendations` to accept session_id parameter | |
| - Modify `_get_fallback_recommendations` to accept session_id parameter | |
| - Ensure all API calls include user-specific session ID | |
| **Files to Modify**: | |
| - `src/ui/chat_interface.py` (Primary changes) | |
| **Testing Strategy**: | |
| - Open multiple browser tabs/windows | |
| - Verify each tab gets unique session ID | |
| - Test follow-up queries work independently per tab | |
| - Ensure no context bleeding between sessions | |
| ### 5.2 Phase 2: Global Conversation History Refactoring (Priority: Medium) | |
| **Issue**: `self.conversation_history` in chat interface is still global state | |
| **Tasks**: | |
| 1. Remove global conversation history from frontend | |
| 2. Rely entirely on backend session storage for conversation context | |
| 3. Update `_get_chat_context()` to retrieve from backend session store | |
| ### 5.3 Phase 3: Enhanced Session Monitoring (Priority: Low) | |
| **Tasks**: | |
| 1. Add session creation/destruction logging | |
| 2. Implement session cleanup for memory management | |
| 3. Add metrics for concurrent session count | |
| --- | |
| ## 6. Testing Strategy | |
| ### 6.1 Multi-User Simulation Tests | |
| **Scenario 1: Concurrent New Sessions** | |
| ``` | |
| 1. Open 3 browser tabs simultaneously | |
| 2. Each should get unique session ID (verify in logs) | |
| 3. Make different queries in each tab: | |
| - Tab 1: "Songs by Radiohead" | |
| - Tab 2: "Electronic music" | |
| - Tab 3: "Jazz for studying" | |
| 4. Verify each gets appropriate recommendations | |
| ``` | |
| **Scenario 2: Follow-up Query Isolation** | |
| ``` | |
| 1. Tab 1: "Songs by Mk.gee" β get Mk.gee recommendations | |
| 2. Tab 2: "Electronic music" β get electronic recommendations | |
| 3. Tab 1: "More tracks" β should get more Mk.gee, NOT electronic | |
| 4. Tab 2: "More tracks" β should get more electronic, NOT Mk.gee | |
| ``` | |
| **Scenario 3: Session Persistence** | |
| ``` | |
| 1. Tab 1: "Songs by Beatles" β get Beatles songs | |
| 2. Tab 1: "More like these" β should get more Beatles-style | |
| 3. Refresh Tab 1 β should get new session ID | |
| 4. Tab 1: "More tracks" β should NOT have Beatles context | |
| ``` | |
| ### 6.2 Performance Tests | |
| **Concurrent Load Test**: | |
| - Simulate 10+ concurrent users | |
| - Verify no session collision | |
| - Monitor memory usage of session store | |
| - Ensure reasonable response times | |
| ### 6.3 Error Handling Tests | |
| **Session ID Edge Cases**: | |
| - Missing session ID in request | |
| - Invalid session ID format | |
| - Backend should gracefully create new session | |
| --- | |
| ## 7. Deployment Considerations | |
| ### 7.1 HuggingFace Spaces Compatibility | |
| **Memory Management**: | |
| - Session store grows with concurrent users | |
| - Implement session timeout/cleanup after inactivity | |
| - Monitor memory usage in Spaces environment | |
| **Gradio State Behavior**: | |
| - `gr.State` is tied to browser session | |
| - Refreshing page creates new session (expected behavior) | |
| - No persistence across device switches (acceptable for v1) | |
| ### 7.2 Rollback Plan | |
| **If Issues Arise**: | |
| 1. Keep current implementation as backup branch | |
| 2. Frontend changes are isolated and easily reversible | |
| 3. Backend changes are minimal/none | |
| 4. Can quickly revert to single-user mode if needed | |
| --- | |
| ## 8. Success Metrics | |
| ### 8.1 Functional Metrics | |
| - β Zero context bleeding between users | |
| - β Follow-up queries work correctly per user | |
| - β Each browser tab maintains independent session | |
| - β New sessions start fresh after page refresh | |
| ### 8.2 Performance Metrics | |
| - Session creation time < 100ms | |
| - Memory usage scales linearly with active sessions | |
| - No degradation in recommendation quality | |
| - Response times remain under 30 seconds per query | |
| ### 8.3 User Experience Metrics | |
| - Consistent behavior across all user interactions | |
| - No confusing recommendations from other users' contexts | |
| - Reliable "more tracks" functionality | |
| - Smooth multi-tab usage experience | |
| --- | |
| ## 9. Future Enhancements | |
| ### 9.1 Session Persistence (v2) | |
| - Add session storage backend (Redis/Database) | |
| - Enable cross-device session continuity | |
| - Implement session sharing functionality | |
| ### 9.2 Advanced Session Management (v2) | |
| - User authentication integration | |
| - Session migration capabilities | |
| - Advanced session analytics | |
| - Custom session expiration policies | |
| ### 9.3 Performance Optimizations (v2) | |
| - Session data compression | |
| - Lazy loading of session contexts | |
| - Distributed session storage | |
| - Session pooling for efficiency | |
| --- | |
| This design ensures BeatDebate can safely handle multiple concurrent users while maintaining the sophisticated follow-up query capabilities and conversation context that make the system valuable. The solution is minimal, focused, and maintains backward compatibility while solving the critical multi-user safety issue. |