viskav commited on
Commit
73f597e
·
verified ·
1 Parent(s): 866b047

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -122
app.py CHANGED
@@ -1,146 +1,187 @@
 
 
 
 
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from pydantic import BaseModel
4
  from llama_cpp import Llama
5
- import re
6
 
7
- # GGUF MODEL
8
- MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
9
- MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
10
-
11
- print("Loading Phi-3.1 Mini GGUF model...")
12
- llm = Llama.from_pretrained(
13
- repo_id=MODEL_REPO,
14
- filename=MODEL_FILE,
15
- n_threads=4, # Adjust based on CPU cores
16
- n_ctx=4096, # Increased to model's full capacity
17
- n_batch=512, # Increased batch size for better performance
18
- n_gpu_layers=0, # Set to >0 if you have GPU
 
 
 
 
 
 
 
 
 
 
 
 
19
  verbose=False,
20
  )
21
  print("Model loaded successfully.")
22
 
23
- app = FastAPI()
 
 
24
 
25
  app.add_middleware(
26
  CORSMiddleware,
27
- allow_origins=["*"],
28
  allow_methods=["*"],
29
  allow_headers=["*"],
30
  )
31
 
32
- class RequestBody(BaseModel):
 
 
33
  text: str
 
34
 
35
- def clean_output(text: str) -> str:
36
- """Clean the model output by removing extra tags and whitespace."""
37
- # Remove common ending tokens and system tags
38
- clean_text = re.sub(r'\[/?(SYSTEM|USER|ASSISTANT)\]', '', text)
39
- clean_text = re.sub(r'</?s>', '', clean_text)
40
- clean_text = re.sub(r'\s+', ' ', clean_text) # Normalize whitespace
41
- clean_text = clean_text.strip()
42
-
43
- # Remove the original text if it's repeated
44
- lines = clean_text.split('\n')
45
- if len(lines) > 1:
46
- # Take the most human-like line (usually the last one)
47
- clean_text = lines[-1].strip()
48
-
49
- return clean_text
50
 
51
- @app.post("/api/humanize")
52
- async def humanize(body: RequestBody):
53
- text = body.text.strip()
54
- if not text:
55
- raise HTTPException(status_code=400, detail="Text cannot be empty")
56
-
57
- # More specific system prompt for humanization
58
- system_prompt = """You are an expert text humanizer. Your task is to rewrite text to sound more natural and human-like while preserving the original meaning. Follow these rules:
59
- 1. Make the text sound like a real person wrote it
60
- 2. Use casual, conversational language when appropriate
61
- 3. Fix robotic or overly formal phrasing
62
- 4. Keep technical terms when necessary but explain them naturally
63
- 5. Maintain the original intent and tone
64
- 6. Output ONLY the humanized version, no explanations
65
-
66
- Examples:
67
- Input: "What is the process for obtaining a permit?"
68
- Output: "How do I get a permit?"
69
-
70
- Input: "The application must be submitted by the deadline."
71
- Output: "You'll need to submit your application before the deadline."
72
-
73
- Input: "Can you provide the requested information?"
74
- Output: "Could you share that information with me?"
75
-
76
- Now humanize this text:"""
77
-
78
- # Create the prompt with proper Phi-3.1 formatting
79
- user_prompt = f"{text}"
80
-
81
- # For Phi-3.1, use this format:
82
- full_prompt = f"<|user|>\n{system_prompt}\n\n{user_prompt}<|end|>\n<|assistant|>\n"
83
-
84
- try:
85
- # Generate with more tokens and slightly higher temperature for creativity
86
- output = llm(
87
- full_prompt,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  max_tokens=512,
89
- temperature=0.7, # Higher for more creativity
90
- top_p=0.9, # Slightly less than 1.0 for diversity
91
- repeat_penalty=1.1, # Penalize repetition
92
- top_k=40, # Limit token choices
93
- echo=False, # Don't echo the prompt in output
94
- stop=["<|end|>", "<|user|>", "<|assistant|>", "\n\n", "###"],
95
  )
96
-
97
- result = output["choices"][0]["text"].strip()
98
- cleaned_result = clean_output(result)
99
-
100
- # If the output is empty or too similar to input, use a fallback
101
- if not cleaned_result or cleaned_result == text:
102
- # Simple fallback humanization rules
103
- fallback_rules = [
104
- ("what is", "what's"),
105
- ("could you", "can you"),
106
- ("would you", "could you"),
107
- ("please be advised that", ""),
108
- ("it is recommended that", "you should"),
109
- ("utilize", "use"),
110
- ("commence", "start"),
111
- ("terminate", "end"),
112
- ("approximately", "about"),
113
- ("therefore", "so"),
114
- ("however", "but"),
115
- ("in order to", "to"),
116
- ("with regard to", "about"),
117
- ("at this point in time", "now"),
118
- ("due to the fact that", "because"),
119
- ("prior to", "before"),
120
- ("subsequent to", "after"),
121
- ]
122
-
123
- cleaned_result = text
124
- for formal, casual in fallback_rules:
125
- if formal in cleaned_result.lower():
126
- # Replace while preserving case
127
- pattern = re.compile(re.escape(formal), re.IGNORECASE)
128
- cleaned_result = pattern.sub(casual, cleaned_result)
129
-
130
- # Add contractions
131
- cleaned_result = re.sub(r'\b(I am|you are|he is|she is|it is|we are|they are)\b',
132
- lambda m: m.group(1).replace(' ', "'"), cleaned_result, flags=re.IGNORECASE)
133
- cleaned_result = re.sub(r'\b(do not|does not|did not|cannot|will not|would not|could not|should not|is not|are not|was not|were not)\b',
134
- lambda m: m.group(1).replace(' ', "'").replace("cannot", "can't"), cleaned_result, flags=re.IGNORECASE)
135
-
136
- return {"result": cleaned_result}
137
-
138
- except Exception as e:
139
- print(f"Model error: {e}")
140
- # Return a simple humanized version as fallback
141
- simple_humanized = text.replace("?", "?").replace(".", ".")
142
- return {"result": simple_humanized}
143
 
144
  @app.get("/")
145
  def health():
146
- return {"status": "ok", "model": MODEL_FILE}
 
1
+ # app.py
2
+ import asyncio
3
+ import re
4
+ from typing import Literal
5
  from fastapi import FastAPI, HTTPException
6
  from fastapi.middleware.cors import CORSMiddleware
7
  from pydantic import BaseModel
8
  from llama_cpp import Llama
 
9
 
10
+ # ---------------- MODEL CONFIG ---------------- #
11
+
12
+ # IMPORTANT: For HuggingFace Spaces, the model file is inside the repo folder
13
+ MODEL_PATH = "model/Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
14
+
15
+ # CPU settings for llama.cpp
16
+ N_THREADS = 4
17
+ N_CTX = 4096
18
+ N_BATCH = 512
19
+ N_GPU_LAYERS = 0
20
+
21
+ # Concurrency limit
22
+ MAX_CONCURRENT_REQUESTS = 6
23
+
24
+ # Unique token to force controlled stopping
25
+ END_TOKEN = "###END_OF_RESPONSE###"
26
+
27
+ print("Loading model:", MODEL_PATH)
28
+ llm = Llama(
29
+ model_path=MODEL_PATH,
30
+ n_threads=N_THREADS,
31
+ n_ctx=N_CTX,
32
+ n_batch=N_BATCH,
33
+ n_gpu_layers=N_GPU_LAYERS,
34
  verbose=False,
35
  )
36
  print("Model loaded successfully.")
37
 
38
+ # ---------------- FASTAPI APP ---------------- #
39
+
40
+ app = FastAPI(title="FormatAI Humanizer Backend")
41
 
42
  app.add_middleware(
43
  CORSMiddleware,
44
+ allow_origins=["*"], # allow all origins (Vercel frontend)
45
  allow_methods=["*"],
46
  allow_headers=["*"],
47
  )
48
 
49
+ # ---------------- REQUEST MODELS ---------------- #
50
+
51
+ class TransformRequest(BaseModel):
52
  text: str
53
+ style: Literal["professional", "casual", "academic", "marketing"]
54
 
55
+ class HumanizeRequest(BaseModel): # legacy
56
+ text: str
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+
59
+ # ---------------- STYLE PROMPTS ---------------- #
60
+
61
+ STYLE_PROMPTS = {
62
+ "professional": (
63
+ "STYLE: PROFESSIONAL\n"
64
+ "Rewrite the user's text in a STRICTLY professional, corporate, formal tone. "
65
+ "Use respectful and clear business language. Do NOT add explanations. Output ONLY the rewritten text, "
66
+ f"then write {END_TOKEN}."
67
+ ),
68
+
69
+ "casual": (
70
+ "STYLE: CASUAL\n"
71
+ "Rewrite the user's text in a friendly, natural, conversational tone. Use contractions and human-like flow. "
72
+ "Do NOT add explanations. Output ONLY the rewritten text, then write "
73
+ f"{END_TOKEN}."
74
+ ),
75
+
76
+ "academic": (
77
+ "STYLE: ACADEMIC\n"
78
+ "Rewrite the user's text in formal academic language suitable for scholarly work. "
79
+ "Use precise and objective vocabulary. Do NOT add explanations. Output ONLY the rewritten text, "
80
+ f"then write {END_TOKEN}."
81
+ ),
82
+
83
+ "marketing": (
84
+ "STYLE: MARKETING\n"
85
+ "Rewrite the user's text in persuasive, benefit-focused marketing copy. "
86
+ "Use strong emotional hooks and punchy messaging. Do NOT add explanations. Output ONLY the rewritten text, "
87
+ f"then write {END_TOKEN}."
88
+ ),
89
+ }
90
+
91
+
92
+ # ---------------- HELPERS ---------------- #
93
+
94
+ def clean_output(raw: str) -> str:
95
+ """Strip junk tokens and trim to final output."""
96
+ if not raw:
97
+ return ""
98
+
99
+ # Remove system markers
100
+ raw = re.sub(r"<\|/?(system|assistant|user|end)\|>", "", raw, flags=re.I)
101
+
102
+ # Stop at END_TOKEN
103
+ if END_TOKEN in raw:
104
+ raw = raw.split(END_TOKEN)[0]
105
+
106
+ raw = raw.strip()
107
+ raw = re.sub(r"[ \t]+", " ", raw)
108
+ return raw.strip()
109
+
110
+
111
+ def build_prompt(text: str, style: str) -> str:
112
+ """Create strict prompt for selected style."""
113
+ system = STYLE_PROMPTS[style]
114
+
115
+ return (
116
+ f"<|system|>\n{system}\n\n"
117
+ f"<|user|>\n{text}\n\n"
118
+ f"<|assistant|>\n"
119
+ )
120
+
121
+
122
+ # ---------------- MODEL CALL ---------------- #
123
+
124
+ async def call_llm(prompt: str, temperature: float = 0.25):
125
+ loop = asyncio.get_event_loop()
126
+
127
+ def sync_call():
128
+ return llm(
129
+ prompt,
130
  max_tokens=512,
131
+ temperature=temperature,
132
+ top_p=0.9,
133
+ top_k=40,
134
+ repeat_penalty=1.1,
135
+ stop=[END_TOKEN],
136
+ echo=False,
137
  )
138
+
139
+ out = await loop.run_in_executor(None, sync_call)
140
+
141
+ if "choices" in out:
142
+ text = out["choices"][0].get("text", "")
143
+ else:
144
+ text = str(out)
145
+
146
+ return clean_output(text)
147
+
148
+
149
+ # ---------------- ENDPOINT: /api/transform ---------------- #
150
+
151
+ @app.post("/api/transform")
152
+ async def transform(req: TransformRequest):
153
+ text = req.text.strip()
154
+ if not text:
155
+ raise HTTPException(400, "Text cannot be empty")
156
+
157
+ if req.style not in STYLE_PROMPTS:
158
+ raise HTTPException(400, "Invalid style")
159
+
160
+ # More creativity for marketing
161
+ temperature = 0.65 if req.style == "marketing" else 0.25
162
+
163
+ prompt = build_prompt(text, req.style)
164
+ transformed = await call_llm(prompt, temperature=temperature)
165
+
166
+ return {
167
+ "original": text,
168
+ "transformed": transformed,
169
+ "style": req.style
170
+ }
171
+
172
+
173
+ # ---------------- LEGACY ENDPOINT: /api/humanize ---------------- #
174
+
175
+ @app.post("/api/humanize")
176
+ async def humanize(req: HumanizeRequest):
177
+ """Old endpoint - always uses casual."""
178
+ prompt = build_prompt(req.text.strip(), "casual")
179
+ out = await call_llm(prompt, temperature=0.4)
180
+ return {"result": out}
181
+
182
+
183
+ # ---------------- HEALTH CHECK ---------------- #
 
184
 
185
  @app.get("/")
186
  def health():
187
+ return {"status": "ok", "model": MODEL_PATH}