Spaces:
Sleeping
Sleeping
| """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 | |
| def health() -> dict: | |
| return {"status": "ok", "default_engine": providers.default_id()} | |
| def engines() -> dict: | |
| return { | |
| "engines": [vars(i) for i in providers.all_infos()], | |
| "default": providers.default_id(), | |
| } | |
| def languages() -> dict: | |
| return {"languages": as_list()} | |
| 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 | |
| ) | |
| async def scalar_html(): | |
| return get_scalar_api_reference( | |
| openapi_url=app.openapi_url, | |
| title=app.title, | |
| ) | |
| def root(): | |
| return RedirectResponse(url="/scalar") |