aria-chatbot / app.py
ArmanXAI's picture
Upload folder using huggingface_hub
2bb4a0d verified
Raw
History Blame Contribute Delete
5.5 kB
"""ARIA — Flask backend for Arman Adil Mangat's portfolio chatbot.
Runs as a Docker Space on HuggingFace (port 7860).
Endpoints:
POST /chat {"message": str, "history": [{"role": ..., "content": ...}]}
GET /health {"status": "ok"}
The only secret is GROQ_API_KEY (HF Space secret / local env var).
"""
from __future__ import annotations
import os
import time
from collections import defaultdict, deque
from flask import Flask, jsonify, request
from flask_cors import CORS
from groq import Groq
from rag import TOP_K, RagIndex
# ── Config ────────────────────────────────────────────────────────────────
ALLOWED_ORIGINS = [
"https://armanadilmangat.github.io", # GitHub Pages (production)
"http://localhost:5173", # Vite dev server
"http://127.0.0.1:5173",
]
GROQ_MODEL = "llama-3.1-8b-instant"
MAX_HISTORY_MESSAGES = 12 # last 6 user/assistant turns
MAX_MESSAGE_CHARS = 1000
RATE_LIMIT = 20 # requests …
RATE_WINDOW = 60 # … per seconds, per IP
SYSTEM_PROMPT = """\
You are ARIA (Arman's Resume Intelligence Assistant), the AI assistant on \
Arman Adil Mangat's portfolio website.
Answer questions about Arman's background, projects, skills, and goals using \
ONLY the context below. Be concise, friendly, and concrete — lead with numbers \
and specifics. Keep answers under 120 words unless the visitor asks for depth.
If asked something about Arman that is not in the context, say you don't have \
that detail and suggest emailing him at aadilmangat@gmail.com. Never invent \
facts about Arman. Never share personal details beyond the provided context — \
you do not have his phone number, family details, or home address.
You may hold light general conversation (greetings, small talk, simple general \
questions), but always steer back to Arman's work.
If asked for contact details, give: aadilmangat@gmail.com · \
github.com/ArmanAdilMangat · linkedin.com/in/armanadilmangat · \
huggingface.co/ArmanXAI
CONTEXT ABOUT ARMAN:
{context}
"""
# ── App setup ─────────────────────────────────────────────────────────────
app = Flask(__name__)
CORS(app, resources={
r"/chat": {"origins": ALLOWED_ORIGINS},
r"/health": {"origins": "*"},
})
print("ARIA: building RAG index (one-time at startup)…", flush=True)
INDEX = RagIndex()
print(f"ARIA: index ready — {len(INDEX.chunks)} chunks.", flush=True)
groq_client = Groq(api_key=os.environ["GROQ_API_KEY"])
# ── Simple in-memory per-IP rate limiter ─────────────────────────────────
_hits: dict[str, deque] = defaultdict(deque)
def _client_ip() -> str:
fwd = request.headers.get("X-Forwarded-For", "")
return (fwd.split(",")[0].strip() if fwd else request.remote_addr) or "unknown"
def _rate_limited(ip: str) -> bool:
now = time.time()
q = _hits[ip]
while q and now - q[0] > RATE_WINDOW:
q.popleft()
if len(q) >= RATE_LIMIT:
return True
q.append(now)
return False
# ── Routes ────────────────────────────────────────────────────────────────
@app.get("/health")
def health():
return jsonify({"status": "ok"})
@app.post("/chat")
def chat():
if _rate_limited(_client_ip()):
return jsonify({"reply": "You're sending messages a bit fast — give it "
"a few seconds and try again."}), 429
data = request.get_json(silent=True) or {}
message = (data.get("message") or "").strip()[:MAX_MESSAGE_CHARS]
if not message:
return jsonify({"error": "message is required"}), 400
# Sanitize client-supplied history (roles whitelisted, length capped).
history = []
for m in (data.get("history") or [])[-MAX_HISTORY_MESSAGES:]:
role = m.get("role")
content = (m.get("content") or "").strip()
if role in ("user", "assistant") and content:
history.append({"role": role, "content": content[:MAX_MESSAGE_CHARS]})
# RAG: retrieve top-k chunks and stuff them into the system prompt.
chunks = INDEX.search(message, TOP_K)
context = "\n\n---\n\n".join(
f"[{c.source}{c.heading or 'overview'}]\n{c.text}" for c in chunks
)
messages = [
{"role": "system", "content": SYSTEM_PROMPT.format(context=context)},
*history,
{"role": "user", "content": message},
]
try:
resp = groq_client.chat.completions.create(
model=GROQ_MODEL,
messages=messages,
temperature=0.4,
max_tokens=400,
)
reply = (resp.choices[0].message.content or "").strip()
except Exception: # noqa: BLE001 — surface a friendly error, log the rest
app.logger.exception("Groq call failed")
return jsonify({"reply": "Hmm, my brain (the LLM API) hiccuped. Try "
"again in a moment — or email Arman directly "
"at aadilmangat@gmail.com."}), 502
return jsonify({"reply": reply})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=False)