Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.responses import StreamingResponse
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from llama_cpp import Llama
|
| 6 |
+
from huggingface_hub import hf_hub_download
|
| 7 |
+
from typing import List, Optional
|
| 8 |
+
import os
|
| 9 |
+
import json
|
| 10 |
+
import uvicorn
|
| 11 |
+
|
| 12 |
+
app = FastAPI()
|
| 13 |
+
|
| 14 |
+
app.add_middleware(
|
| 15 |
+
CORSMiddleware,
|
| 16 |
+
allow_origins=["*"],
|
| 17 |
+
allow_methods=["*"],
|
| 18 |
+
allow_headers=["*"],
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
MODEL_REPO = "bartowski/Qwen2.5-7B-Instruct-GGUF"
|
| 22 |
+
MODEL_FILE = "Qwen2.5-7B-Instruct-Q4_K_M.gguf"
|
| 23 |
+
|
| 24 |
+
llm: Optional[Llama] = None
|
| 25 |
+
|
| 26 |
+
@app.on_event("startup")
|
| 27 |
+
async def startup_event():
|
| 28 |
+
global llm
|
| 29 |
+
if not os.path.exists(MODEL_FILE):
|
| 30 |
+
print(f"Downloading {MODEL_FILE}...")
|
| 31 |
+
hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=".")
|
| 32 |
+
print("Download done!")
|
| 33 |
+
print("Loading model...")
|
| 34 |
+
llm = Llama(
|
| 35 |
+
model_path=MODEL_FILE,
|
| 36 |
+
n_ctx=2048,
|
| 37 |
+
n_threads=2,
|
| 38 |
+
n_gpu_layers=0,
|
| 39 |
+
verbose=False,
|
| 40 |
+
use_mmap=True,
|
| 41 |
+
use_mlock=False,
|
| 42 |
+
)
|
| 43 |
+
print("Model ready!")
|
| 44 |
+
|
| 45 |
+
class Message(BaseModel):
|
| 46 |
+
role: str
|
| 47 |
+
content: str
|
| 48 |
+
|
| 49 |
+
class ChatRequest(BaseModel):
|
| 50 |
+
prompt: str
|
| 51 |
+
history: List[Message] = []
|
| 52 |
+
system_prompt: Optional[str] = "Bạn là một trợ lý AI thông minh, thân thiện. Trả lời ngắn gọn, chính xác, dễ hiểu bằng tiếng Việt. Khi viết code hãy giải thích rõ ràng. Không bịa đặt thông tin."
|
| 53 |
+
max_tokens: int = 1024
|
| 54 |
+
temperature: float = 0.7
|
| 55 |
+
top_p: float = 0.9
|
| 56 |
+
|
| 57 |
+
@app.post("/chat")
|
| 58 |
+
async def chat(req: ChatRequest):
|
| 59 |
+
if llm is None:
|
| 60 |
+
raise HTTPException(status_code=503, detail="Model chưa sẵn sàng, thử lại sau!")
|
| 61 |
+
|
| 62 |
+
if not req.prompt or not req.prompt.strip():
|
| 63 |
+
raise HTTPException(status_code=400, detail="Prompt trống")
|
| 64 |
+
|
| 65 |
+
if len(req.prompt) > 4000:
|
| 66 |
+
raise HTTPException(status_code=400, detail="Prompt quá dài")
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
messages = [{"role": "system", "content": req.system_prompt}]
|
| 70 |
+
|
| 71 |
+
for msg in req.history[-10:]:
|
| 72 |
+
if msg.role in ("user", "assistant") and msg.content.strip():
|
| 73 |
+
if messages[-1]["role"] == msg.role:
|
| 74 |
+
continue
|
| 75 |
+
messages.append({"role": msg.role, "content": msg.content})
|
| 76 |
+
|
| 77 |
+
if len(messages) > 1 and messages[-1]["role"] == "user":
|
| 78 |
+
messages.pop()
|
| 79 |
+
|
| 80 |
+
messages.append({"role": "user", "content": req.prompt.strip()})
|
| 81 |
+
|
| 82 |
+
print(f">> Prompt: {req.prompt[:80]}")
|
| 83 |
+
print(f">> Messages count: {len(messages)}")
|
| 84 |
+
|
| 85 |
+
def generate():
|
| 86 |
+
full_response = ""
|
| 87 |
+
try:
|
| 88 |
+
for chunk in llm.create_chat_completion(
|
| 89 |
+
messages=messages,
|
| 90 |
+
max_tokens=req.max_tokens,
|
| 91 |
+
temperature=req.temperature,
|
| 92 |
+
top_p=req.top_p,
|
| 93 |
+
stream=True,
|
| 94 |
+
):
|
| 95 |
+
delta = chunk["choices"][0]["delta"].get("content", "")
|
| 96 |
+
if delta:
|
| 97 |
+
full_response += delta
|
| 98 |
+
yield f"data: {json.dumps({'delta': delta})}\n\n"
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f">> Stream error: {e}")
|
| 101 |
+
yield f"data: {json.dumps({'delta': f'[Lỗi: {str(e)}]'})}\n\n"
|
| 102 |
+
finally:
|
| 103 |
+
print(f">> Response: {full_response[:80]}")
|
| 104 |
+
yield "data: [DONE]\n\n"
|
| 105 |
+
|
| 106 |
+
return StreamingResponse(
|
| 107 |
+
generate(),
|
| 108 |
+
media_type="text/event-stream",
|
| 109 |
+
headers={
|
| 110 |
+
"Cache-Control": "no-cache",
|
| 111 |
+
"X-Accel-Buffering": "no",
|
| 112 |
+
}
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
print(f">> Error: {e}")
|
| 117 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 118 |
+
|
| 119 |
+
@app.get("/")
|
| 120 |
+
async def root():
|
| 121 |
+
return {
|
| 122 |
+
"status": "ok" if llm else "loading",
|
| 123 |
+
"message": "Model ready!" if llm else "Model đang tải...",
|
| 124 |
+
"model": MODEL_FILE
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
@app.get("/health")
|
| 128 |
+
async def health():
|
| 129 |
+
return {"status": "healthy", "model_loaded": llm is not None}
|
| 130 |
+
|
| 131 |
+
if __name__ == "__main__":
|
| 132 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|