Abhay557 commited on
Commit
462230f
Β·
verified Β·
1 Parent(s): 42554d8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -155
app.py CHANGED
@@ -1,155 +1,149 @@
1
- """
2
- Code Collab AI Backend β€” Fast code generation API
3
- Uses Qwen 2.5 Coder 0.5B GGUF for low-latency HTML/CSS/JS generation.
4
- """
5
-
6
- import os
7
- import re
8
- import time
9
- from contextlib import asynccontextmanager
10
-
11
- from fastapi import FastAPI, HTTPException
12
- from fastapi.middleware.cors import CORSMiddleware
13
- from pydantic import BaseModel
14
- from huggingface_hub import hf_hub_download
15
- from llama_cpp import Llama
16
-
17
- # ─── Config ────────────────────────────────────────────────────────────
18
- MODEL_REPO = "Qwen/Qwen2.5-Coder-0.5B-Instruct-GGUF"
19
- MODEL_FILE = "qwen2.5-coder-0.5b-instruct-q4_k_m.gguf"
20
- N_CTX = 1536 # Smaller context = faster (free tier)
21
- N_THREADS = 2 # HF free tier has 2 vCPU
22
- MAX_TOKENS = 512 # Keep output short for speed
23
- TEMPERATURE = 0.5 # Lower = faster + more deterministic
24
-
25
- # ─── System prompt (optimized for structured output) ───────────────────
26
- SYSTEM_PROMPT = """You are a web code generator. Given a user request, output ONLY three fenced code blocks:
27
-
28
- ```html
29
- (body content only, no html/head/body tags)
30
- ```
31
-
32
- ```css
33
- (complete styles)
34
- ```
35
-
36
- ```js
37
- (complete JavaScript)
38
- ```
39
-
40
- Rules:
41
- - No explanations, no markdown text outside code blocks
42
- - If a section is not needed, output an empty code block for it
43
- - Write clean, modern code"""
44
-
45
- # ─── Global model reference ───────────────────────────────────────────
46
- llm = None
47
-
48
-
49
- @asynccontextmanager
50
- async def lifespan(app: FastAPI):
51
- """Load model once at startup, keep in memory for fast inference."""
52
- global llm
53
- print("⬇️ Downloading model...")
54
- model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
55
- print(f"βœ… Model downloaded: {model_path}")
56
-
57
- print("πŸ”„ Loading model into memory...")
58
- llm = Llama(
59
- model_path=model_path,
60
- n_ctx=N_CTX,
61
- n_threads=N_THREADS,
62
- n_gpu_layers=0, # CPU only (free HF Spaces)
63
- verbose=False,
64
- )
65
- print("πŸš€ Model loaded and ready!")
66
- yield
67
- llm = None
68
-
69
-
70
- # ─── FastAPI App ───────────────────────────────────────────────────────
71
- app = FastAPI(title="Code Collab AI", lifespan=lifespan)
72
-
73
- app.add_middleware(
74
- CORSMiddleware,
75
- allow_origins=["*"],
76
- allow_methods=["*"],
77
- allow_headers=["*"],
78
- )
79
-
80
-
81
- class GenerateRequest(BaseModel):
82
- prompt: str
83
- max_tokens: int = MAX_TOKENS
84
- temperature: float = TEMPERATURE
85
-
86
-
87
- class GenerateResponse(BaseModel):
88
- html: str
89
- css: str
90
- js: str
91
- raw: str
92
- time_ms: int
93
-
94
-
95
- def parse_code_blocks(text: str) -> dict:
96
- """Extract HTML, CSS, JS from fenced code blocks."""
97
- result = {"html": "", "css": "", "js": ""}
98
-
99
- for match in re.finditer(r"```(\w+)\s*\n([\s\S]*?)```", text):
100
- lang = match.group(1).lower()
101
- code = match.group(2).strip()
102
-
103
- if lang in ("html", "xml"):
104
- result["html"] = code
105
- elif lang == "css":
106
- result["css"] = code
107
- elif lang in ("js", "javascript"):
108
- result["js"] = code
109
-
110
- # Fallback: if no blocks found, treat as HTML
111
- if not any(result.values()):
112
- result["html"] = text.strip()
113
-
114
- return result
115
-
116
-
117
- # ─── API Endpoints ─────────────────────────────────────────────────────
118
-
119
- @app.get("/")
120
- def health():
121
- return {"status": "ok", "model": MODEL_REPO}
122
-
123
-
124
- @app.post("/generate", response_model=GenerateResponse)
125
- def generate(req: GenerateRequest):
126
- if llm is None:
127
- raise HTTPException(503, "Model not loaded yet")
128
-
129
- if not req.prompt.strip():
130
- raise HTTPException(400, "Prompt cannot be empty")
131
-
132
- start = time.time()
133
-
134
- output = llm.create_chat_completion(
135
- messages=[
136
- {"role": "system", "content": SYSTEM_PROMPT},
137
- {"role": "user", "content": req.prompt},
138
- ],
139
- max_tokens=req.max_tokens,
140
- temperature=req.temperature,
141
- stop=["```\n\n", "---"], # Stop early if model rambles
142
- )
143
-
144
- raw_text = output["choices"][0]["message"]["content"]
145
- elapsed_ms = int((time.time() - start) * 1000)
146
-
147
- parsed = parse_code_blocks(raw_text)
148
-
149
- return GenerateResponse(
150
- html=parsed["html"],
151
- css=parsed["css"],
152
- js=parsed["js"],
153
- raw=raw_text,
154
- time_ms=elapsed_ms,
155
- )
 
1
+ """
2
+ Code Collab AI Backend β€” Fast code generation API
3
+ Uses Qwen 2.5 Coder 0.5B GGUF for low-latency HTML/CSS/JS generation.
4
+ Model is loaded once at startup and stays in memory forever.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import time
10
+ from contextlib import asynccontextmanager
11
+
12
+ from fastapi import FastAPI, HTTPException
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from pydantic import BaseModel
15
+ from llama_cpp import Llama
16
+
17
+ # ─── Config ────────────────────────────────────────────────────────────
18
+ MODEL_PATH = os.path.join(os.path.dirname(__file__), "qwen2.5-coder-0.5b-instruct-q4_k_m.gguf")
19
+ N_CTX = 1536 # Smaller context = faster (free tier)
20
+ N_THREADS = 2 # HF free tier has 2 vCPU
21
+ MAX_TOKENS = 512 # Keep output short for speed
22
+ TEMPERATURE = 0.5 # Lower = faster + more deterministic
23
+
24
+ # ─── System prompt (optimized for structured output) ───────────────────
25
+ SYSTEM_PROMPT = """You are a web code generator. Given a user request, output ONLY three fenced code blocks:
26
+
27
+ ```html
28
+ (body content only, no html/head/body tags)
29
+ ```
30
+
31
+ ```css
32
+ (complete styles)
33
+ ```
34
+
35
+ ```js
36
+ (complete JavaScript)
37
+ ```
38
+
39
+ Rules:
40
+ - No explanations, no markdown text outside code blocks
41
+ - If a section is not needed, output an empty code block for it
42
+ - Write clean, modern code"""
43
+
44
+ # ─── Global model reference (loaded once, stays forever) ──────────────
45
+ llm = None
46
+
47
+
48
+ @asynccontextmanager
49
+ async def lifespan(app: FastAPI):
50
+ """Load model once at startup. It stays in memory for the entire lifetime."""
51
+ global llm
52
+ print(f"πŸ”„ Loading model from {MODEL_PATH}...")
53
+ llm = Llama(
54
+ model_path=MODEL_PATH,
55
+ n_ctx=N_CTX,
56
+ n_threads=N_THREADS,
57
+ n_gpu_layers=0, # CPU only (free HF Spaces)
58
+ verbose=False,
59
+ )
60
+ print("πŸš€ Model loaded and ready! It will stay in memory forever.")
61
+ yield
62
+ # Model stays loaded β€” never unloaded
63
+
64
+
65
+ # ─── FastAPI App ───────────────────────────────────────────────────────
66
+ app = FastAPI(title="Code Collab AI", lifespan=lifespan)
67
+
68
+ app.add_middleware(
69
+ CORSMiddleware,
70
+ allow_origins=["*"],
71
+ allow_methods=["*"],
72
+ allow_headers=["*"],
73
+ )
74
+
75
+
76
+ class GenerateRequest(BaseModel):
77
+ prompt: str
78
+ max_tokens: int = MAX_TOKENS
79
+ temperature: float = TEMPERATURE
80
+
81
+
82
+ class GenerateResponse(BaseModel):
83
+ html: str
84
+ css: str
85
+ js: str
86
+ raw: str
87
+ time_ms: int
88
+
89
+
90
+ def parse_code_blocks(text: str) -> dict:
91
+ """Extract HTML, CSS, JS from fenced code blocks."""
92
+ result = {"html": "", "css": "", "js": ""}
93
+
94
+ for match in re.finditer(r"```(\w+)\s*\n([\s\S]*?)```", text):
95
+ lang = match.group(1).lower()
96
+ code = match.group(2).strip()
97
+
98
+ if lang in ("html", "xml"):
99
+ result["html"] = code
100
+ elif lang == "css":
101
+ result["css"] = code
102
+ elif lang in ("js", "javascript"):
103
+ result["js"] = code
104
+
105
+ # Fallback: if no blocks found, treat as HTML
106
+ if not any(result.values()):
107
+ result["html"] = text.strip()
108
+
109
+ return result
110
+
111
+
112
+ # ─── API Endpoints ─────────────────────────────────────────────────────
113
+
114
+ @app.get("/")
115
+ def health():
116
+ return {"status": "ok", "model": "Qwen2.5-Coder-0.5B-Instruct-GGUF", "loaded": llm is not None}
117
+
118
+
119
+ @app.post("/generate", response_model=GenerateResponse)
120
+ def generate(req: GenerateRequest):
121
+ if llm is None:
122
+ raise HTTPException(503, "Model not loaded yet")
123
+
124
+ if not req.prompt.strip():
125
+ raise HTTPException(400, "Prompt cannot be empty")
126
+
127
+ start = time.time()
128
+
129
+ output = llm.create_chat_completion(
130
+ messages=[
131
+ {"role": "system", "content": SYSTEM_PROMPT},
132
+ {"role": "user", "content": req.prompt},
133
+ ],
134
+ max_tokens=req.max_tokens,
135
+ temperature=req.temperature,
136
+ )
137
+
138
+ raw_text = output["choices"][0]["message"]["content"]
139
+ elapsed_ms = int((time.time() - start) * 1000)
140
+
141
+ parsed = parse_code_blocks(raw_text)
142
+
143
+ return GenerateResponse(
144
+ html=parsed["html"],
145
+ css=parsed["css"],
146
+ js=parsed["js"],
147
+ raw=raw_text,
148
+ time_ms=elapsed_ms,
149
+ )