NEKReport/proxy.py

95 lines
2.8 KiB
Python

import logging
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, Response
import httpx
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
UPSTREAM_URL = os.getenv(
"LLM_UPSTREAM_URL",
"https://llm.corp.septem.pro/v1/chat/completions",
).strip()
OPENROUTER_KEY = os.getenv("OPENROUTER_API_KEY", "").strip()
def _client_timeout() -> httpx.Timeout:
total = float(os.getenv("LLM_PROXY_TIMEOUT_SECONDS", "120"))
connect = float(os.getenv("LLM_PROXY_CONNECT_TIMEOUT", "15"))
return httpx.Timeout(total, connect=connect)
@app.post("/v1/chat/completions")
async def proxy(req: Request):
if not OPENROUTER_KEY:
logger.error("OPENROUTER_API_KEY is not set (env or .env)")
return JSONResponse(
status_code=503,
content={
"error": "Service unavailable",
"details": "Set OPENROUTER_API_KEY for the LLM proxy (or add it to .env).",
},
)
try:
body = await req.json()
except Exception as e:
return JSONResponse(
status_code=400,
content={"error": "Invalid JSON", "details": str(e)},
)
verify = os.getenv("HTTPX_VERIFY", "true").lower() not in ("0", "false", "no")
async with httpx.AsyncClient(verify=verify) as client:
try:
r = await client.post(
UPSTREAM_URL,
headers={
"Authorization": f"Bearer {OPENROUTER_KEY}",
"HTTP-Referer": os.getenv("LLM_HTTP_REFERER", "http://localhost"),
"X-Title": os.getenv("LLM_PROXY_X_TITLE", "rag-engine"),
},
json=body,
timeout=_client_timeout(),
)
except httpx.RequestError as e:
logger.error(
"LLM upstream unreachable (%s): %s",
UPSTREAM_URL,
e,
exc_info=True,
)
return JSONResponse(
status_code=502,
content={
"error": "Bad gateway",
"details": str(e),
"upstream": UPSTREAM_URL,
"hint": "Проверьте VPN, DNS, доступность хоста и переменную LLM_UPSTREAM_URL.",
},
)
if r.status_code >= 500:
logger.warning(
"LLM upstream HTTP %s, body (prefix): %s",
r.status_code,
(r.text or "")[:800],
)
return Response(
content=r.content,
status_code=r.status_code,
media_type=r.headers.get("content-type", "application/json"),
)