viskav commited on
Commit
b1d2d1e
·
verified ·
1 Parent(s): 77513d0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -130
app.py CHANGED
@@ -1,141 +1,122 @@
 
1
  from fastapi import FastAPI, HTTPException
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel, Field
4
- from typing import Literal
5
  from llama_cpp import Llama
6
- import re
7
 
8
- # ==================== MODEL CONFIG ====================
 
 
9
  MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
10
  MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
11
 
12
- print("🚀 Loading Phi-3.1 Mini (Fast Humanizer)...")
13
- llm = Llama.from_pretrained(
14
- repo_id=MODEL_REPO,
15
- filename=MODEL_FILE,
16
- n_threads=4,
17
- n_ctx=2048, # Smaller context = faster
18
- n_batch=256,
19
- n_gpu_layers=0,
20
- verbose=False,
21
- )
22
- print("✅ Model loaded")
23
-
24
- # ==================== FASTAPI ====================
25
- app = FastAPI(title="Fast Humanizer")
26
-
27
- app.add_middleware(
28
- CORSMiddleware,
29
- allow_origins=["*"],
30
- allow_methods=["*"],
31
- allow_headers=["*"],
32
- )
33
-
34
- # ==================== REQUEST ====================
 
 
 
35
  class HumanizeRequest(BaseModel):
36
- text: str = Field(..., min_length=1, max_length=500)
37
- style: Literal["professional", "casual", "academic", "marketing"] = "professional"
38
-
39
- # ==================== STYLE PROMPTS (SHORT & EFFECTIVE) ====================
40
- STYLE_PROMPTS = {
41
- "professional":
42
- "Rewrite this text in a clear, polished, professional tone. "
43
- "Make it sound natural and confident. Output ONLY the rewritten text:\n",
44
-
45
- "casual":
46
- "Rewrite this text in a friendly, casual, human way. "
47
- "Use natural phrasing and contractions. Output ONLY the rewritten text:\n",
48
-
49
- "academic":
50
- "Rewrite this text in a formal academic style. "
51
- "Use clear structure and precise language. Output ONLY the rewritten text:\n",
52
-
53
- "marketing":
54
- "Rewrite this text as persuasive marketing copy. "
55
- "Make it engaging and benefit-focused. Output ONLY the rewritten text:\n",
56
  }
57
 
58
- # ==================== OUTPUT CLEANER ====================
59
- def clean_output(text: str) -> str:
60
- text = re.sub(r'<\|.*?\|>', '', text)
61
- text = re.sub(r'\s+', ' ', text)
62
- text = text.strip()
63
-
64
- lines = [l.strip() for l in text.split("\n") if len(l.strip()) > 10]
65
- return lines[-1] if lines else text
66
-
67
- # ==================== FALLBACK HUMANIZER ====================
68
- def fallback_humanize(text: str) -> str:
69
- replacements = [
70
- ("utilize", "use"),
71
- ("commence", "start"),
72
- ("approximately", "about"),
73
- ("therefore", "so"),
74
- ("however", "but"),
75
- ("in order to", "to"),
76
- ("due to the fact that", "because"),
77
- ("prior to", "before"),
78
- ("subsequent to", "after"),
79
- ]
80
-
81
- result = text
82
- for formal, casual in replacements:
83
- result = re.sub(formal, casual, result, flags=re.IGNORECASE)
84
-
85
- result = re.sub(r"\b(do not|does not|did not|cannot|will not|is not|are not)\b",
86
- lambda m: m.group(1).replace(" ", "'").replace("cannot", "can't"),
87
- result,
88
- flags=re.IGNORECASE)
89
- return result
90
-
91
- # ==================== ENDPOINT ====================
92
- @app.post("/api/humanize")
93
- async def humanize(req: HumanizeRequest):
94
- text = req.text.strip()
95
- style = req.style
96
-
97
- prompt = (
98
- f"<|user|>\n"
99
- f"{STYLE_PROMPTS[style]}"
100
- f"{text}\n"
101
- f"<|end|>\n"
102
- f"<|assistant|>\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  )
104
 
105
- try:
106
- output = llm(
107
- prompt,
108
- max_tokens=180, # FAST
109
- temperature=0.7,
110
- top_p=0.9,
111
- top_k=40,
112
- repeat_penalty=1.1,
113
- stop=["<|end|>", "<|user|>"],
114
- echo=False,
115
- )
116
-
117
- raw = output["choices"][0]["text"]
118
- cleaned = clean_output(raw)
119
-
120
- if not cleaned or cleaned.lower() == text.lower():
121
- cleaned = fallback_humanize(text)
122
-
123
- return {
124
- "original": text,
125
- "style": style,
126
- "humanized": cleaned,
127
- "success": True
128
- }
129
-
130
- except Exception as e:
131
- print("❌ Model error:", e)
132
- return {
133
- "original": text,
134
- "style": style,
135
- "humanized": fallback_humanize(text),
136
- "success": False
137
- }
138
-
139
- @app.get("/")
140
- def health():
141
- return {"status": "ok", "model": MODEL_FILE}
 
1
+ import os
2
  from fastapi import FastAPI, HTTPException
3
+ from pydantic import BaseModel
 
 
4
  from llama_cpp import Llama
5
+ from contextlib import asynccontextmanager
6
 
7
+ # =========================
8
+ # MODEL CONFIG
9
+ # =========================
10
  MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
11
  MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
12
 
13
+ N_THREADS = int(os.getenv("N_THREADS", "8"))
14
+ N_CTX = int(os.getenv("N_CTX", "2048"))
15
+ N_BATCH = int(os.getenv("N_BATCH", "256"))
16
+
17
+ llm = None
18
+
19
+ # =========================
20
+ # FASTAPI LIFESPAN
21
+ # =========================
22
+ @asynccontextmanager
23
+ async def lifespan(app: FastAPI):
24
+ global llm
25
+ llm = Llama(
26
+ model_path=MODEL_FILE,
27
+ n_ctx=N_CTX,
28
+ n_threads=N_THREADS,
29
+ n_batch=N_BATCH,
30
+ verbose=False,
31
+ )
32
+ yield
33
+
34
+ app = FastAPI(title="AI Humanizer", lifespan=lifespan)
35
+
36
+ # =========================
37
+ # INPUT SCHEMA
38
+ # =========================
39
  class HumanizeRequest(BaseModel):
40
+ text: str
41
+ section: str # abstract | introduction | methodology | results | discussion
42
+ author_notes: str | None = None
43
+
44
+ # =========================
45
+ # SECTION-AWARE STYLE MAP
46
+ # =========================
47
+ SECTION_STYLE = {
48
+ "abstract": "Write concisely, densely, and objectively. Avoid narrative tone.",
49
+ "introduction": "Use contextual and motivational academic tone. Explain relevance.",
50
+ "methodology": "Be procedural, precise, and restrained. No persuasion.",
51
+ "results": "Be cautious, numerical, and observational. No strong claims.",
52
+ "discussion": "Be interpretive, reflective, and analytical."
 
 
 
 
 
 
 
53
  }
54
 
55
+ # =========================
56
+ # PROMPT BUILDER
57
+ # =========================
58
+ def build_prompt(text: str, section: str, author_notes: str | None):
59
+ style = SECTION_STYLE.get(section.lower(), "Use formal academic tone.")
60
+
61
+ notes_block = (
62
+ f"\nAuthor context (do not invent new reasoning):\n{author_notes}\n"
63
+ if author_notes else ""
64
+ )
65
+
66
+ return f"""
67
+ You are an academic writing assistant.
68
+
69
+ GOAL:
70
+ Restore natural human authorship signals while preserving formal academic language.
71
+
72
+ NON-NEGOTIABLE RULES:
73
+ - Preserve all technical meaning, claims, numbers, and citations
74
+ - Do NOT add new information
75
+ - Do NOT remove uncertainty
76
+ - Do NOT invent justifications
77
+ - Do NOT introduce grammar or punctuation errors
78
+
79
+ STYLE GUIDANCE:
80
+ {style}
81
+
82
+ HUMANIZATION RULES:
83
+ - Vary sentence rhythm naturally (short / medium / long)
84
+ - Reorder clauses where appropriate
85
+ - Avoid overused academic fillers (e.g., "Moreover", "Furthermore", "It is important to note")
86
+ - Prefer implicit transitions over explicit signposting
87
+ - Preserve and highlight existing author reasoning or constraints
88
+ - Use controlled lexical variation without replacing technical terms
89
+
90
+ {notes_block}
91
+
92
+ FINAL CHECK:
93
+ Ensure the text sounds like a researcher explaining their own work.
94
+
95
+ TEXT TO HUMANIZE:
96
+ {text}
97
+
98
+ OUTPUT:
99
+ Return ONLY the humanized text.
100
+ """.strip()
101
+
102
+ # =========================
103
+ # API ENDPOINT
104
+ # =========================
105
+ @app.post("/humanize")
106
+ def humanize(req: HumanizeRequest):
107
+ if not req.text.strip():
108
+ raise HTTPException(status_code=400, detail="Input text is empty")
109
+
110
+ prompt = build_prompt(req.text, req.section, req.author_notes)
111
+
112
+ response = llm(
113
+ prompt,
114
+ max_tokens=512,
115
+ temperature=0.4,
116
+ top_p=0.9,
117
+ repetition_penalty=1.1,
118
+ stop=["<|end|>"]
119
  )
120
 
121
+ output = response["choices"][0]["text"].strip()
122
+ return {"humanized_text": output}