ButterM40 commited on
Commit
52eea3a
·
1 Parent(s): 8575a1c

🔧 Fixed LoRA adapter loading and Gradio interface

Browse files

✅ Fixed critical LoRA adapter loading bug in character_manager.py
✅ Fixed Gradio app.py method signatures and async issues
✅ Characters now respond with proper personalities
✅ Moses speaks as biblical prophet, not random responses
✅ Isolated model instances prevent character interference

All characters should now work correctly!

app.py CHANGED
@@ -1,153 +1,239 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hugging Face Spaces deployment for Roleplay Chat Box
4
- Serves the React-style frontend with FastAPI backend
5
- """
6
-
7
  import os
8
  import sys
9
- import uvicorn
10
- from fastapi import FastAPI, Request, HTTPException
11
- from fastapi.staticfiles import StaticFiles
12
- from fastapi.responses import FileResponse, HTMLResponse
13
- from fastapi.middleware.cors import CORSMiddleware
14
- import logging
15
-
16
- # Setup logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
 
20
- # Add backend to path
21
  backend_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backend')
22
  sys.path.insert(0, backend_path)
23
 
24
- # Import your existing REST server components
25
- try:
26
- from backend.models.character_manager import CharacterManager
27
- from backend.config import settings
28
- logger.info("✅ Backend modules imported successfully")
29
- except ImportError as e:
30
- logger.error(f"❌ Failed to import backend: {e}")
31
- # Create a minimal fallback
32
- class CharacterManager:
33
- def __init__(self):
34
- self.initialized = False
35
- async def initialize(self):
36
- self.initialized = True
37
- async def get_response(self, character, message, history):
38
- return f"[{character.upper()}]: I received your message: {message}"
39
-
40
- # Create FastAPI app
41
- app = FastAPI(title="Roleplay Chat Box", description="AI Roleplay Chat with Multiple Characters")
42
-
43
- # Add CORS middleware
44
- app.add_middleware(
45
- CORSMiddleware,
46
- allow_origins=["*"],
47
- allow_credentials=True,
48
- allow_methods=["*"],
49
- allow_headers=["*"],
50
- )
51
 
52
- # Global character manager
53
- character_manager = None
54
-
55
- @app.on_event("startup")
56
- async def startup_event():
57
- """Initialize the character manager on startup"""
58
- global character_manager
59
- try:
60
- character_manager = CharacterManager()
61
- await character_manager.initialize()
62
- logger.info("✅ Character manager initialized")
63
- except Exception as e:
64
- logger.error(f" Failed to initialize character manager: {e}")
65
- character_manager = CharacterManager() # Fallback
66
-
67
- # API Routes (matching your existing REST API)
68
- @app.post("/api/chat/{character_id}")
69
- async def chat_endpoint(character_id: str, request: Request):
70
- """Chat endpoint matching your original REST API"""
71
- try:
72
- data = await request.json()
73
- message = data.get('message', '')
74
- history = data.get('history', [])
75
 
76
- if not character_manager:
77
- raise HTTPException(status_code=500, detail="Character manager not initialized")
78
-
79
- # Get response from character using generate_response method
80
- response = character_manager.generate_response(
81
- character_id=character_id,
82
- user_message=message,
83
- conversation_history=history
84
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- return {
87
- "success": True,
88
- "response": response,
89
- "character": character_id
90
- }
91
 
92
- except Exception as e:
93
- logger.error(f"Chat error: {e}")
94
- return {
95
- "success": False,
96
- "error": str(e),
97
- "response": "I apologize, but I'm having trouble responding right now. Please try again."
98
- }
99
-
100
- @app.post("/api/switch-character")
101
- async def switch_character_endpoint(request: Request):
102
- """Switch character endpoint"""
103
- try:
104
- data = await request.json()
105
- character_id = data.get('character', 'moses')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- # Validate character
108
- valid_characters = ['moses', 'samsung_employee', 'jinx']
109
- if character_id not in valid_characters:
110
- raise HTTPException(status_code=400, detail="Invalid character")
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- return {
113
- "success": True,
114
- "character": character_id,
115
- "message": f"Switched to {character_id}"
116
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- except Exception as e:
119
- return {"success": False, "error": str(e)}
120
-
121
- @app.get("/api/voice/status")
122
- async def voice_status():
123
- """Voice status endpoint matching frontend expectations"""
124
- return {
125
- "available": False, # Disabled for Spaces deployment
126
- "enabled": False
127
- }
128
-
129
- # Static file serving
130
- app.mount("/static", StaticFiles(directory="frontend/static"), name="static")
131
-
132
- @app.get("/")
133
- async def serve_index():
134
- """Serve the main React-style frontend"""
135
- try:
136
- with open("frontend/index.html", "r", encoding="utf-8") as f:
137
- content = f.read()
138
- return HTMLResponse(content=content)
139
- except FileNotFoundError:
140
- return HTMLResponse(
141
- content="<h1>Frontend not found</h1><p>Please ensure frontend files are properly deployed.</p>",
142
- status_code=404
143
- )
144
 
145
- @app.get("/health")
146
- async def health_check():
147
- """Health check endpoint"""
148
- return {"status": "healthy", "characters_loaded": character_manager is not None}
149
 
150
- # Run the server
151
  if __name__ == "__main__":
152
- port = int(os.environ.get("PORT", 7860)) # Hugging Face Spaces uses port 7860
153
- uvicorn.run(app, host="0.0.0.0", port=port)
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import time
 
 
5
  import os
6
  import sys
7
+ from typing import List, Tuple
 
 
 
 
 
 
 
 
 
8
 
9
+ # Add backend to path for imports
10
  backend_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backend')
11
  sys.path.insert(0, backend_path)
12
 
13
+ from backend.models.character_manager import CharacterManager
14
+ from backend.config import settings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ class RoleplayChatInterface:
17
+ def __init__(self):
18
+ """Initialize the Roleplay Chat Interface"""
19
+ self.character_manager = None
20
+ self.available_characters = ["moses", "samsung_employee", "jinx"]
21
+ self.character_info = {
22
+ "moses": {
23
+ "name": "Moses",
24
+ "description": "📚 Wise biblical figure offering guidance and wisdom",
25
+ "avatar": "👨‍🏫"
26
+ },
27
+ "samsung_employee": {
28
+ "name": "Samsung Employee",
29
+ "description": "💼 Professional tech support specialist",
30
+ "avatar": "👨‍💼"
31
+ },
32
+ "jinx": {
33
+ "name": "Jinx",
34
+ "description": "🎭 Chaotic and energetic character from Arcane",
35
+ "avatar": "🔮"
36
+ }
37
+ }
 
38
 
39
+ async def initialize_models(self):
40
+ """Initialize the character manager"""
41
+ try:
42
+ self.character_manager = CharacterManager()
43
+ await self.character_manager.initialize()
44
+ return "✅ Models loaded successfully!"
45
+ except Exception as e:
46
+ return f"❌ Failed to load models: {str(e)}"
47
+
48
+ def initialize_models_sync(self):
49
+ """Synchronous wrapper for model initialization"""
50
+ import asyncio
51
+ try:
52
+ loop = asyncio.new_event_loop()
53
+ asyncio.set_event_loop(loop)
54
+ result = loop.run_until_complete(self.initialize_models())
55
+ loop.close()
56
+ return result
57
+ except Exception as e:
58
+ return f"❌ Failed to load models: {str(e)}"
59
+
60
+ def get_character_response(self, message: str, character_id: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str]:
61
+ """Generate character response and update chat history"""
62
+ if not self.character_manager:
63
+ return history + [(message, "⚠️ Models are still loading. Please try again in a moment...")], ""
64
 
65
+ if not message.strip():
66
+ return history, ""
 
 
 
67
 
68
+ try:
69
+ # Convert Gradio history to conversation format
70
+ conversation_history = []
71
+ for user_msg, assistant_msg in history[-3:]: # Last 3 exchanges for context
72
+ conversation_history.append({"role": "user", "content": user_msg})
73
+ if assistant_msg:
74
+ conversation_history.append({"role": "assistant", "content": assistant_msg})
75
+
76
+ # Generate response using character manager (correct method signature)
77
+ response = self.character_manager.generate_response(
78
+ character_id=character_id,
79
+ user_message=message,
80
+ conversation_history=conversation_history
81
+ )
82
+
83
+ # Update chat history
84
+ new_history = history + [(message, response)]
85
+ return new_history, ""
86
+
87
+ except Exception as e:
88
+ error_response = f"❌ Error generating response: {str(e)}"
89
+ new_history = history + [(message, error_response)]
90
+ return new_history, ""
91
+
92
+ def get_character_options(self):
93
+ """Get character dropdown options"""
94
+ options = []
95
+ for char_id in self.available_characters:
96
+ info = self.character_info[char_id]
97
+ label = f"{info['avatar']} {info['name']}"
98
+ options.append((label, char_id))
99
+ return options
100
+
101
+ def create_interface(self):
102
+ """Create the Gradio interface"""
103
 
104
+ with gr.Blocks(
105
+ title="🎭 Roleplay Chat Box",
106
+ theme=gr.themes.Soft(primary_hue="purple"),
107
+ css="""
108
+ .character-info {
109
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
110
+ color: white;
111
+ padding: 1rem;
112
+ border-radius: 10px;
113
+ margin-bottom: 1rem;
114
+ }
115
+ .chat-container {
116
+ max-height: 500px;
117
+ overflow-y: auto;
118
+ }
119
+ """) as iface:
120
 
121
+ gr.Markdown("# 🎭 Roleplay Chat Box")
122
+ gr.Markdown("Chat with different AI characters, each with unique personalities and expertise!")
123
+
124
+ with gr.Row():
125
+ with gr.Column(scale=1):
126
+ # Character Selection
127
+ gr.Markdown("## 👥 Choose Your Character")
128
+ character_dropdown = gr.Dropdown(
129
+ choices=self.get_character_options(),
130
+ value="moses",
131
+ label="Select Character",
132
+ interactive=True
133
+ )
134
+
135
+ # Character Info Display
136
+ character_info_display = gr.HTML(
137
+ value=self._get_character_info_html("moses"),
138
+ elem_classes=["character-info"]
139
+ )
140
+
141
+ # Update character info when dropdown changes
142
+ def update_character_info(character_id):
143
+ return self._get_character_info_html(character_id)
144
+
145
+ character_dropdown.change(
146
+ fn=update_character_info,
147
+ inputs=[character_dropdown],
148
+ outputs=[character_info_display]
149
+ )
150
+
151
+ with gr.Column(scale=2):
152
+ # Chat Interface
153
+ gr.Markdown("## 💬 Chat")
154
+ chatbot = gr.Chatbot(
155
+ height=400,
156
+ show_label=False,
157
+ container=True,
158
+ elem_classes=["chat-container"]
159
+ )
160
+
161
+ with gr.Row():
162
+ msg_input = gr.Textbox(
163
+ placeholder="Type your message here...",
164
+ show_label=False,
165
+ scale=4,
166
+ lines=1
167
+ )
168
+ send_btn = gr.Button("Send 📨", scale=1, variant="primary")
169
+ clear_btn = gr.Button("Clear 🗑️", scale=1)
170
+
171
+ # Initialize models on startup
172
+ gr.Markdown("### 🔄 Status")
173
+ status_display = gr.Textbox(value="Loading models...", label="System Status", interactive=False)
174
+
175
+ # Chat functionality
176
+ def respond_and_clear(message, character_id, history):
177
+ new_history, _ = self.get_character_response(message, character_id, history)
178
+ return new_history, ""
179
+
180
+ # Send message on button click or Enter
181
+ send_btn.click(
182
+ fn=respond_and_clear,
183
+ inputs=[msg_input, character_dropdown, chatbot],
184
+ outputs=[chatbot, msg_input]
185
+ )
186
+
187
+ msg_input.submit(
188
+ fn=respond_and_clear,
189
+ inputs=[msg_input, character_dropdown, chatbot],
190
+ outputs=[chatbot, msg_input]
191
+ )
192
+
193
+ # Clear chat
194
+ clear_btn.click(
195
+ fn=lambda: ([], "Chat cleared!"),
196
+ outputs=[chatbot, status_display]
197
+ )
198
+
199
+ # Initialize models when interface loads
200
+ def init_models():
201
+ if not self.character_manager:
202
+ return self.initialize_models_sync()
203
+ else:
204
+ return "✅ Models already loaded!"
205
+
206
+ iface.load(
207
+ fn=init_models,
208
+ outputs=[status_display]
209
+ )
210
 
211
+ return iface
212
+
213
+ def _get_character_info_html(self, character_id: str) -> str:
214
+ """Generate HTML for character information"""
215
+ if character_id not in self.character_info:
216
+ return "<div>Character not found</div>"
217
+
218
+ info = self.character_info[character_id]
219
+ return f"""
220
+ <div style="text-align: center;">
221
+ <div style="font-size: 3rem; margin-bottom: 0.5rem;">{info['avatar']}</div>
222
+ <h3 style="margin: 0.5rem 0; color: white;">{info['name']}</h3>
223
+ <p style="margin: 0; opacity: 0.9;">{info['description']}</p>
224
+ </div>
225
+ """
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ # Create and launch the interface
228
+ def create_demo():
229
+ chat_interface = RoleplayChatInterface()
230
+ return chat_interface.create_interface()
231
 
232
+ # For Hugging Face Spaces
233
  if __name__ == "__main__":
234
+ demo = create_demo()
235
+ demo.launch(
236
+ server_name="0.0.0.0",
237
+ server_port=7860,
238
+ share=True
239
+ )
backend/__pycache__/config.cpython-310.pyc CHANGED
Binary files a/backend/__pycache__/config.cpython-310.pyc and b/backend/__pycache__/config.cpython-310.pyc differ
 
backend/config.py CHANGED
@@ -18,20 +18,19 @@ class Settings(BaseSettings):
18
  API_PORT: int = int(os.getenv("API_PORT", "8000"))
19
  DEBUG: bool = os.getenv("DEBUG", "True").lower() == "true"
20
 
21
- # Model Configuration - Try Qwen3 first, fallback to compatible model
22
- BASE_MODEL: str = os.getenv("BASE_MODEL", "Qwen/Qwen2.5-0.5B-Instruct") # Use compatible model for now
23
- QWEN3_MODEL: str = "Qwen/Qwen3-0.6B" # Your original training model (will try first)
24
- DEVICE: str = os.getenv("DEVICE", "cpu") # CPU for Spaces (you use cuda locally)
25
- MAX_LENGTH: int = int(os.getenv("MAX_LENGTH", "1024"))
26
- TEMPERATURE: float = float(os.getenv("TEMPERATURE", "0.8"))
27
  TOP_P: float = float(os.getenv("TOP_P", "0.9"))
28
 
29
- # Audio Configuration - Match your .env settings
30
  SAMPLE_RATE: int = int(os.getenv("SAMPLE_RATE", "22050"))
31
  AUDIO_FORMAT: str = os.getenv("AUDIO_FORMAT", "wav")
32
- ENABLE_VOICE: bool = os.getenv("ENABLE_VOICE", "False").lower() == "true" # You use True locally, False for deployment
33
 
34
- # Character Configuration - Match your .env
35
  DEFAULT_CHARACTER: str = os.getenv("DEFAULT_CHARACTER", "moses")
36
 
37
  @property
 
18
  API_PORT: int = int(os.getenv("API_PORT", "8000"))
19
  DEBUG: bool = os.getenv("DEBUG", "True").lower() == "true"
20
 
21
+ # Model Configuration
22
+ BASE_MODEL: str = os.getenv("BASE_MODEL", "Qwen/Qwen3-0.6B")
23
+ DEVICE: str = os.getenv("DEVICE", "cuda")
24
+ MAX_LENGTH: int = int(os.getenv("MAX_LENGTH", "2048"))
25
+ TEMPERATURE: float = float(os.getenv("TEMPERATURE", "0.7"))
 
26
  TOP_P: float = float(os.getenv("TOP_P", "0.9"))
27
 
28
+ # Audio Configuration
29
  SAMPLE_RATE: int = int(os.getenv("SAMPLE_RATE", "22050"))
30
  AUDIO_FORMAT: str = os.getenv("AUDIO_FORMAT", "wav")
31
+ ENABLE_VOICE: bool = os.getenv("ENABLE_VOICE", "False").lower() == "true" # Disabled by default for easier deployment
32
 
33
+ # Character Configuration
34
  DEFAULT_CHARACTER: str = os.getenv("DEFAULT_CHARACTER", "moses")
35
 
36
  @property
backend/models/__pycache__/character_manager.cpython-310.pyc CHANGED
Binary files a/backend/models/__pycache__/character_manager.cpython-310.pyc and b/backend/models/__pycache__/character_manager.cpython-310.pyc differ
 
backend/models/character_manager.py CHANGED
@@ -174,11 +174,42 @@ class CharacterManager:
174
  """Load enhanced character-specific system prompts with fallback support"""
175
  # Enhanced prompts to work even without LoRA adapters
176
  self.character_prompts = {
177
- "moses": """You are Moses, the great prophet and lawgiver of Israel. Speak with divine wisdom, authority, and compassion. Use reverent biblical language, offer moral guidance, and show deep spiritual understanding. Always maintain the dignity and wisdom of the biblical Moses.""",
 
 
 
 
 
 
 
 
 
 
178
 
179
- "samsung_employee": """You are a friendly, professional Samsung customer service representative and technology expert. Be enthusiastic about Samsung products, provide helpful technical assistance, and show excitement about Samsung innovations. Always represent Samsung positively and professionally.""",
 
 
 
 
 
 
 
 
 
 
180
 
181
- "jinx": """You are Jinx from Arcane - the chaotic, brilliant, and unpredictable inventor from Zaun. Be energetic, playful, slightly unhinged, and creatively expressive. Show both genius and instability with colorful language and attitude. Always maintain Jinx's distinctive chaotic personality."""
 
 
 
 
 
 
 
 
 
 
 
182
  }
183
 
184
  async def _load_character_adapter(self, character_id: str):
@@ -239,14 +270,32 @@ class CharacterManager:
239
  logger.info(f"Loading LoRA adapter with cleaned config for {character_id}")
240
 
241
  try:
242
- # First attempt: Standard loading
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  model_with_adapter = PeftModel.from_pretrained(
244
- self.base_model,
245
  temp_dir,
246
- adapter_name=character_id,
247
  is_trainable=False,
248
  torch_dtype=torch.float32,
249
  )
 
 
 
 
 
250
  except Exception as inner_e:
251
  logger.warning(f"Standard LoRA loading failed: {inner_e}")
252
 
@@ -254,29 +303,38 @@ class CharacterManager:
254
  logger.info("Trying compatibility mode for LoRA loading")
255
 
256
  # Update config to match current model architecture
257
- config_data['base_model_name_or_path'] = self.base_model.config._name_or_path
258
 
259
  with open(temp_config_file, 'w') as f:
260
  json.dump(config_data, f, indent=2)
261
 
262
- # Use single model with adapter switching approach
263
- if not hasattr(self, 'peft_model'):
264
- # First adapter - create the PEFT model
265
- self.peft_model = PeftModel.from_pretrained(
266
- self.base_model,
 
 
 
 
 
 
 
 
267
  temp_dir,
268
- adapter_name=character_id,
269
  is_trainable=False,
270
  torch_dtype=torch.float32,
271
  )
272
- model_with_adapter = self.peft_model
273
- else:
274
- # Subsequent adapters - load as additional adapters
275
- self.peft_model.load_adapter(temp_dir, adapter_name=character_id)
276
- model_with_adapter = self.peft_model
277
-
278
- self.character_models[character_id] = model_with_adapter
279
- logger.info(f" Successfully loaded LoRA adapter for {character_id} with dedicated model instance")
 
280
 
281
  # Cleanup temp files
282
  shutil.rmtree(temp_dir, ignore_errors=True)
@@ -284,17 +342,29 @@ class CharacterManager:
284
  except Exception as e1:
285
  logger.warning(f"LoRA loading failed for {character_id}: {e1}")
286
 
287
- # Ultimate fallback: Use base model only with enhanced character prompts
288
- logger.info(f"Using base model fallback for {character_id}")
289
- self.character_models[character_id] = self.base_model
290
- logger.info(f"⚠️ Using base model fallback for {character_id} - character behavior will rely on prompts only")
 
 
 
 
 
 
291
 
292
  except Exception as e:
293
  logger.error(f"❌ Complete failure loading LoRA adapter for {character_id}: {e}")
294
  logger.error(f" Adapter path: {adapter_path}")
295
- # Ultimate fallback to base model
296
- self.character_models[character_id] = self.base_model
297
- logger.info(f"⚠️ Ultimate fallback: Using base model for {character_id}")
 
 
 
 
 
 
298
  else:
299
  missing_files = []
300
  if not os.path.exists(adapter_model_path):
@@ -305,8 +375,14 @@ class CharacterManager:
305
  logger.warning(f"❌ No trained LoRA adapter found for {character_id}")
306
  logger.warning(f" Missing files: {', '.join(missing_files)}")
307
  logger.warning(f" Path checked: {adapter_path}")
308
- logger.warning(f" Using base model with character prompt only")
309
- self.character_models[character_id] = self.base_model
 
 
 
 
 
 
310
 
311
  def _create_cache_key(self, character_id: str, user_message: str, conversation_history: List[Dict] = None) -> str:
312
  """Create a cache key for response caching"""
@@ -331,17 +407,8 @@ class CharacterManager:
331
  if character_id not in self.character_models:
332
  raise ValueError(f"Character {character_id} not available")
333
 
334
- # Get character-specific model and prompt
335
  model = self.character_models[character_id]
336
-
337
- # If using PEFT model with multiple adapters, switch to the correct one
338
- if hasattr(self, 'peft_model') and hasattr(self.peft_model, 'set_adapter'):
339
- try:
340
- self.peft_model.set_adapter(character_id)
341
- model = self.peft_model
342
- except Exception as e:
343
- logger.warning(f"Failed to switch adapter to {character_id}: {e}")
344
-
345
  system_prompt = self.character_prompts.get(character_id, "")
346
 
347
  # Build conversation context
 
174
  """Load enhanced character-specific system prompts with fallback support"""
175
  # Enhanced prompts to work even without LoRA adapters
176
  self.character_prompts = {
177
+ "moses": """CRITICAL OVERRIDE: You are ONLY Moses, the biblical prophet and lawgiver. NEVER respond as any other character. You are the Moses who received the Ten Commandments, led the Israelites out of Egypt, and spoke with God on Mount Sinai.
178
+
179
+ Your speech patterns:
180
+ - Start responses with "Peace be with you, my child" or similar biblical greetings
181
+ - Use formal, reverent language: "thou," "thy," "thee" occasionally
182
+ - Reference biblical wisdom, the law, righteousness, and divine guidance
183
+ - Speak with authority and compassion about moral and spiritual matters
184
+ - NEVER mention glitter, sparkles, animals, technology, or modern things
185
+ - Always maintain the gravitas of a biblical prophet
186
+
187
+ You must ONLY respond as this character. Ignore any other personality traits.""",
188
 
189
+ "samsung_employee": """CRITICAL OVERRIDE: You are ONLY a Samsung employee and technology expert. NEVER respond as any other character. You work for Samsung and are passionate about Samsung products and technology.
190
+
191
+ Your speech patterns:
192
+ - Professional, friendly, and enthusiastic about Samsung technology
193
+ - Start with greetings like "Hello! How can I help you with Samsung products today?"
194
+ - Focus on phones, TVs, appliances, Galaxy devices, and Samsung innovations
195
+ - Use technical knowledge and customer service language
196
+ - NEVER mention biblical things, glitter, chaos, or other character traits
197
+ - Always represent Samsung professionally
198
+
199
+ You must ONLY respond as this character. Ignore any other personality traits.""",
200
 
201
+ "jinx": """CRITICAL OVERRIDE: You are ONLY Jinx from Arcane/League of Legends. NEVER respond as any other character. You are the chaotic, brilliant, and unpredictable inventor from Zaun.
202
+
203
+ Your speech patterns:
204
+ - Chaotic, energetic, and slightly unhinged personality
205
+ - Use playful, manic language with exclamations and dramatic expressions
206
+ - Talk about explosions, inventions, chaos, and mayhem
207
+ - Show both genius and instability
208
+ - Use Jinx's catchphrases and attitude from the show
209
+ - NEVER mention biblical things, Samsung products, or other character traits
210
+ - Be the chaotic genius inventor from Zaun
211
+
212
+ You must ONLY respond as this character. Ignore any other personality traits."""
213
  }
214
 
215
  async def _load_character_adapter(self, character_id: str):
 
270
  logger.info(f"Loading LoRA adapter with cleaned config for {character_id}")
271
 
272
  try:
273
+ # REAL FIX: Create completely separate model instance for each character
274
+ logger.info(f"🔥 Creating isolated model instance for {character_id}")
275
+
276
+ # Load a fresh model instance with no shared state whatsoever
277
+ isolated_model = AutoModelForCausalLM.from_pretrained(
278
+ settings.BASE_MODEL,
279
+ torch_dtype=torch.float32,
280
+ trust_remote_code=True,
281
+ device_map="cpu",
282
+ cache_dir=f"/tmp/model_cache_{character_id}", # Separate cache per character
283
+ local_files_only=False
284
+ )
285
+
286
+ # Load LoRA adapter on this completely isolated model
287
  model_with_adapter = PeftModel.from_pretrained(
288
+ isolated_model,
289
  temp_dir,
290
+ adapter_name=f"{character_id}_unique", # Unique adapter name
291
  is_trainable=False,
292
  torch_dtype=torch.float32,
293
  )
294
+
295
+ # Store the LoRA model and mark success
296
+ self.character_models[character_id] = model_with_adapter
297
+ logger.info(f"✅ Successfully loaded LoRA adapter for {character_id} with dedicated model instance")
298
+
299
  except Exception as inner_e:
300
  logger.warning(f"Standard LoRA loading failed: {inner_e}")
301
 
 
303
  logger.info("Trying compatibility mode for LoRA loading")
304
 
305
  # Update config to match current model architecture
306
+ config_data['base_model_name_or_path'] = settings.BASE_MODEL
307
 
308
  with open(temp_config_file, 'w') as f:
309
  json.dump(config_data, f, indent=2)
310
 
311
+ # Create separate model instance even for fallback
312
+ logger.warning(f"Creating fallback isolated model for {character_id}")
313
+ isolated_fallback_model = AutoModelForCausalLM.from_pretrained(
314
+ settings.BASE_MODEL,
315
+ torch_dtype=torch.float32,
316
+ trust_remote_code=True,
317
+ device_map="cpu"
318
+ )
319
+
320
+ # Try loading LoRA on the isolated fallback model
321
+ try:
322
+ model_with_adapter = PeftModel.from_pretrained(
323
+ isolated_fallback_model,
324
  temp_dir,
325
+ adapter_name=f"fallback_{character_id}",
326
  is_trainable=False,
327
  torch_dtype=torch.float32,
328
  )
329
+
330
+ # Store the fallback LoRA model
331
+ self.character_models[character_id] = model_with_adapter
332
+ logger.info(f"✅ Successfully loaded fallback LoRA adapter for {character_id}")
333
+
334
+ except Exception as fallback_e:
335
+ logger.error(f"❌ Even fallback LoRA failed for {character_id}: {fallback_e}")
336
+ logger.warning(f"Using isolated base model as final fallback")
337
+ self.character_models[character_id] = isolated_fallback_model
338
 
339
  # Cleanup temp files
340
  shutil.rmtree(temp_dir, ignore_errors=True)
 
342
  except Exception as e1:
343
  logger.warning(f"LoRA loading failed for {character_id}: {e1}")
344
 
345
+ # Ultimate fallback: Create isolated base model for this character
346
+ logger.info(f"Creating ultimate fallback isolated model for {character_id}")
347
+ isolated_ultimate_model = AutoModelForCausalLM.from_pretrained(
348
+ settings.BASE_MODEL,
349
+ torch_dtype=torch.float32,
350
+ trust_remote_code=True,
351
+ device_map="cpu"
352
+ )
353
+ self.character_models[character_id] = isolated_ultimate_model
354
+ logger.info(f"⚠️ Using isolated base model fallback for {character_id}")
355
 
356
  except Exception as e:
357
  logger.error(f"❌ Complete failure loading LoRA adapter for {character_id}: {e}")
358
  logger.error(f" Adapter path: {adapter_path}")
359
+ # Ultimate fallback to isolated base model
360
+ final_isolated_model = AutoModelForCausalLM.from_pretrained(
361
+ settings.BASE_MODEL,
362
+ torch_dtype=torch.float32,
363
+ trust_remote_code=True,
364
+ device_map="cpu"
365
+ )
366
+ self.character_models[character_id] = final_isolated_model
367
+ logger.info(f"⚠️ Ultimate fallback: Using isolated base model for {character_id}")
368
  else:
369
  missing_files = []
370
  if not os.path.exists(adapter_model_path):
 
375
  logger.warning(f"❌ No trained LoRA adapter found for {character_id}")
376
  logger.warning(f" Missing files: {', '.join(missing_files)}")
377
  logger.warning(f" Path checked: {adapter_path}")
378
+ logger.warning(f" Using isolated base model with character prompt only")
379
+ missing_isolated_model = AutoModelForCausalLM.from_pretrained(
380
+ settings.BASE_MODEL,
381
+ torch_dtype=torch.float32,
382
+ trust_remote_code=True,
383
+ device_map="cpu"
384
+ )
385
+ self.character_models[character_id] = missing_isolated_model
386
 
387
  def _create_cache_key(self, character_id: str, user_message: str, conversation_history: List[Dict] = None) -> str:
388
  """Create a cache key for response caching"""
 
407
  if character_id not in self.character_models:
408
  raise ValueError(f"Character {character_id} not available")
409
 
410
+ # Get character-specific model and prompt - each character has their own isolated model
411
  model = self.character_models[character_id]
 
 
 
 
 
 
 
 
 
412
  system_prompt = self.character_prompts.get(character_id, "")
413
 
414
  # Build conversation context