GitHub Actions
fix: 0 test failures; FileService; real RagService; emergency probe; chat return
4aaae80
Raw
History Blame
4.45 kB
"""TranslationService β€” registers trans.text@1.0 on the bus."""
from __future__ import annotations
from typing import Any
from hearthnet.constants import TRANSLATION_MAX_CHARS
class TranslationService:
name = "translation"
version = "1.0"
def __init__(
self,
backends: list[Any] | None = None,
bus: Any = None,
) -> None:
if backends is not None:
self._backends = backends
else:
self._backends = self._discover_backends()
if bus is not None:
self.register(bus)
# ── Backend discovery ─────────────────────────────────────────────────────
def _discover_backends(self) -> list[Any]:
backends: list[Any] = []
try:
from hearthnet.services.translation.backends.nllb import NllbBackend
b = NllbBackend()
if b.health().get("status") == "ok":
backends.append(b)
except Exception:
pass
return backends
def _select_backend(
self,
from_lang: str,
to_lang: str,
preferred: str | None = None,
) -> Any | None:
for backend in self._backends:
if preferred and backend.name != preferred:
continue
if backend.health().get("status") != "ok":
continue
pairs = backend.supported_pairs
if from_lang == "auto" or (from_lang, to_lang) in pairs:
return backend
# Any healthy backend as fallback
for backend in self._backends:
if backend.health().get("status") == "ok":
return backend
return None
# ── Capability registration ───────────────────────────────────────────────
def register(self, bus: Any) -> None:
from hearthnet.bus.capability import CapabilityDescriptor
desc = CapabilityDescriptor(
name="trans.text",
version=(1, 0),
stability="stable",
params={
"backends": [b.name for b in self._backends],
"max_chars": TRANSLATION_MAX_CHARS,
},
max_concurrent=4,
trust_required="member",
timeout_seconds=30,
idempotent=True,
)
bus.register_capability(desc, self._handle_translate, self.params_compatible)
def params_compatible(self, offered: dict, requested: dict) -> bool:
req_backend = requested.get("backend")
if not req_backend:
return True
return req_backend in offered.get("backends", [])
# ── Handler ───────────────────────────────────────────────────────────────
async def _handle_translate(self, req: Any) -> dict:
body = req.body if hasattr(req, "body") else req
inp = body.get("input", body)
text: str = inp.get("text", "")
from_lang: str = inp.get("from_lang", "auto")
to_lang: str | None = inp.get("to_lang")
domain: str | None = inp.get("domain")
preferred: str | None = inp.get("backend")
if not to_lang:
return {"error": "bad_request", "reason": "to_lang is required"}
if len(text) > TRANSLATION_MAX_CHARS:
return {
"error": "bad_request",
"reason": f"Text too long: {len(text)} > {TRANSLATION_MAX_CHARS} chars",
}
backend = self._select_backend(from_lang, to_lang, preferred)
if backend is None:
return {
"error": "backend_unavailable",
"reason": "No healthy translation backend available",
}
try:
result = await backend.translate(
text, from_lang=from_lang, to_lang=to_lang, domain=domain
)
except Exception as exc:
return {"error": "internal_error", "reason": str(exc)}
return {
"text": result.text,
"from_lang": result.from_lang,
"to_lang": result.to_lang,
"backend": result.backend,
"ms": result.ms,
}