ai-humanizer / app.py
Rofati's picture
New approach: formal academic with subtle imperfections, no casual overcorrection
f96a05e verified
Raw
History Blame Contribute Delete
9.68 kB
"""
AI Humanizer β€” Gemma 3 27B via Inference API + AI Detector
"""
import os, time, gc
import gradio as gr
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from huggingface_hub import InferenceClient
from transformers import pipeline
# ── Config ──────────────────────────────────────────────────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN", "")
MODEL_ID = "google/gemma-3-27b-it"
DETECTOR_MODEL = "Hello-SimpleAI/chatgpt-detector-roberta"
TEMPERATURE = 0.8
INSTRUCTIONS = """Take this text and rewrite it as if you are a second-year university student who understands the material well but writes imperfectly. You are writing this for a class paper that will be checked by Turnitin.
Your writing style:
You understand the concepts but occasionally phrase things in a slightly roundabout way. You sometimes start sentences with "And" or "But." You use semicolons sometimes but not always correctly. Your paragraphs are uneven in length. You do not write with perfect parallel structure. Some of your sentences are too long and could be split, but you left them. You occasionally repeat a word when a synonym would be better. You write in a formal register but it is clearly a student's formal, not a professor's.
Constraints:
Same word count as the original, give or take ten percent. Every argument, citation, and claim preserved exactly. No dashes of any kind. No bullet points or lists. No symbols or decorative characters. Only periods, commas, semicolons, colons, apostrophes, question marks.
Banned vocabulary. Do not use any of these anywhere: Furthermore, Moreover, Additionally, Nevertheless, Consequently, In conclusion, In summary, It is important to note, It should be noted, It is worth mentioning, This suggests, What stands out, The evidence points to, A closer look reveals, Ultimately, Significantly, Notably, Indeed, Thus, Hence, Thereby, Wherein, Whereby, Basically, Kind of, Sort of, It's like, The thing is, Really important, Plays a crucial role.
Output the rewritten text only. No explanations."""
# ── Load Inference Client ───────────────────────────────────────────────────
print("[INIT] Connecting to HF Inference API...")
client = InferenceClient(token=HF_TOKEN)
print(f"[INIT] Model: {MODEL_ID}")
# ── Load Detector ───────────────────────────────────────────────────────────
print("[INIT] Loading detector...")
detector = pipeline("text-classification", model=DETECTOR_MODEL, device=-1, top_k=None)
print("[INIT] Ready.")
gc.collect()
# ── Functions ───────────────────────────────────────────────────────────────
def detect_text(text):
if not text or not text.strip():
return 0.0, 0.0
results = detector(text.strip(), truncation=True, max_length=512)
scores = {r["label"]: r["score"] for r in results[0]}
return scores.get("ChatGPT", 0.0), scores.get("Human", 0.0)
def humanize_stream(text):
if not text or not text.strip():
yield "", "", ""
return
t0 = time.perf_counter()
input_words = len(text.split())
messages = [
{"role": "user", "content": f"{INSTRUCTIONS}\n\nText ({input_words} words):\n\n{text.strip()}"},
]
output = ""
n_tokens = 0
ttft = 0
try:
stream = client.chat.completions.create(
model=MODEL_ID,
messages=messages,
max_tokens=int(input_words * 2),
temperature=TEMPERATURE,
top_p=0.92,
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
token = chunk.choices[0].delta.content
output += token
n_tokens += 1
if n_tokens == 1:
ttft = time.perf_counter() - t0
elapsed = time.perf_counter() - t0
tps = round(n_tokens / elapsed, 1) if elapsed > 0 else 0
yield output, f"⏱ {elapsed:.1f}s · {n_tokens} tok · {tps} tok/s", ""
except Exception as e:
yield f"Error: {str(e)}", "", ""
return
elapsed = time.perf_counter() - t0
tps = round(n_tokens / elapsed, 1) if elapsed > 0 else 0
final = output.strip()
ai_score, human_score = detect_text(final)
if ai_score > 0.7:
det = f"πŸ€– AI: {ai_score:.0%} | Human: {human_score:.0%} β€” still detectable"
elif ai_score > 0.4:
det = f"⚠️ AI: {ai_score:.0%} | Human: {human_score:.0%} β€” borderline"
else:
det = f"βœ… AI: {ai_score:.0%} | Human: {human_score:.0%} β€” passes"
out_w = len(final.split())
yield final, f"βœ… {elapsed:.1f}s Β· {n_tokens} tok Β· {tps} tok/s Β· TTFT: {ttft:.1f}s Β· {input_words}β†’{out_w} words", det
def detect_only(text):
if not text or not text.strip():
return ""
ai, human = detect_text(text.strip())
if ai > 0.7:
return f"πŸ€– **AI-Generated** β€” AI: {ai:.0%} | Human: {human:.0%}"
elif ai > 0.4:
return f"⚠️ **Borderline** β€” AI: {ai:.0%} | Human: {human:.0%}"
else:
return f"βœ… **Human-Written** β€” AI: {ai:.0%} | Human: {human:.0%}"
# ── FastAPI ─────────────────────────────────────────────────────────────────
app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"])
@app.get("/api/health")
async def health():
return {"status": "ok", "model": MODEL_ID}
@app.post("/api/humanize")
async def api_humanize(request: Request):
body = await request.json()
text = body.get("text", "").strip()
if not text:
return JSONResponse({"error": "text required"}, 400)
input_words = len(text.split())
t0 = time.perf_counter()
messages = [
{"role": "user", "content": f"{INSTRUCTIONS}\n\nText ({input_words} words):\n\n{text}"},
]
try:
resp = client.chat.completions.create(
model=MODEL_ID, messages=messages,
max_tokens=int(input_words * 2),
temperature=TEMPERATURE, top_p=0.92,
)
result = resp.choices[0].message.content.strip()
except Exception as e:
return JSONResponse({"error": str(e)}, 500)
elapsed = time.perf_counter() - t0
ai, human = detect_text(result)
return JSONResponse({
"result": result,
"input_words": input_words,
"output_words": len(result.split()),
"time_seconds": round(elapsed, 2),
"detection": {"human": round(human, 3), "ai": round(ai, 3)}
})
@app.post("/api/detect")
async def api_detect(request: Request):
body = await request.json()
text = body.get("text", "").strip()
if not text:
return JSONResponse({"error": "text required"}, 400)
ai, human = detect_text(text)
return JSONResponse({"human": round(human, 3), "ai": round(ai, 3),
"prediction": "human" if human > 0.5 else "ai"})
# ── Gradio ──────────────────────────────────────────────────────────────────
with gr.Blocks(title="AI Humanizer") as demo:
gr.Markdown("# AI Text Humanizer\nPaste AI text. Same length back, undetectable.")
with gr.Tabs():
with gr.Tab("Humanize"):
with gr.Row():
h_in = gr.Textbox(lines=12, label="Paste AI text", placeholder="Paste ChatGPT/Claude output...")
h_out = gr.Textbox(lines=12, label="Humanized output", interactive=False)
h_stats = gr.Markdown("")
h_det = gr.Markdown("")
with gr.Row():
gr.Button("Clear").click(fn=lambda: ("", "", "", ""), outputs=[h_in, h_out, h_stats, h_det])
gr.Button("Humanize", variant="primary").click(
fn=humanize_stream, inputs=[h_in], outputs=[h_out, h_stats, h_det])
with gr.Tab("Check Detection"):
d_in = gr.Textbox(lines=12, label="Text to check", placeholder="Paste text...")
d_out = gr.Markdown("")
with gr.Row():
gr.Button("Clear").click(fn=lambda: ("", ""), outputs=[d_in, d_out])
gr.Button("Check", variant="primary").click(fn=detect_only, inputs=[d_in], outputs=[d_out])
with gr.Accordion("API for React", open=False):
gr.Markdown("""
```javascript
const API = "https://rofati-ai-humanizer.hf.space";
// Humanize
const res = await fetch(`${API}/api/humanize`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({text: "your AI text"})
});
// Detect
const res = await fetch(`${API}/api/detect`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({text: "text to check"})
});
```
""")
app = gr.mount_gradio_app(app, demo, path="/")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)))