|
|
|
|
|
import os |
|
|
import math |
|
|
import requests |
|
|
from flask import Flask, request, jsonify |
|
|
from flask_cors import CORS |
|
|
from langdetect import detect |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HF_API_URL = "https://api-inference.huggingface.co/models/YOUR_USERNAME/YOUR_MODEL" |
|
|
HF_TOKEN = os.getenv("HF_TOKEN") |
|
|
|
|
|
HEADERS = { |
|
|
"Authorization": f"Bearer {HF_TOKEN}", |
|
|
"Content-Type": "application/json" |
|
|
} |
|
|
|
|
|
app = Flask(__name__) |
|
|
CORS(app) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def entropy(probs): |
|
|
"""Shannon entropy as epistemic uncertainty indicator.""" |
|
|
return -sum(p * math.log2(p) for p in probs if p > 0) |
|
|
|
|
|
def normalize_labels(hf_output): |
|
|
""" |
|
|
Normalize Hugging Face output into a stable schema. |
|
|
Expected HF format: |
|
|
[ |
|
|
{"label": "HUMAN", "score": 0.73}, |
|
|
{"label": "AI", "score": 0.27} |
|
|
] |
|
|
""" |
|
|
result = {item["label"].lower(): float(item["score"]) for item in hf_output} |
|
|
human_p = result.get("human", 0.0) |
|
|
ai_p = result.get("ai", 0.0) |
|
|
|
|
|
return human_p, ai_p |
|
|
|
|
|
def hf_inference(text): |
|
|
payload = {"inputs": text} |
|
|
r = requests.post(HF_API_URL, headers=HEADERS, json=payload, timeout=30) |
|
|
r.raise_for_status() |
|
|
return r.json() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/analyze", methods=["POST"]) |
|
|
def analyze(): |
|
|
data = request.get_json() |
|
|
text = data.get("text", "").strip() |
|
|
|
|
|
if not text: |
|
|
return jsonify({"error": "Empty input"}), 400 |
|
|
|
|
|
|
|
|
try: |
|
|
language = detect(text) |
|
|
except Exception: |
|
|
language = "unknown" |
|
|
|
|
|
|
|
|
hf_raw = hf_inference(text) |
|
|
|
|
|
if not isinstance(hf_raw, list): |
|
|
return jsonify({"error": "Unexpected model response", "raw": hf_raw}), 500 |
|
|
|
|
|
human_p, ai_p = normalize_labels(hf_raw) |
|
|
|
|
|
|
|
|
label = "Human" if human_p >= ai_p else "Machine" |
|
|
confidence = max(human_p, ai_p) |
|
|
|
|
|
|
|
|
H = entropy([human_p, ai_p]) |
|
|
|
|
|
|
|
|
explainability_stub = { |
|
|
"method": "pending", |
|
|
"note": ( |
|
|
"This model endpoint does not natively expose SHAP/LIME. " |
|
|
"Post-hoc explainability must be computed locally using a " |
|
|
"replicated model or proxy explainer." |
|
|
), |
|
|
"token_attributions": [] |
|
|
} |
|
|
|
|
|
|
|
|
fairness_context = { |
|
|
"language": language, |
|
|
"human_probability": human_p, |
|
|
"ai_probability": ai_p, |
|
|
"entropy": H |
|
|
} |
|
|
|
|
|
response = { |
|
|
"prediction": { |
|
|
"label": label, |
|
|
"confidence": round(confidence, 4) |
|
|
}, |
|
|
"probabilities": { |
|
|
"human": round(human_p, 4), |
|
|
"machine": round(ai_p, 4) |
|
|
}, |
|
|
"uncertainty": { |
|
|
"entropy": round(H, 4), |
|
|
"interpretation": ( |
|
|
"High entropy indicates epistemic ambiguity; " |
|
|
"classification should be treated cautiously." |
|
|
) |
|
|
}, |
|
|
"linguistic_context": { |
|
|
"detected_language": language |
|
|
}, |
|
|
"explainability": explainability_stub, |
|
|
"fairness_audit_fields": fairness_context |
|
|
} |
|
|
|
|
|
return jsonify(response) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/", methods=["GET"]) |
|
|
def index(): |
|
|
return jsonify({ |
|
|
"system": "HATA API", |
|
|
"capabilities": [ |
|
|
"Human vs AI classification", |
|
|
"Probability calibration", |
|
|
"Uncertainty estimation", |
|
|
"Language-aware auditing", |
|
|
"Explainability-ready schema", |
|
|
"Fairness instrumentation" |
|
|
] |
|
|
}) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
app.run(host="0.0.0.0", port=5000, debug=True) |
|
|
|