viskav commited on
Commit
cb930f8
·
verified ·
1 Parent(s): 94ebfe7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -113
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import os
2
  import re
3
- import asyncio
4
  import time
5
  from typing import Literal, Optional
6
  from fastapi import FastAPI, HTTPException
@@ -10,14 +9,12 @@ from llama_cpp import Llama
10
  from contextlib import asynccontextmanager
11
 
12
  # ==================== CONFIGURATION ====================
13
- # Hugging Face Spaces optimized settings
14
  MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
15
  MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
16
- MODEL_PATH = os.environ.get("MODEL_PATH", MODEL_FILE)
17
 
18
- # CPU settings optimized for Spaces (2 CPU cores typical)
19
  N_THREADS = int(os.environ.get("N_THREADS", "2"))
20
- N_CTX = int(os.environ.get("N_CTX", "2048")) # Reduced for faster inference
21
  N_BATCH = int(os.environ.get("N_BATCH", "128"))
22
  N_GPU_LAYERS = int(os.environ.get("N_GPU_LAYERS", "0"))
23
 
@@ -26,20 +23,34 @@ END_TOKEN = "<|endoftext|>"
26
 
27
  # ==================== GLOBAL MODEL ====================
28
  llm = None
 
29
 
30
  # ==================== LIFECYCLE MANAGEMENT ====================
31
  @asynccontextmanager
32
  async def lifespan(app: FastAPI):
33
- # Startup
34
- print("🚀 Starting FormatAI Humanizer Backend on Hugging Face Space")
35
- print(f"📊 Configuration:")
36
- print(f" Model: {MODEL_PATH}")
37
- print(f" Threads: {N_THREADS}")
38
- print(f" Context: {N_CTX}")
39
 
40
- # Load model on startup
41
- global llm
42
- llm = load_model()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  yield
45
 
@@ -52,16 +63,14 @@ async def lifespan(app: FastAPI):
52
  app = FastAPI(
53
  title="FormatAI Humanizer API",
54
  description="Backend API for text transformation with Phi-3.1 Mini",
55
- version="1.0.0",
56
  lifespan=lifespan
57
  )
58
 
59
  # CORS - Allow your Vercel frontend
60
  app.add_middleware(
61
  CORSMiddleware,
62
- allow_origins=[
63
- "*", # Allow all origins temporarily, update with your Vercel URL
64
- ],
65
  allow_credentials=True,
66
  allow_methods=["*"],
67
  allow_headers=["*"],
@@ -77,7 +86,8 @@ class HumanizeRequest(BaseModel):
77
 
78
  # ==================== STYLE PROMPTS ====================
79
  STYLE_PROMPTS = {
80
- "professional": """You are a professional writing assistant. Rewrite the text below in formal, corporate business language.
 
81
 
82
  IMPORTANT RULES:
83
  1. Output ONLY the rewritten text
@@ -85,12 +95,15 @@ IMPORTANT RULES:
85
  3. Keep the same meaning
86
  4. Use formal vocabulary
87
  5. Proper grammar and structure
88
-
 
89
  Text to rewrite: {text}
90
 
91
- Rewritten (professional):""",
 
92
 
93
- "casual": """You are a casual writing assistant. Rewrite the text below in friendly, natural, conversational English.
 
94
 
95
  IMPORTANT RULES:
96
  1. Output ONLY the rewritten text
@@ -98,12 +111,15 @@ IMPORTANT RULES:
98
  3. Keep the same meaning
99
  4. Use contractions (I'm, don't, etc.)
100
  5. Sound like a real person speaking
101
-
 
102
  Text to rewrite: {text}
103
 
104
- Rewritten (casual):""",
 
105
 
106
- "academic": """You are an academic writing assistant. Rewrite the text below in formal scholarly language.
 
107
 
108
  IMPORTANT RULES:
109
  1. Output ONLY the rewritten text
@@ -111,12 +127,15 @@ IMPORTANT RULES:
111
  3. Keep the same meaning
112
  4. Use precise academic vocabulary
113
  5. Maintain formal structure
114
-
 
115
  Text to rewrite: {text}
116
 
117
- Rewritten (academic):""",
 
118
 
119
- "marketing": """You are a marketing copywriter. Rewrite the text below into persuasive marketing language.
 
120
 
121
  IMPORTANT RULES:
122
  1. Output ONLY the rewritten text
@@ -124,10 +143,12 @@ IMPORTANT RULES:
124
  3. Keep the same meaning
125
  4. Use emotional hooks and benefits
126
  5. Make it engaging and compelling
127
-
 
128
  Text to rewrite: {text}
129
 
130
- Rewritten (marketing):"""
 
131
  }
132
 
133
  STYLE_TEMPERATURES = {
@@ -138,68 +159,17 @@ STYLE_TEMPERATURES = {
138
  }
139
 
140
  # ==================== HELPER FUNCTIONS ====================
141
- def load_model():
142
- """Load the GGUF model"""
143
- global MODEL_PATH # Access the global MODEL_PATH
144
-
145
- print(f"🔄 Loading model from: {MODEL_PATH}")
146
-
147
- try:
148
- # Check if model exists locally
149
- if not os.path.exists(MODEL_PATH):
150
- print("📥 Downloading model from Hugging Face Hub...")
151
- try:
152
- from huggingface_hub import hf_hub_download
153
- # Use a different variable name to avoid conflict
154
- downloaded_path = hf_hub_download(
155
- repo_id=MODEL_REPO,
156
- filename=MODEL_FILE,
157
- local_dir=".",
158
- token=os.environ.get("HF_TOKEN", None)
159
- )
160
- print(f"✅ Model downloaded to: {downloaded_path}")
161
- except ImportError:
162
- print("⚠️ huggingface-hub not installed, using local model path")
163
- # If we can't download, use fallback path
164
- MODEL_PATH = os.path.join("/code", MODEL_FILE)
165
-
166
- print(f"📁 Model path: {MODEL_PATH}")
167
-
168
- model = Llama(
169
- model_path=MODEL_PATH,
170
- n_threads=N_THREADS,
171
- n_ctx=N_CTX,
172
- n_batch=N_BATCH,
173
- n_gpu_layers=N_GPU_LAYERS,
174
- verbose=False,
175
- use_mlock=False, # Important for Spaces
176
- )
177
-
178
- print(f"✅ Model loaded successfully!")
179
- return model
180
-
181
- except Exception as e:
182
- print(f"❌ Failed to load model: {e}")
183
- import traceback
184
- traceback.print_exc()
185
- return None
186
-
187
  def clean_output(text: str) -> str:
188
  """Clean model output"""
189
  if not text:
190
  return ""
191
 
192
- # Remove common artifacts
193
- clean = re.sub(r'Rewritten\s*\([^)]+\):', '', text, flags=re.IGNORECASE)
194
- clean = re.sub(r'IMPORTANT RULES:.*?(?=\n\n|\Z)', '', clean, flags=re.DOTALL)
195
- clean = re.sub(r'You are [^\.]+\.', '', clean)
196
-
197
  # Remove Phi-3.1 special tokens
198
- clean = re.sub(r'<\|[^>]+\|>', '', clean)
199
- clean = re.sub(r'\[/?[^]]+\]', '', clean)
 
200
 
201
  # Clean whitespace
202
- clean = re.sub(r'\n+', ' ', clean)
203
  clean = re.sub(r'\s+', ' ', clean)
204
  clean = clean.strip()
205
 
@@ -211,24 +181,16 @@ def clean_output(text: str) -> str:
211
 
212
  def format_prompt(text: str, style: str) -> str:
213
  """Format prompt for Phi-3.1"""
214
- system_prompt = STYLE_PROMPTS[style].format(text=text)
215
-
216
- # Phi-3.1 chat format
217
- prompt = f"<|system|>\n{system_prompt}\n<|end|>\n"
218
- prompt += f"<|user|>\nPlease rewrite this text in {style} style:\n{text}\n<|end|>\n"
219
- prompt += "<|assistant|>\n"
220
-
221
- return prompt
222
 
223
  async def transform_with_model(text: str, style: str) -> str:
224
  """Transform text using the loaded model"""
225
- global llm
226
 
227
  if llm is None:
228
- # Try to load model if not loaded
229
- llm = load_model()
230
- if llm is None:
231
- raise HTTPException(status_code=503, detail="Model not available. Please check if model file exists.")
232
 
233
  try:
234
  # Build prompt
@@ -240,7 +202,7 @@ async def transform_with_model(text: str, style: str) -> str:
240
 
241
  output = llm(
242
  prompt,
243
- max_tokens=min(400, len(text) + 100), # Dynamic token limit
244
  temperature=temperature,
245
  top_p=0.9,
246
  repeat_penalty=1.1,
@@ -263,13 +225,11 @@ async def transform_with_model(text: str, style: str) -> str:
263
  if not cleaned or cleaned.isspace():
264
  cleaned = f"[{style.capitalize()} Version]: {text}"
265
 
266
- print(f"✅ Transformation completed in {processing_time:.2f}s")
267
  return cleaned
268
 
269
  except Exception as e:
270
  print(f"❌ Model error: {e}")
271
- import traceback
272
- traceback.print_exc()
273
  raise HTTPException(status_code=500, detail=f"Model error: {str(e)}")
274
 
275
  # ==================== API ENDPOINTS ====================
@@ -277,9 +237,11 @@ async def transform_with_model(text: str, style: str) -> str:
277
  async def root():
278
  """Health check endpoint"""
279
  return {
280
- "status": "online",
281
  "service": "FormatAI Humanizer",
282
- "model": "Phi-3.1-mini-4k-instruct-GGUF",
 
 
283
  "styles_available": list(STYLE_PROMPTS.keys()),
284
  "max_input_length": MAX_INPUT_LENGTH
285
  }
@@ -288,11 +250,11 @@ async def root():
288
  async def health_check():
289
  """Detailed health check"""
290
  return {
291
- "status": "healthy" if llm else "model_loading_failed",
292
  "model_loaded": llm is not None,
 
293
  "threads": N_THREADS,
294
- "context_size": N_CTX,
295
- "model_path": MODEL_PATH
296
  }
297
 
298
  @app.post("/api/humanize")
@@ -357,8 +319,6 @@ async def transform_text(request: TransformRequest):
357
  raise
358
  except Exception as e:
359
  print(f"❌ Transformation error: {e}")
360
- import traceback
361
- traceback.print_exc()
362
  raise HTTPException(status_code=500, detail=f"Transformation failed: {str(e)}")
363
 
364
  @app.get("/api/styles")
@@ -367,13 +327,12 @@ async def get_styles():
367
  styles_info = {}
368
  for style, prompt in STYLE_PROMPTS.items():
369
  # Extract first line for description
370
- first_line = prompt.split('\n')[0]
371
- description = first_line.replace("You are ", "").replace(".", "")
372
 
373
  styles_info[style] = {
374
- "description": description,
375
  "temperature": STYLE_TEMPERATURES[style],
376
- "example_prompt": prompt[:100] + "..." if len(prompt) > 100 else prompt
377
  }
378
 
379
  return {
@@ -391,5 +350,5 @@ if __name__ == "__main__":
391
  "app:app",
392
  host="0.0.0.0",
393
  port=port,
394
- reload=False # Disable reload for production
395
  )
 
1
  import os
2
  import re
 
3
  import time
4
  from typing import Literal, Optional
5
  from fastapi import FastAPI, HTTPException
 
9
  from contextlib import asynccontextmanager
10
 
11
  # ==================== CONFIGURATION ====================
 
12
  MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
13
  MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
 
14
 
15
+ # Hugging Face Spaces optimized settings
16
  N_THREADS = int(os.environ.get("N_THREADS", "2"))
17
+ N_CTX = int(os.environ.get("N_CTX", "2048")) # Reduced for faster loading
18
  N_BATCH = int(os.environ.get("N_BATCH", "128"))
19
  N_GPU_LAYERS = int(os.environ.get("N_GPU_LAYERS", "0"))
20
 
 
23
 
24
  # ==================== GLOBAL MODEL ====================
25
  llm = None
26
+ model_loading_error = None
27
 
28
  # ==================== LIFECYCLE MANAGEMENT ====================
29
  @asynccontextmanager
30
  async def lifespan(app: FastAPI):
31
+ # Startup - load model
32
+ global llm, model_loading_error
33
+ print("🚀 Starting FormatAI Humanizer Backend")
34
+ print(f"📊 Configuration: Threads={N_THREADS}, Context={N_CTX}")
 
 
35
 
36
+ try:
37
+ print(f"📥 Downloading model: {MODEL_REPO}/{MODEL_FILE}")
38
+ llm = Llama.from_pretrained(
39
+ repo_id=MODEL_REPO,
40
+ filename=MODEL_FILE,
41
+ n_threads=N_THREADS,
42
+ n_ctx=N_CTX,
43
+ n_batch=N_BATCH,
44
+ n_gpu_layers=N_GPU_LAYERS,
45
+ verbose=True, # Set to True to see loading progress
46
+ use_mlock=False, # Important for Spaces
47
+ )
48
+ print("✅ Model loaded successfully!")
49
+ model_loading_error = None
50
+ except Exception as e:
51
+ print(f"❌ Model loading failed: {e}")
52
+ model_loading_error = str(e)
53
+ llm = None
54
 
55
  yield
56
 
 
63
  app = FastAPI(
64
  title="FormatAI Humanizer API",
65
  description="Backend API for text transformation with Phi-3.1 Mini",
66
+ version="2.0.0",
67
  lifespan=lifespan
68
  )
69
 
70
  # CORS - Allow your Vercel frontend
71
  app.add_middleware(
72
  CORSMiddleware,
73
+ allow_origins=["*"],
 
 
74
  allow_credentials=True,
75
  allow_methods=["*"],
76
  allow_headers=["*"],
 
86
 
87
  # ==================== STYLE PROMPTS ====================
88
  STYLE_PROMPTS = {
89
+ "professional": """<|system|>
90
+ You are a professional writing assistant. Rewrite the text below in formal, corporate business language.
91
 
92
  IMPORTANT RULES:
93
  1. Output ONLY the rewritten text
 
95
  3. Keep the same meaning
96
  4. Use formal vocabulary
97
  5. Proper grammar and structure
98
+ <|end|>
99
+ <|user|>
100
  Text to rewrite: {text}
101
 
102
+ Rewritten (professional):<|end|>
103
+ <|assistant|>""",
104
 
105
+ "casual": """<|system|>
106
+ You are a casual writing assistant. Rewrite the text below in friendly, natural, conversational English.
107
 
108
  IMPORTANT RULES:
109
  1. Output ONLY the rewritten text
 
111
  3. Keep the same meaning
112
  4. Use contractions (I'm, don't, etc.)
113
  5. Sound like a real person speaking
114
+ <|end|>
115
+ <|user|>
116
  Text to rewrite: {text}
117
 
118
+ Rewritten (casual):<|end|>
119
+ <|assistant|>""",
120
 
121
+ "academic": """<|system|>
122
+ You are an academic writing assistant. Rewrite the text below in formal scholarly language.
123
 
124
  IMPORTANT RULES:
125
  1. Output ONLY the rewritten text
 
127
  3. Keep the same meaning
128
  4. Use precise academic vocabulary
129
  5. Maintain formal structure
130
+ <|end|>
131
+ <|user|>
132
  Text to rewrite: {text}
133
 
134
+ Rewritten (academic):<|end|>
135
+ <|assistant|>""",
136
 
137
+ "marketing": """<|system|>
138
+ You are a marketing copywriter. Rewrite the text below into persuasive marketing language.
139
 
140
  IMPORTANT RULES:
141
  1. Output ONLY the rewritten text
 
143
  3. Keep the same meaning
144
  4. Use emotional hooks and benefits
145
  5. Make it engaging and compelling
146
+ <|end|>
147
+ <|user|>
148
  Text to rewrite: {text}
149
 
150
+ Rewritten (marketing):<|end|>
151
+ <|assistant|>"""
152
  }
153
 
154
  STYLE_TEMPERATURES = {
 
159
  }
160
 
161
  # ==================== HELPER FUNCTIONS ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  def clean_output(text: str) -> str:
163
  """Clean model output"""
164
  if not text:
165
  return ""
166
 
 
 
 
 
 
167
  # Remove Phi-3.1 special tokens
168
+ clean = re.sub(r'<\|[^>]+\|>', '', text)
169
+ clean = re.sub(r'Rewritten\s*\([^)]+\):', '', clean, flags=re.IGNORECASE)
170
+ clean = re.sub(r'Text to rewrite:.*', '', clean, flags=re.DOTALL)
171
 
172
  # Clean whitespace
 
173
  clean = re.sub(r'\s+', ' ', clean)
174
  clean = clean.strip()
175
 
 
181
 
182
  def format_prompt(text: str, style: str) -> str:
183
  """Format prompt for Phi-3.1"""
184
+ return STYLE_PROMPTS[style].format(text=text)
 
 
 
 
 
 
 
185
 
186
  async def transform_with_model(text: str, style: str) -> str:
187
  """Transform text using the loaded model"""
188
+ global llm, model_loading_error
189
 
190
  if llm is None:
191
+ if model_loading_error:
192
+ raise HTTPException(status_code=503, detail=f"Model failed to load: {model_loading_error}")
193
+ raise HTTPException(status_code=503, detail="Model not available. Please wait for model to load.")
 
194
 
195
  try:
196
  # Build prompt
 
202
 
203
  output = llm(
204
  prompt,
205
+ max_tokens=min(400, len(text) + 100),
206
  temperature=temperature,
207
  top_p=0.9,
208
  repeat_penalty=1.1,
 
225
  if not cleaned or cleaned.isspace():
226
  cleaned = f"[{style.capitalize()} Version]: {text}"
227
 
228
+ print(f"✅ Transformation completed in {processing_time:.2f}s (Style: {style})")
229
  return cleaned
230
 
231
  except Exception as e:
232
  print(f"❌ Model error: {e}")
 
 
233
  raise HTTPException(status_code=500, detail=f"Model error: {str(e)}")
234
 
235
  # ==================== API ENDPOINTS ====================
 
237
  async def root():
238
  """Health check endpoint"""
239
  return {
240
+ "status": "online" if llm else "model_loading_failed",
241
  "service": "FormatAI Humanizer",
242
+ "model": MODEL_FILE,
243
+ "model_loaded": llm is not None,
244
+ "model_error": model_loading_error,
245
  "styles_available": list(STYLE_PROMPTS.keys()),
246
  "max_input_length": MAX_INPUT_LENGTH
247
  }
 
250
  async def health_check():
251
  """Detailed health check"""
252
  return {
253
+ "status": "healthy" if llm else "unhealthy",
254
  "model_loaded": llm is not None,
255
+ "model_error": model_loading_error,
256
  "threads": N_THREADS,
257
+ "context_size": N_CTX
 
258
  }
259
 
260
  @app.post("/api/humanize")
 
319
  raise
320
  except Exception as e:
321
  print(f"❌ Transformation error: {e}")
 
 
322
  raise HTTPException(status_code=500, detail=f"Transformation failed: {str(e)}")
323
 
324
  @app.get("/api/styles")
 
327
  styles_info = {}
328
  for style, prompt in STYLE_PROMPTS.items():
329
  # Extract first line for description
330
+ first_line = prompt.split('\n')[0].replace('<|system|>', '').strip()
 
331
 
332
  styles_info[style] = {
333
+ "description": first_line,
334
  "temperature": STYLE_TEMPERATURES[style],
335
+ "max_tokens": 400
336
  }
337
 
338
  return {
 
350
  "app:app",
351
  host="0.0.0.0",
352
  port=port,
353
+ reload=False
354
  )