Spaces:
Running
Running
File size: 6,627 Bytes
4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b 4004dc2 e4d3a9b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
import os
import requests
import json
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
# --- CONFIGURATION ---
# 1. OpenAI/Azure Configuration
AI_SERVICE_TOKENS_RAW = os.environ.get("AI_SERVICE_TOKEN", "")
AI_SERVICE_TOKENS = [t.strip() for t in AI_SERVICE_TOKENS_RAW.split(",") if t.strip()]
OPENAI_API_URL = "https://models.inference.ai.azure.com/chat/completions"
OPENAI_MODEL_NAME = "gpt-4o-mini"
# 2. Google Gemini Configuration (Direct Google API)
# You need to set GOOGLE_API_KEY in your HF Space secrets
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", "")
# Use the stable Gemini 1.5 Flash model which supports JSON mode reliable
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemma-3-27b-it:generateContent?key={GOOGLE_API_KEY}"
app = FastAPI(
title="AI Backend Service",
description="Running on Hugging Face Spaces (Docker SDK)"
)
# --- MODELS ---
class AnalyzeRequest(BaseModel):
filename: str
model_provider: str = "openai" # 'openai' or 'gemma' (maps to Gemini)
# --- HELPERS ---
def get_headers(token):
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# --- ENDPOINTS ---
@app.get("/")
def home():
"""Health check endpoint."""
return {
"status": "active",
"platform": "Hugging Face Spaces",
"tokens_loaded": len(AI_SERVICE_TOKENS),
"google_api_enabled": bool(GOOGLE_API_KEY)
}
@app.get("/check-limit")
def check_limit():
"""
Checks the rate limit status of ALL configured AI Service Tokens (OpenAI only).
"""
if not AI_SERVICE_TOKENS:
return {"tokens_checked": 0, "results": [], "note": "OpenAI tokens missing"}
results = []
for i, token in enumerate(AI_SERVICE_TOKENS):
headers = get_headers(token)
payload = {
"model": OPENAI_MODEL_NAME,
"messages": [{"role": "user", "content": "Ping."}],
"temperature": 0.1,
"max_tokens": 1
}
try:
response = requests.post(OPENAI_API_URL, headers=headers, json=payload, timeout=10)
token_status = {
"token_index": i,
"status_code": response.status_code,
"valid": response.status_code == 200,
"remaining": response.headers.get('x-ratelimit-remaining-requests', 'N/A')
}
results.append(token_status)
except Exception as e:
results.append({"token_index": i, "status_code": "ERROR", "error": str(e)})
return {"tokens_checked": len(results), "results": results}
def call_openai_gpt4o(filename, tokens):
payload = {
"model": OPENAI_MODEL_NAME,
"messages": [
{"role": "system", "content": "You are an expert Movie and TV metadata analyst. Return ONLY raw JSON in the format: {\"title\": \"...\", \"year\": \"...\", \"isSeries\": false/true}. Analyze the following filename and extract the data."},
{"role": "user", "content": f"Analyze: \"{filename}\""}
],
"temperature": 0.1,
"max_tokens": 500
}
last_error = ""
for i, token in enumerate(tokens):
try:
response = requests.post(OPENAI_API_URL, headers=get_headers(token), json=payload, timeout=30)
if response.status_code == 200:
content = response.json().get('choices', [{}])[0].get('message', {}).get('content')
return content
elif response.status_code in [429, 401, 403]:
last_error = f"Token {i}: {response.status_code}"
continue
else:
last_error = f"Token {i} Error: {response.text}"
except Exception as e:
last_error = str(e)
continue
raise Exception(f"OpenAI All tokens failed. Last: {last_error}")
def call_google_gemini(filename):
if not GOOGLE_API_KEY:
raise Exception("GOOGLE_API_KEY not configured.")
# Construct the Gemini payload
prompt = f"""
You are an expert Movie and TV metadata analyst.
Analyze the filename: "{filename}"
Identify the title, year, and whether it is a series.
Return ONLY a raw JSON object with this exact format:
{{"title": "Movie Title", "year": "2024", "isSeries": false}}
"""
payload = {
"contents": [{
"parts": [{"text": prompt}]
}],
"generationConfig": {
"temperature": 0.1,
"maxOutputTokens": 100,
# Removed strict responseMimeType to avoid 400 error on some models
# "responseMimeType": "application/json"
}
}
response = requests.post(GEMINI_API_URL, headers={"Content-Type": "application/json"}, json=payload, timeout=30)
if response.status_code != 200:
raise Exception(f"Google Gemini API Error {response.status_code}: {response.text}")
result = response.json()
# Extract text from Gemini response structure
try:
return result['candidates'][0]['content']['parts'][0]['text']
except (KeyError, IndexError):
raise Exception(f"Unexpected response structure from Gemini: {str(result)}")
@app.post("/analyze")
def analyze_filename(request: AnalyzeRequest):
"""
Analyze filename using selected provider (openai or gemma/gemini).
"""
raw_content = ""
provider_used = request.model_provider
try:
if provider_used == "gemma":
# Although the frontend sends "gemma", we map this to our Google Gemini function
raw_content = call_google_gemini(request.filename)
else:
# Default to OpenAI
if not AI_SERVICE_TOKENS: raise HTTPException(500, "OpenAI tokens missing.")
raw_content = call_openai_gpt4o(request.filename, AI_SERVICE_TOKENS)
# Parse JSON output from either provider
if raw_content:
clean_content = raw_content.replace("```json", "").replace("```", "").strip()
# Simple extraction of JSON object if surrounded by text
start = clean_content.find('{')
end = clean_content.rfind('}') + 1
if start != -1 and end != -1:
clean_content = clean_content[start:end]
return json.loads(clean_content)
return {"error": "No content returned", "provider": provider_used}
except Exception as e:
print(f"Analysis Error ({provider_used}): {e}")
raise HTTPException(status_code=500, detail=f"Analysis failed ({provider_used}): {str(e)}") |