noexit-proxy / app.py
ckonteos80
Add multi-provider proxy with Hugging Face support
ea4b76e
import os
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Literal
import httpx
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Read API keys from Hugging Face Secrets
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
HF_TOKEN = os.environ.get("HF_TOKEN")
if not OPENAI_API_KEY:
logger.warning("OPENAI_API_KEY not set — OpenAI provider will be unavailable")
if not HF_TOKEN:
logger.warning("HF_TOKEN not set — Hugging Face provider will be unavailable")
# Provider config: each provider has a base URL and a key
PROVIDERS = {
"openai": {
"url": "https://api.openai.com/v1/chat/completions",
"key": OPENAI_API_KEY,
},
"hf": {
"url": "https://router.huggingface.co/v1/chat/completions",
"key": HF_TOKEN,
},
}
app = FastAPI(title="Proxy for Unity")
# Enable CORS for Unity WebGL builds
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify your domain
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: str
temperature: float
messages: List[Message]
provider: Literal["openai", "hf"] = "openai" # default keeps existing Unity code working
@app.get("/")
async def root():
return {
"status": "running",
"message": "Proxy Active",
"providers": {
"openai": bool(OPENAI_API_KEY),
"hf": bool(HF_TOKEN),
},
"endpoints": {
"chat": "POST /chat"
}
}
@app.post("/chat")
async def proxy_chat(request: ChatRequest):
provider_config = PROVIDERS.get(request.provider)
if not provider_config or not provider_config["key"]:
raise HTTPException(
status_code=400,
detail=f"Provider '{request.provider}' is not configured"
)
try:
logger.info(
f"Provider: {request.provider} | Model: {request.model} | "
f"Messages: {len(request.messages)}"
)
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
provider_config["url"],
json={
"model": request.model,
"temperature": request.temperature,
"messages": [
{"role": msg.role, "content": msg.content}
for msg in request.messages
]
},
headers={
"Authorization": f"Bearer {provider_config['key']}",
"Content-Type": "application/json"
}
)
if response.status_code != 200:
logger.error(f"{request.provider} API error: {response.text}")
raise HTTPException(
status_code=response.status_code,
detail=response.text
)
result = response.json()
logger.info(f"Successfully proxied {request.provider} request")
return result
except httpx.HTTPError as e:
logger.error(f"HTTP error: {str(e)}")
raise HTTPException(status_code=500, detail=f"Proxy error: {str(e)}")
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")
@app.get("/health")
async def health():
return {
"status": "healthy",
"openai_configured": bool(OPENAI_API_KEY),
"hf_configured": bool(HF_TOKEN),
}