hatamo's picture
Added dummy endpoint and api key validation
f20dfb3 verified
from fastapi import FastAPI, HTTPException, Security, Depends
from fastapi.security import APIKeyHeader
from pydantic import BaseModel, HttpUrl
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import json
import re
import random
import os
MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct"
app = FastAPI()
# ========== KONFIGURACJA API KEY ==========
# Pobierz klucz z zmiennej środowiskowej (ustaw w Hugging Face Space)
API_KEY = os.getenv("API_KEY", "twoj-domyslny-klucz-testowy")
# Nagłówek do sprawdzania
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
async def validate_api_key(api_key: str = Security(api_key_header)):
"""Walidacja klucza API"""
if api_key is None:
raise HTTPException(
status_code=401,
detail="Brak klucza API. Dodaj nagłówek 'X-API-Key'"
)
if api_key != API_KEY:
raise HTTPException(
status_code=403,
detail="Nieprawidłowy klucz API"
)
return api_key
# ========== LOADING MODEL ==========
print("Loading model...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.float32,
device_map="auto"
)
print("Model loaded")
# ========== ENDPOINTS Z OCHRONĄ ==========
class GenerateRequest(BaseModel):
prompt: str
max_new_tokens: int = 120
@app.post("/generate")
def generate(req: GenerateRequest, api_key: str = Depends(validate_api_key)):
inputs = tokenizer(req.prompt, return_tensors="pt")
with torch.no_grad():
output = model.generate(
**inputs,
max_new_tokens=req.max_new_tokens,
do_sample=True,
temperature=0.7,
top_p=0.9
)
text = tokenizer.decode(output[0], skip_special_tokens=True)
return {"response": text}
# ---------- NEW ENDPOINT ----------
ALLOWED_DOMAINS = [
"allegro.pl",
"olx.pl",
"ebay.com",
"www.ebay.com"
]
class AnalyzeAuctionRequest(BaseModel):
url: HttpUrl
@app.post("/analyze-auction")
def analyze_auction(
req: AnalyzeAuctionRequest,
api_key: str = Depends(validate_api_key)
):
url = str(req.url)
if not any(domain in url for domain in ALLOWED_DOMAINS):
raise HTTPException(
status_code=400,
detail="Obsługiwane są tylko linki z Allegro, OLX i eBay"
)
prompt = f"""
Jesteś ekspertem od antyków (monety, broń biała, ceramika).
Twoim zadaniem jest OCENIĆ aukcję i PODJĄĆ JEDNĄ DECYZJĘ.
Musisz wybrać DOKŁADNIE JEDEN status:
- autentyk
- replika
- scam
- nieznane
NIE WOLNO zwracać list, opcji ani schematów.
NIE WOLNO używać znaków | w wartościach.
NIE WOLNO powtarzać przykładu.
Zwróć WYŁĄCZNIE wypełniony JSON pomiędzy znacznikami <json></json>.
<json>
{{
"status": "autentyk",
"description": "Konkretny opis decyzji, odnoszący się do realiów rynku antyków."
}}
</json>
Link aukcji:
{url}
"""
inputs = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
output = model.generate(
**inputs,
max_new_tokens=250,
temperature=0.2,
do_sample=False
)
text = tokenizer.decode(output[0], skip_special_tokens=True)
# -------- JSON EXTRACTION --------
json_text = None
# 1 Preferowane: <json>...</json>
tag_match = re.search(r"<json>([\s\S]*?)</json>", text)
if tag_match:
json_text = tag_match.group(1)
# 2 Fallback: pierwsze { ... }
if not json_text:
brace_match = re.search(r"\{[\s\S]*?\}", text)
if brace_match:
json_text = brace_match.group()
# 3 Totalny fallback
if not json_text:
return {
"status": "nieznane",
"description": "Model nie zwrócił możliwej do odczytania struktury danych"
}
try:
return json.loads(json_text)
except Exception:
return {
"status": "nieznane",
"description": "Nie udało się poprawnie sparsować odpowiedzi modelu"
}
#----------- DUMMY RESPONSE-----------
@app.post("/analyze-auction-dummy")
def analyze_auction_dummy(
req: AnalyzeAuctionRequest,
api_key: str = Depends(validate_api_key)
):
url = str(req.url)
if not any(domain in url for domain in ALLOWED_DOMAINS):
raise HTTPException(
status_code=400,
detail="Obsługiwane są tylko linki z Allegro, OLX i eBay"
)
statuses = ["autentyk", "replika", "scam", "nieznane"]
descriptions = {
"autentyk": "Aukcja wygląda na zgodną z typowymi ofertami autentycznych antyków.",
"replika": "Oferta może dotyczyć współczesnej repliki stylizowanej na antyk.",
"scam": "Występują elementy, które mogą sugerować próbę oszustwa.",
"nieznane": "Na podstawie dostępnych informacji nie można jednoznacznie ocenić aukcji."
}
status = random.choice(statuses)
return {
"status": status,
"description": descriptions[status]
}
# ---------- HEALTH (BEZ OCHRONY) ----------
@app.get("/")
def health():
return {"status": "ok"}