Spaces:
Running
Running
🔧 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 +223 -137
- backend/__pycache__/config.cpython-310.pyc +0 -0
- backend/config.py +8 -9
- backend/models/__pycache__/character_manager.cpython-310.pyc +0 -0
- backend/models/character_manager.py +107 -40
app.py
CHANGED
|
@@ -1,153 +1,239 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
import os
|
| 8 |
import sys
|
| 9 |
-
import
|
| 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 |
-
|
| 25 |
-
|
| 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 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
history = data.get('history', [])
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
"
|
| 88 |
-
"response": response,
|
| 89 |
-
"character": character_id
|
| 90 |
-
}
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
"
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
"
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 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 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
return
|
| 149 |
|
| 150 |
-
#
|
| 151 |
if __name__ == "__main__":
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 22 |
-
BASE_MODEL: str = os.getenv("BASE_MODEL", "Qwen/
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
TEMPERATURE: float = float(os.getenv("TEMPERATURE", "0.8"))
|
| 27 |
TOP_P: float = float(os.getenv("TOP_P", "0.9"))
|
| 28 |
|
| 29 |
-
# Audio Configuration
|
| 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" #
|
| 33 |
|
| 34 |
-
# Character Configuration
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
"samsung_employee": """You are a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
-
"jinx": """You are Jinx from Arcane
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
model_with_adapter = PeftModel.from_pretrained(
|
| 244 |
-
|
| 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'] =
|
| 258 |
|
| 259 |
with open(temp_config_file, 'w') as f:
|
| 260 |
json.dump(config_data, f, indent=2)
|
| 261 |
|
| 262 |
-
#
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
temp_dir,
|
| 268 |
-
adapter_name=character_id,
|
| 269 |
is_trainable=False,
|
| 270 |
torch_dtype=torch.float32,
|
| 271 |
)
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
| 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:
|
| 288 |
-
logger.info(f"
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|