viskav commited on
Commit
77438f0
·
verified ·
1 Parent(s): 9b56add

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -123
app.py CHANGED
@@ -1,107 +1,94 @@
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
- from huggingface_hub import hf_hub_download
7
 
8
- # ==================================================
9
- # MODEL CONFIGURATION
10
- # ==================================================
11
  MODEL_REPO = "bartowski/Phi-3.1-mini-4k-instruct-GGUF"
12
  MODEL_FILE = "Phi-3.1-mini-4k-instruct-IQ2_M.gguf"
13
 
14
- N_THREADS = int(os.getenv("N_THREADS", "8"))
15
- N_CTX = int(os.getenv("N_CTX", "2048"))
16
- N_BATCH = int(os.getenv("N_BATCH", "256"))
17
-
18
- llm = None
19
-
20
- # ==================================================
21
- # FASTAPI LIFESPAN (MODEL LOAD)
22
- # ==================================================
23
- @asynccontextmanager
24
- async def lifespan(app: FastAPI):
25
- global llm
26
- try:
27
- print("⏳ Downloading model...")
28
- model_path = hf_hub_download(
29
- repo_id=MODEL_REPO,
30
- filename=MODEL_FILE,
31
- )
32
-
33
- print("✅ Model downloaded. Loading...")
34
- llm = Llama(
35
- model_path=model_path,
36
- n_ctx=N_CTX,
37
- n_threads=N_THREADS,
38
- n_batch=N_BATCH,
39
- verbose=False,
40
- )
41
 
42
- print("🚀 Model loaded successfully")
 
43
 
44
- except Exception as e:
45
- print("❌ Model load failed:", e)
46
- llm = None
47
-
48
- yield
49
-
50
- # ==================================================
51
- # APP INIT
52
- # ==================================================
53
- app = FastAPI(
54
- title="AI Humanizer",
55
- description="Academic-safe AI Humanizer",
56
- version="1.0",
57
- lifespan=lifespan
58
  )
59
- @app.get("/")
60
- def root():
61
- return {
62
- "status": "ok",
63
- "message": "AI Humanizer backend is running",
64
- "endpoints": {
65
- "humanize": "POST /humanize",
66
- "auth": "GET /api/auth/verify",
67
- "docs": "/docs"
68
- }
69
- }
70
 
71
- # ==================================================
72
- # DUMMY AUTH VERIFY (Frontend Fix)
73
- # ==================================================
74
- @app.get("/api/auth/verify")
75
- def verify_auth():
76
- return {"authenticated": True}
77
-
78
- # ==================================================
79
- # INPUT SCHEMA
80
- # ==================================================
81
  class HumanizeRequest(BaseModel):
82
- text: str
83
- section: str # abstract | introduction | methodology | results | discussion
84
- author_notes: str | None = None
85
-
86
- # ==================================================
87
- # SECTION STYLE MAP
88
- # ==================================================
89
- SECTION_STYLE = {
90
- "abstract": "Write concisely and densely with objective academic tone.",
91
- "introduction": "Use contextual, motivational academic tone.",
92
- "methodology": "Be procedural, precise, and restrained.",
93
- "results": "Be cautious, numerical, and observational.",
94
- "discussion": "Be interpretive, reflective, and analytical."
 
 
 
 
 
 
 
 
 
95
  }
96
 
97
- # ==================================================
98
- # PROMPT BUILDER
99
- # ==================================================
100
- def build_prompt(text: str, section: str, author_notes: str | None):
101
- style = SECTION_STYLE.get(section.lower(), "Use formal academic tone.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  notes_block = (
104
- f"\nAuthor context (do not invent reasoning):\n{author_notes}\n"
105
  if author_notes else ""
106
  )
107
 
@@ -116,62 +103,73 @@ NON-NEGOTIABLE RULES:
116
  - Do NOT add new information
117
  - Do NOT remove uncertainty
118
  - Do NOT invent justifications
 
119
  - Do NOT introduce grammar or punctuation errors
120
 
121
- STYLE GUIDANCE:
122
- {style}
123
 
124
  HUMANIZATION RULES:
125
  - Vary sentence rhythm naturally (short / medium / long)
126
  - Reorder clauses where appropriate
127
- - Avoid overused academic fillers (e.g., "Moreover", "Furthermore", "It is important to note")
128
- - Prefer implicit transitions over explicit signposting
129
- - Preserve existing author reasoning and constraints
130
- - Use controlled lexical variation without replacing technical terms
131
 
132
  {notes_block}
133
 
134
- FINAL CHECK:
135
- Ensure the text sounds like a researcher explaining their own work.
136
-
137
- TEXT TO HUMANIZE:
138
  {text}
139
 
140
  OUTPUT:
141
- Return ONLY the humanized text.
142
  """.strip()
143
 
144
- # ==================================================
145
- # HUMANIZE ENDPOINT
146
- # ==================================================
147
- @app.post("/humanize")
148
- def humanize(req: HumanizeRequest):
149
- if llm is None:
150
- raise HTTPException(
151
- status_code=503,
152
- detail="Model is still loading. Please try again in a few seconds."
153
- )
154
-
155
- if not req.text.strip():
156
- raise HTTPException(status_code=400, detail="Input text is empty")
157
 
158
- prompt = build_prompt(req.text, req.section, req.author_notes)
159
 
160
  try:
161
- response = llm.create_completion(
162
- prompt=prompt,
163
  max_tokens=400,
164
- temperature=0.4,
165
  top_p=0.9,
166
- repeat_penalty=1.1,
 
 
167
  )
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  except Exception as e:
170
- raise HTTPException(
171
- status_code=500,
172
- detail=f"Inference error: {str(e)}"
173
- )
 
 
 
174
 
 
 
 
175
  return {
176
- "humanized_text": response["choices"][0]["text"].strip()
 
 
177
  }
 
 
1
  from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel, Field
4
+ from typing import Literal, Optional
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 (Human Authorship Restorer)...")
13
+ llm = Llama.from_pretrained(
14
+ repo_id=MODEL_REPO,
15
+ filename=MODEL_FILE,
16
+ n_threads=4,
17
+ n_ctx=1024, # safer for HF Spaces
18
+ n_batch=128,
19
+ n_gpu_layers=0,
20
+ verbose=False,
21
+ )
22
+ print("✅ Model loaded")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ # ==================== FASTAPI ====================
25
+ app = FastAPI(title="AI Humanizer – Author Voice Restorer")
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=3000)
37
+ section: Literal[
38
+ "abstract",
39
+ "introduction",
40
+ "methodology",
41
+ "results",
42
+ "discussion"
43
+ ]
44
+ author_notes: Optional[str] = None
45
+
46
+ # ==================== SECTION-AWARE STYLE ====================
47
+ SECTION_GUIDANCE = {
48
+ "abstract":
49
+ "Write concisely and densely. Maintain objective academic tone.",
50
+ "introduction":
51
+ "Provide context and motivation. Sound like a researcher framing a problem.",
52
+ "methodology":
53
+ "Be procedural, precise, and restrained. No persuasive language.",
54
+ "results":
55
+ "Be cautious, observational, and factual. Avoid strong claims.",
56
+ "discussion":
57
+ "Be interpretive and reflective. Explain implications carefully."
58
  }
59
 
60
+ # ==================== OUTPUT CLEANER ====================
61
+ def clean_output(text: str) -> str:
62
+ text = re.sub(r'<\|.*?\|>', '', text)
63
+ text = re.sub(r'\s+', ' ', text)
64
+ return text.strip()
65
+
66
+ # ==================== FALLBACK (UNCHANGED, SAFE) ====================
67
+ def fallback_humanize(text: str) -> str:
68
+ replacements = [
69
+ ("utilize", "use"),
70
+ ("commence", "start"),
71
+ ("approximately", "about"),
72
+ ("therefore", "so"),
73
+ ("however", "but"),
74
+ ("in order to", "to"),
75
+ ("due to the fact that", "because"),
76
+ ("prior to", "before"),
77
+ ("subsequent to", "after"),
78
+ ]
79
+
80
+ result = text
81
+ for formal, simple in replacements:
82
+ result = re.sub(formal, simple, result, flags=re.IGNORECASE)
83
+
84
+ return result
85
+
86
+ # ==================== PROMPT BUILDER ====================
87
+ def build_prompt(text: str, section: str, author_notes: Optional[str]) -> str:
88
+ guidance = SECTION_GUIDANCE.get(section, "Use formal academic tone.")
89
 
90
  notes_block = (
91
+ f"\nAuthor context (do NOT invent new reasoning):\n{author_notes}\n"
92
  if author_notes else ""
93
  )
94
 
 
103
  - Do NOT add new information
104
  - Do NOT remove uncertainty
105
  - Do NOT invent justifications
106
+ - Do NOT change terminology
107
  - Do NOT introduce grammar or punctuation errors
108
 
109
+ SECTION GUIDANCE:
110
+ {guidance}
111
 
112
  HUMANIZATION RULES:
113
  - Vary sentence rhythm naturally (short / medium / long)
114
  - Reorder clauses where appropriate
115
+ - Reduce overused academic fillers (e.g., "Moreover", "Furthermore")
116
+ - Prefer implicit transitions
117
+ - Preserve author intent and constraints
 
118
 
119
  {notes_block}
120
 
121
+ TEXT:
 
 
 
122
  {text}
123
 
124
  OUTPUT:
125
+ Return ONLY the revised text.
126
  """.strip()
127
 
128
+ # ==================== ENDPOINT ====================
129
+ @app.post("/api/humanize")
130
+ async def humanize(req: HumanizeRequest):
131
+ text = req.text.strip()
 
 
 
 
 
 
 
 
 
132
 
133
+ prompt = build_prompt(text, req.section, req.author_notes)
134
 
135
  try:
136
+ output = llm(
137
+ prompt,
138
  max_tokens=400,
139
+ temperature=0.4, # controlled, academic-safe
140
  top_p=0.9,
141
+ top_k=40,
142
+ stop=["<|user|>", "<|end|>"],
143
+ echo=False,
144
  )
145
 
146
+ raw = output["choices"][0]["text"]
147
+ cleaned = clean_output(raw)
148
+
149
+ if not cleaned or cleaned.lower() == text.lower():
150
+ cleaned = fallback_humanize(text)
151
+
152
+ return {
153
+ "original": text,
154
+ "section": req.section,
155
+ "humanized": cleaned,
156
+ "success": True
157
+ }
158
+
159
  except Exception as e:
160
+ print("❌ Inference error:", e)
161
+ return {
162
+ "original": text,
163
+ "section": req.section,
164
+ "humanized": fallback_humanize(text),
165
+ "success": False
166
+ }
167
 
168
+ # ==================== HEALTH ====================
169
+ @app.get("/")
170
+ def health():
171
  return {
172
+ "status": "ok",
173
+ "model": MODEL_FILE,
174
+ "endpoint": "/api/humanize"
175
  }