Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
-
|
| 2 |
import os
|
| 3 |
import sys
|
|
|
|
| 4 |
import uuid
|
|
|
|
| 5 |
import threading
|
| 6 |
import importlib.util
|
| 7 |
|
| 8 |
import torch
|
| 9 |
-
from flask import Flask, request, jsonify
|
| 10 |
from flask_cors import CORS
|
| 11 |
from transformers import AutoConfig, AutoModelForCausalLM
|
| 12 |
from tokenizers import Tokenizer
|
|
@@ -25,6 +26,13 @@ TOP_P = 0.6
|
|
| 25 |
PENALTY = 1.2
|
| 26 |
TEMPERATURE = 1.0
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
app = Flask(__name__)
|
| 29 |
CORS(app)
|
| 30 |
|
|
@@ -43,8 +51,6 @@ print(f"[Bridge] Loading {MODEL_ID}...")
|
|
| 43 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 44 |
print(f"[Bridge] Selected device: {device}")
|
| 45 |
|
| 46 |
-
# Download and import Luna's actual custom architecture.
|
| 47 |
-
# No separate Python file is required in your application folder.
|
| 48 |
architecture_path = hf_hub_download(
|
| 49 |
repo_id=MODEL_ID,
|
| 50 |
filename="modeling_luna.py"
|
|
@@ -62,7 +68,6 @@ module = importlib.util.module_from_spec(spec)
|
|
| 62 |
sys.modules[spec.name] = module
|
| 63 |
spec.loader.exec_module(module)
|
| 64 |
|
| 65 |
-
# Load Luna's custom Transformers configuration and model.
|
| 66 |
config = AutoConfig.from_pretrained(
|
| 67 |
MODEL_ID,
|
| 68 |
trust_remote_code=True
|
|
@@ -77,7 +82,6 @@ model = AutoModelForCausalLM.from_pretrained(
|
|
| 77 |
model = model.to(device)
|
| 78 |
model.eval()
|
| 79 |
|
| 80 |
-
# Load Luna's tokenizer.
|
| 81 |
tokenizer_path = hf_hub_download(
|
| 82 |
repo_id=MODEL_ID,
|
| 83 |
filename="luna_tokenizer.json"
|
|
@@ -120,7 +124,154 @@ def generate_response(prompt, history):
|
|
| 120 |
|
| 121 |
|
| 122 |
# ============================================================
|
| 123 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
# ============================================================
|
| 125 |
|
| 126 |
@app.route("/ask", methods=["POST"])
|
|
@@ -150,11 +301,8 @@ def ask():
|
|
| 150 |
"logs": "Empty input."
|
| 151 |
}), 400
|
| 152 |
|
| 153 |
-
# The browser should reuse this ID for the same conversation.
|
| 154 |
session_id = str(data.get("session_id") or uuid.uuid4())
|
| 155 |
|
| 156 |
-
# Keep history retrieval, inference, and updates in one
|
| 157 |
-
# critical section so concurrent requests cannot corrupt it.
|
| 158 |
with generation_lock:
|
| 159 |
history = sessions.setdefault(session_id, [])
|
| 160 |
|
|
@@ -183,7 +331,7 @@ def ask():
|
|
| 183 |
|
| 184 |
|
| 185 |
# ============================================================
|
| 186 |
-
# Clear conversation history
|
| 187 |
# ============================================================
|
| 188 |
|
| 189 |
@app.route("/clear", methods=["POST"])
|
|
@@ -205,20 +353,6 @@ def clear_history():
|
|
| 205 |
})
|
| 206 |
|
| 207 |
|
| 208 |
-
# ============================================================
|
| 209 |
-
# Health check
|
| 210 |
-
# ============================================================
|
| 211 |
-
|
| 212 |
-
@app.route("/", methods=["GET"])
|
| 213 |
-
def health():
|
| 214 |
-
return jsonify({
|
| 215 |
-
"service": "Aoban Luna 2 Flash",
|
| 216 |
-
"model": MODEL_ID,
|
| 217 |
-
"device": str(device),
|
| 218 |
-
"status": "online"
|
| 219 |
-
})
|
| 220 |
-
|
| 221 |
-
|
| 222 |
# ============================================================
|
| 223 |
# Start server
|
| 224 |
# ============================================================
|
|
@@ -232,4 +366,4 @@ if __name__ == "__main__":
|
|
| 232 |
host="0.0.0.0",
|
| 233 |
port=port,
|
| 234 |
threaded=True
|
| 235 |
-
)
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import sys
|
| 3 |
+
import json
|
| 4 |
import uuid
|
| 5 |
+
import hmac
|
| 6 |
import threading
|
| 7 |
import importlib.util
|
| 8 |
|
| 9 |
import torch
|
| 10 |
+
from flask import Flask, request, jsonify, Response, stream_with_context
|
| 11 |
from flask_cors import CORS
|
| 12 |
from transformers import AutoConfig, AutoModelForCausalLM
|
| 13 |
from tokenizers import Tokenizer
|
|
|
|
| 26 |
PENALTY = 1.2
|
| 27 |
TEMPERATURE = 1.0
|
| 28 |
|
| 29 |
+
# Poe sends this as "Authorization: Bearer <key>" on every request.
|
| 30 |
+
# Set it as a Secret in your Hugging Face Space (POE_ACCESS_KEY).
|
| 31 |
+
# If unset, Poe requests are accepted without auth (fine for testing only).
|
| 32 |
+
POE_ACCESS_KEY = os.environ.get("POE_ACCESS_KEY", "")
|
| 33 |
+
|
| 34 |
+
POE_INTRO_MESSAGE = "Hi! I'm Aoban Luna 2 Flash. Say something."
|
| 35 |
+
|
| 36 |
app = Flask(__name__)
|
| 37 |
CORS(app)
|
| 38 |
|
|
|
|
| 51 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 52 |
print(f"[Bridge] Selected device: {device}")
|
| 53 |
|
|
|
|
|
|
|
| 54 |
architecture_path = hf_hub_download(
|
| 55 |
repo_id=MODEL_ID,
|
| 56 |
filename="modeling_luna.py"
|
|
|
|
| 68 |
sys.modules[spec.name] = module
|
| 69 |
spec.loader.exec_module(module)
|
| 70 |
|
|
|
|
| 71 |
config = AutoConfig.from_pretrained(
|
| 72 |
MODEL_ID,
|
| 73 |
trust_remote_code=True
|
|
|
|
| 82 |
model = model.to(device)
|
| 83 |
model.eval()
|
| 84 |
|
|
|
|
| 85 |
tokenizer_path = hf_hub_download(
|
| 86 |
repo_id=MODEL_ID,
|
| 87 |
filename="luna_tokenizer.json"
|
|
|
|
| 124 |
|
| 125 |
|
| 126 |
# ============================================================
|
| 127 |
+
# Poe helpers
|
| 128 |
+
# ============================================================
|
| 129 |
+
|
| 130 |
+
def sse(event, data):
|
| 131 |
+
"""Format one Server-Sent Event the way Poe expects."""
|
| 132 |
+
return f"event: {event}\ndata: {json.dumps(data)}\n\n"
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def poe_authorized():
|
| 136 |
+
if not POE_ACCESS_KEY:
|
| 137 |
+
return True
|
| 138 |
+
|
| 139 |
+
header = request.headers.get("Authorization", "")
|
| 140 |
+
token = header[7:] if header.lower().startswith("bearer ") else ""
|
| 141 |
+
return hmac.compare_digest(token, POE_ACCESS_KEY)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def poe_to_luna_history(query):
|
| 145 |
+
"""
|
| 146 |
+
Poe sends the FULL conversation on every request, so Poe bots are
|
| 147 |
+
stateless. Convert it to (prompt, history) in Luna's format.
|
| 148 |
+
|
| 149 |
+
Poe roles: "system" | "user" | "bot"
|
| 150 |
+
Luna roles: "user" | "assistant"
|
| 151 |
+
"""
|
| 152 |
+
messages = []
|
| 153 |
+
|
| 154 |
+
for msg in query:
|
| 155 |
+
if not isinstance(msg, dict):
|
| 156 |
+
continue
|
| 157 |
+
|
| 158 |
+
role = msg.get("role")
|
| 159 |
+
content = msg.get("content")
|
| 160 |
+
|
| 161 |
+
if not isinstance(content, str) or not content.strip():
|
| 162 |
+
continue
|
| 163 |
+
|
| 164 |
+
if role == "user":
|
| 165 |
+
messages.append({"role": "user", "content": content.strip()})
|
| 166 |
+
elif role == "bot":
|
| 167 |
+
messages.append({"role": "assistant", "content": content.strip()})
|
| 168 |
+
# "system" messages are ignored; Luna has no system prompt.
|
| 169 |
+
|
| 170 |
+
# The prompt is the latest user message; everything before it is history.
|
| 171 |
+
prompt = ""
|
| 172 |
+
history = []
|
| 173 |
+
|
| 174 |
+
for i in range(len(messages) - 1, -1, -1):
|
| 175 |
+
if messages[i]["role"] == "user":
|
| 176 |
+
prompt = messages[i]["content"]
|
| 177 |
+
history = messages[:i]
|
| 178 |
+
break
|
| 179 |
+
|
| 180 |
+
return prompt, history
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def handle_poe_query(body):
|
| 184 |
+
prompt, history = poe_to_luna_history(body.get("query", []))
|
| 185 |
+
|
| 186 |
+
def stream():
|
| 187 |
+
yield sse("meta", {
|
| 188 |
+
"content_type": "text/markdown",
|
| 189 |
+
"linkify": True,
|
| 190 |
+
"suggested_replies": False
|
| 191 |
+
})
|
| 192 |
+
|
| 193 |
+
if not prompt:
|
| 194 |
+
yield sse("error", {
|
| 195 |
+
"text": "I didn't get a message to respond to.",
|
| 196 |
+
"allow_retry": False
|
| 197 |
+
})
|
| 198 |
+
yield sse("done", {})
|
| 199 |
+
return
|
| 200 |
+
|
| 201 |
+
try:
|
| 202 |
+
with generation_lock:
|
| 203 |
+
response = generate_response(prompt, history)
|
| 204 |
+
|
| 205 |
+
if not response:
|
| 206 |
+
response = "..."
|
| 207 |
+
|
| 208 |
+
yield sse("text", {"text": response})
|
| 209 |
+
|
| 210 |
+
except Exception as e:
|
| 211 |
+
app.logger.exception("Luna generation failed (Poe)")
|
| 212 |
+
yield sse("error", {
|
| 213 |
+
"text": f"Brain Error: {e}",
|
| 214 |
+
"allow_retry": True
|
| 215 |
+
})
|
| 216 |
+
|
| 217 |
+
yield sse("done", {})
|
| 218 |
+
|
| 219 |
+
return Response(
|
| 220 |
+
stream_with_context(stream()),
|
| 221 |
+
mimetype="text/event-stream",
|
| 222 |
+
headers={
|
| 223 |
+
"Cache-Control": "no-cache",
|
| 224 |
+
"X-Accel-Buffering": "no"
|
| 225 |
+
}
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def handle_poe_request(body):
|
| 230 |
+
if not poe_authorized():
|
| 231 |
+
return jsonify({"error": "Unauthorized."}), 401
|
| 232 |
+
|
| 233 |
+
request_type = body.get("type")
|
| 234 |
+
|
| 235 |
+
if request_type == "query":
|
| 236 |
+
return handle_poe_query(body)
|
| 237 |
+
|
| 238 |
+
if request_type == "settings":
|
| 239 |
+
return jsonify({
|
| 240 |
+
"server_bot_dependencies": {},
|
| 241 |
+
"allow_attachments": False,
|
| 242 |
+
"introduction_message": POE_INTRO_MESSAGE
|
| 243 |
+
})
|
| 244 |
+
|
| 245 |
+
# "report_feedback" and "report_error" just need a 200 JSON reply.
|
| 246 |
+
return jsonify({})
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# ============================================================
|
| 250 |
+
# Poe endpoint + health check (same URL: "/")
|
| 251 |
+
# ============================================================
|
| 252 |
+
# Poe POSTs to the bot URL you register (the Space's root URL).
|
| 253 |
+
# The browser/health check keeps using GET on the same route.
|
| 254 |
+
|
| 255 |
+
@app.route("/", methods=["GET", "POST"])
|
| 256 |
+
def root():
|
| 257 |
+
if request.method == "POST":
|
| 258 |
+
body = request.get_json(silent=True)
|
| 259 |
+
|
| 260 |
+
if not isinstance(body, dict) or "type" not in body:
|
| 261 |
+
return jsonify({"error": "Invalid Poe request."}), 400
|
| 262 |
+
|
| 263 |
+
return handle_poe_request(body)
|
| 264 |
+
|
| 265 |
+
return jsonify({
|
| 266 |
+
"service": "Aoban Luna 2 Flash",
|
| 267 |
+
"model": MODEL_ID,
|
| 268 |
+
"device": str(device),
|
| 269 |
+
"status": "online"
|
| 270 |
+
})
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
# ============================================================
|
| 274 |
+
# Chat endpoint (website / your JS script) - unchanged
|
| 275 |
# ============================================================
|
| 276 |
|
| 277 |
@app.route("/ask", methods=["POST"])
|
|
|
|
| 301 |
"logs": "Empty input."
|
| 302 |
}), 400
|
| 303 |
|
|
|
|
| 304 |
session_id = str(data.get("session_id") or uuid.uuid4())
|
| 305 |
|
|
|
|
|
|
|
| 306 |
with generation_lock:
|
| 307 |
history = sessions.setdefault(session_id, [])
|
| 308 |
|
|
|
|
| 331 |
|
| 332 |
|
| 333 |
# ============================================================
|
| 334 |
+
# Clear conversation history - unchanged
|
| 335 |
# ============================================================
|
| 336 |
|
| 337 |
@app.route("/clear", methods=["POST"])
|
|
|
|
| 353 |
})
|
| 354 |
|
| 355 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
# ============================================================
|
| 357 |
# Start server
|
| 358 |
# ============================================================
|
|
|
|
| 366 |
host="0.0.0.0",
|
| 367 |
port=port,
|
| 368 |
threaded=True
|
| 369 |
+
)
|