Chat-Service / app /services /nlp_client.py
ArabicNewsAnalyzer's picture
Upload 56 files
de28957 verified
Raw
History Blame Contribute Delete
2.81 kB
from __future__ import annotations
import asyncio
import logging
from typing import Any
import httpx
import numpy as np
from app.config import get_settings
logger = logging.getLogger(__name__)
FAISS_DIM = 384 # match Django's MiniLM output dim (NewsArticle.search_vector)
_NLP_MAX_RETRIES = 3
_NLP_TIMEOUT_S = 60.0 # generous — accounts for HF Space cold start
def _nlp_service_url() -> str:
settings = get_settings()
url = settings.nlp_service_url.rstrip("/")
if not url:
raise RuntimeError("NLP_SERVICE_URL not configured in settings")
return url
def _nlp_headers() -> dict[str, str]:
settings = get_settings()
if not settings.nlp_service_token:
raise RuntimeError("NLP_SERVICE_TOKEN not configured in settings")
return {
"Authorization": f"Bearer {settings.nlp_service_token}",
"Content-Type": "application/json",
}
async def _nlp_post(path: str, payload: dict) -> Any:
"""POST to the shared NLP microservice with retry on timeout/5xx (cold-start safe)."""
url = f"{_nlp_service_url()}{path}"
headers = _nlp_headers()
last_exc: Exception | None = None
async with httpx.AsyncClient(timeout=_NLP_TIMEOUT_S) as client:
for attempt in range(1, _NLP_MAX_RETRIES + 1):
try:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
return resp.json()
except (httpx.TimeoutException, httpx.ConnectError) as exc:
last_exc = exc
logger.warning(
"NLP service attempt %d/%d timed out: %s",
attempt, _NLP_MAX_RETRIES, exc,
)
if attempt < _NLP_MAX_RETRIES:
await asyncio.sleep(2 ** attempt) # 2s, 4s
except httpx.HTTPStatusError as exc:
if exc.response.status_code < 500:
raise # 4xx — don't retry
last_exc = exc
logger.warning(
"NLP service attempt %d/%d HTTP error: %s",
attempt, _NLP_MAX_RETRIES, exc,
)
if attempt < _NLP_MAX_RETRIES:
await asyncio.sleep(2 ** attempt)
raise RuntimeError(
f"NLP microservice unavailable after {_NLP_MAX_RETRIES} attempts"
) from last_exc
async def embed(texts: list[str]) -> np.ndarray:
"""
Embed texts via the shared remote MiniLM endpoint (same HF Space Django uses).
Returns float32 array of shape (len(texts), FAISS_DIM), L2-normalised.
"""
result: list[list[float]] = await _nlp_post("/embed", {"texts": texts})
return np.array(result, dtype="float32")