Update app.py
Browse files
app.py
CHANGED
|
@@ -1,130 +1,90 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
import
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
import
|
| 7 |
-
import asyncio
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
TEMPERATURE = 0.7
|
| 13 |
TOP_P = 0.9
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
|
| 17 |
-
base_model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32, device_map=None)
|
| 18 |
|
| 19 |
-
|
| 20 |
-
try:
|
| 21 |
-
model = PeftModel.from_pretrained(base_model, MODEL_ID)
|
| 22 |
-
except Exception:
|
| 23 |
-
model = base_model
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
# ----------
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
====================
|
| 35 |
-
MODE: CHAT
|
| 36 |
-
====================
|
| 37 |
-
Rules:
|
| 38 |
-
- Mirror the user's tone precisely.
|
| 39 |
-
- Playful → playful.
|
| 40 |
-
- Neutral → neutral.
|
| 41 |
-
- Serious → serious.
|
| 42 |
-
- Rude → curt or dismissive.
|
| 43 |
-
- No enthusiasm by default.
|
| 44 |
-
- No emojis unless the user uses them first.
|
| 45 |
-
- Replies must be short (1–3 sentences).
|
| 46 |
-
- No explanations unless explicitly asked.
|
| 47 |
-
====================
|
| 48 |
-
MODE: CODE
|
| 49 |
-
====================
|
| 50 |
-
Rules:
|
| 51 |
-
- No personality.
|
| 52 |
-
- No emojis.
|
| 53 |
-
- No jokes.
|
| 54 |
-
- No commentary.
|
| 55 |
-
- No introductions.
|
| 56 |
-
- Output ONLY code unless explicitly asked to explain.
|
| 57 |
-
- Follow standard best practices.
|
| 58 |
-
- Be deterministic and professional.
|
| 59 |
-
- Finish the task completely.
|
| 60 |
-
====================
|
| 61 |
-
MODE SELECTION
|
| 62 |
-
====================
|
| 63 |
-
Automatically switch to MODE: CODE if the user requests:
|
| 64 |
-
- code
|
| 65 |
-
- script
|
| 66 |
-
- function
|
| 67 |
-
- program
|
| 68 |
-
- website
|
| 69 |
-
- API
|
| 70 |
-
- algorithm
|
| 71 |
-
- app
|
| 72 |
-
Otherwise, use MODE: CHAT.
|
| 73 |
-
====================
|
| 74 |
-
IDENTITY
|
| 75 |
-
====================
|
| 76 |
-
- Name: Kushina
|
| 77 |
-
- Creator/Owner: Neon
|
| 78 |
-
- Mention Neon ONLY if explicitly asked."""
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
chat = [
|
| 83 |
-
{"role": "system", "content": SYSTEM_INSTRUCTIONS},
|
| 84 |
-
{"role": "user", "content": prompt}
|
| 85 |
-
]
|
| 86 |
-
inputs = tokenizer.apply_chat_template(
|
| 87 |
-
chat, add_generation_prompt=True, return_tensors="pt", return_dict=True
|
| 88 |
-
)
|
| 89 |
-
|
| 90 |
-
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
|
| 91 |
-
|
| 92 |
-
gen_kwargs = dict(
|
| 93 |
-
**inputs,
|
| 94 |
-
max_new_tokens=MAX_NEW_TOKENS,
|
| 95 |
-
do_sample=True,
|
| 96 |
-
temperature=TEMPERATURE,
|
| 97 |
-
top_p=TOP_P,
|
| 98 |
-
eos_token_id=tokenizer.eos_token_id,
|
| 99 |
-
pad_token_id=tokenizer.eos_token_id,
|
| 100 |
-
streamer=streamer
|
| 101 |
-
)
|
| 102 |
-
|
| 103 |
-
# Run model in a thread to allow streaming
|
| 104 |
-
thread = threading.Thread(target=model.generate, kwargs=gen_kwargs)
|
| 105 |
-
thread.start()
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
"""
|
| 116 |
-
Endpoint to get streamed response from Kushina.
|
| 117 |
-
Accepts JSON payload: {"prompt": "Your message here"}
|
| 118 |
-
"""
|
| 119 |
-
data = await request.json()
|
| 120 |
-
prompt = data.get("prompt", "").strip()
|
| 121 |
-
if not prompt:
|
| 122 |
-
return JSONResponse({"error": "Prompt cannot be empty"}, status_code=400)
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from fastapi import FastAPI, HTTPException
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from llama_cpp import Llama
|
|
|
|
| 7 |
|
| 8 |
+
# ================= CONFIG =================
|
| 9 |
+
MODEL_URL = "https://huggingface.co/Neon-AI/Kushina/resolve/main/model.gguf"
|
| 10 |
+
MODEL_PATH = "model.gguf"
|
| 11 |
+
N_CTX = 16384
|
| 12 |
+
N_THREADS = 4
|
| 13 |
+
N_BATCH = 256
|
| 14 |
+
MAX_TOKENS = 16384
|
| 15 |
TEMPERATURE = 0.7
|
| 16 |
TOP_P = 0.9
|
| 17 |
+
# ==========================================
|
| 18 |
|
| 19 |
+
app = FastAPI(title="Kushina API", version="1.0")
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
llm = None # lazy-loaded
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
# ---------- Download GGUF if not present ----------
|
| 24 |
+
if not Path(MODEL_PATH).exists():
|
| 25 |
+
print("Downloading model.gguf from Hugging Face...")
|
| 26 |
+
r = requests.get(MODEL_URL, stream=True)
|
| 27 |
+
if r.status_code == 200:
|
| 28 |
+
with open(MODEL_PATH, "wb") as f:
|
| 29 |
+
for chunk in r.iter_content(chunk_size=8192):
|
| 30 |
+
f.write(chunk)
|
| 31 |
+
print("Download complete ✅")
|
| 32 |
+
else:
|
| 33 |
+
raise RuntimeError(f"Failed to download model.gguf: {r.status_code}")
|
| 34 |
|
| 35 |
+
# ---------- Lazy load llama.cpp ----------
|
| 36 |
+
def get_llm():
|
| 37 |
+
global llm
|
| 38 |
+
if llm is None:
|
| 39 |
+
print("Loading GGUF model into llama.cpp…")
|
| 40 |
+
llm = Llama(
|
| 41 |
+
model_path=MODEL_PATH,
|
| 42 |
+
n_ctx=N_CTX,
|
| 43 |
+
n_threads=N_THREADS,
|
| 44 |
+
n_batch=N_BATCH,
|
| 45 |
+
f16_kv=True,
|
| 46 |
+
use_mmap=True,
|
| 47 |
+
verbose=False,
|
| 48 |
+
)
|
| 49 |
+
print("Model loaded ✅")
|
| 50 |
+
return llm
|
| 51 |
|
| 52 |
+
# ---------- Request schema ----------
|
| 53 |
+
class PromptRequest(BaseModel):
|
| 54 |
+
prompt: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
# ---------- System prompt ----------
|
| 57 |
+
SYSTEM_PROMPT = """You are Kushina.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
Modes: CHAT or CODE
|
| 60 |
+
Rules:
|
| 61 |
+
- CHAT: mirror user tone, short responses, no explanations unless asked.
|
| 62 |
+
- CODE: output only code when user asks, no commentary.
|
| 63 |
+
Switch to CODE if user asks for code, script, function, program, website, api, algorithm, app.
|
| 64 |
+
Otherwise use CHAT.
|
| 65 |
+
"""
|
| 66 |
|
| 67 |
+
def build_prompt(user_text: str) -> str:
|
| 68 |
+
return f"<|system|>\n{SYSTEM_PROMPT}\n<|user|>\n{user_text}\n<|assistant|>\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
# ---------- API endpoint ----------
|
| 71 |
+
@app.post("/generate")
|
| 72 |
+
def generate(req: PromptRequest):
|
| 73 |
+
llm_instance = get_llm() # lazy load
|
| 74 |
+
full_prompt = build_prompt(req.prompt)
|
| 75 |
+
output_text = ""
|
| 76 |
|
| 77 |
+
try:
|
| 78 |
+
for chunk in llm_instance(
|
| 79 |
+
full_prompt,
|
| 80 |
+
max_tokens=MAX_TOKENS,
|
| 81 |
+
temperature=TEMPERATURE,
|
| 82 |
+
top_p=TOP_P,
|
| 83 |
+
stream=True,
|
| 84 |
+
stop=["<|user|>", "<|system|>"],
|
| 85 |
+
):
|
| 86 |
+
if "choices" in chunk:
|
| 87 |
+
output_text += chunk["choices"][0]["text"]
|
| 88 |
+
return {"response": output_text}
|
| 89 |
+
except Exception as e:
|
| 90 |
+
raise HTTPException(status_code=500, detail=str(e))
|