Spaces:
Sleeping
Sleeping
File size: 4,373 Bytes
034506e d90a0af 012abcf 034506e d90a0af 034506e d90a0af 034506e 012abcf 034506e 012abcf 034506e 012abcf 034506e 012abcf 034506e 012abcf 034506e 012abcf d90a0af | 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 | """FastAPI service — model-serving API consumed by the Django application."""
from __future__ import annotations
import os
import httpx
from fastapi import FastAPI, Header, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from scalar_fastapi import get_scalar_api_reference
from pydantic import BaseModel, Field
import providers
from languages import as_list
readme_path = os.path.join(os.path.dirname(__file__), "..", "README.md")
readme_content = ""
if os.path.exists(readme_path):
with open(readme_path, "r", encoding="utf-8") as f:
raw_text = f.read()
if raw_text.startswith("---"):
parts = raw_text.split("---", 2)
readme_content = parts[2] if len(parts) >= 3 else raw_text
else:
readme_content = raw_text
app = FastAPI(
title="Translator Model API",
description=readme_content,
version="1.3.0",
)
_origins = os.environ.get("CORS_ORIGINS", "*").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in _origins],
allow_methods=["*"],
allow_headers=["*"],
)
MAX_INPUT_CHARS = int(os.environ.get("MAX_INPUT_CHARS", "5000"))
class TranslateRequest(BaseModel):
text: str = Field(..., description="Text to translate.")
source: str = Field(..., description="Source FLORES-200 code, e.g. 'eng_Latn'.")
target: str = Field(..., description="Target FLORES-200 code, e.g. 'kor_Hang'.")
engine: str | None = Field(
None, description="Engine id; defaults to first available."
)
class TranslateResponse(BaseModel):
translation: str
source: str
target: str
engine: str
@app.get("/api/health")
def health() -> dict:
return {"status": "ok", "default_engine": providers.default_id()}
@app.get("/api/engines")
def engines() -> dict:
return {
"engines": [vars(i) for i in providers.all_infos()],
"default": providers.default_id(),
}
@app.get("/api/languages")
def languages() -> dict:
return {"languages": as_list()}
@app.post("/api/translate", response_model=TranslateResponse)
def translate_endpoint(
req: TranslateRequest,
x_gemini_key: str | None = Header(default=None),
x_groq_key: str | None = Header(default=None),
) -> TranslateResponse:
if not req.text.strip():
return TranslateResponse(
translation="",
source=req.source,
target=req.target,
engine=req.engine or "",
)
if len(req.text) > MAX_INPUT_CHARS:
raise HTTPException(400, f"Input too long (max {MAX_INPUT_CHARS} chars).")
engine_id = req.engine or providers.default_id()
if not engine_id:
raise HTTPException(503, "No translation engine is configured.")
provider = providers.get(engine_id)
if provider is None:
raise HTTPException(400, f"Unknown engine: {engine_id!r}")
# Client-supplied key (bring-your-own-key) for API engines.
client_keys = {"gemini": x_gemini_key, "groq": x_groq_key}
api_key = client_keys.get(provider.key_field) if provider.key_field else None
if provider.kind == "api":
if not (provider.is_available() or api_key):
raise HTTPException(
503, provider.setup_hint or "Add an API key in Settings."
)
elif not provider.is_available():
raise HTTPException(
503, provider.setup_hint or f"Engine '{engine_id}' is not available."
)
try:
result = provider.translate(req.text, req.source, req.target, api_key=api_key)
except ValueError as exc:
raise HTTPException(400, str(exc)) from exc
except httpx.HTTPStatusError as exc:
raise HTTPException(
502, f"Upstream API error: {exc.response.status_code}"
) from exc
except Exception as exc: # noqa: BLE001 — surface engine errors to the client
raise HTTPException(500, f"Translation failed: {exc}") from exc
return TranslateResponse(
translation=result, source=req.source, target=req.target, engine=engine_id
)
@app.get("/scalar", include_in_schema=False)
async def scalar_html():
return get_scalar_api_reference(
openapi_url=app.openapi_url,
title=app.title,
)
@app.get("/", include_in_schema=False)
def root():
return RedirectResponse(url="/scalar") |