Neon-AI commited on
Commit
eaeb0c2
·
verified ·
1 Parent(s): 4755549

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -117
app.py CHANGED
@@ -1,130 +1,90 @@
1
- from fastapi import FastAPI, Request
2
- from fastapi.responses import StreamingResponse, JSONResponse
3
- import torch
4
- from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
5
- from peft import PeftModel
6
- import threading
7
- import asyncio
8
 
9
- # ---------------- CONFIG ----------------
10
- MODEL_ID = "Neon-AI/Kushina"
11
- MAX_NEW_TOKENS = 16384
 
 
 
 
12
  TEMPERATURE = 0.7
13
  TOP_P = 0.9
 
14
 
15
- # ---------------- LOAD MODEL ----------------
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
- # Try loading LoRA
20
- try:
21
- model = PeftModel.from_pretrained(base_model, MODEL_ID)
22
- except Exception:
23
- model = base_model
24
 
25
- model.to("cpu")
26
- model.eval()
 
 
 
 
 
 
 
 
 
27
 
28
- # ---------------- FASTAPI APP ----------------
29
- app = FastAPI(title="Niche AI API")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # System instructions (same as your Streamlit setup)
32
- SYSTEM_INSTRUCTIONS = """You are Kushina.
33
- You operate in exactly ONE of two modes.
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
- def generate_stream(prompt: str):
81
- """Generator function to stream text tokens."""
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
- # Yield tokens as they come
108
- output_text = ""
109
- for token in streamer:
110
- output_text += token
111
- yield f"{token}" # Could wrap in JSON if desired
 
 
112
 
113
- @app.post("/chat")
114
- async def chat_endpoint(request: Request):
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
- # Stream response
125
- return StreamingResponse(generate_stream(prompt), media_type="text/plain")
 
 
 
 
126
 
127
- # ---------------- OPTIONAL: SIMPLE GET TEST ----------------
128
- @app.get("/")
129
- async def root():
130
- return {"message": "Niche AI API is running. POST /chat with {'prompt': 'text'} to interact."}
 
 
 
 
 
 
 
 
 
 
 
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))