Add Vision image fetch failure observability

This commit is contained in:
2026-08-29 07:56:35 +02:00
parent 1087561400
commit 4ed3b619be
4 changed files with 217 additions and 15 deletions
+30
View File
@@ -189,34 +189,64 @@ def _col(name: Optional[str]) -> str:
return name or COLLECTION_NAME
def _safe_clip_detail(response: httpx.Response) -> str:
"""Extract only a bounded machine-readable category; never log response text."""
try:
detail = response.json().get("detail")
except Exception:
return "non_json_response"
if not isinstance(detail, str):
return "structured_error"
import re
match = re.search(r"(?:error_type|category)[=: ]+([a-z_]+)", detail)
return match.group(1)[:40] if match else "upstream_error"
def _log_clip_failure(operation: str, *, elapsed_ms: float, status: int | None = None, error_type: str, safe_detail: str | None = None) -> None:
fields = {"event": "qdrant_clip_upstream_failed", "upstream": "clip", "operation": operation, "elapsed_ms": round(elapsed_ms, 1), "error_type": error_type}
if status is not None:
fields["upstream_status"] = status
if safe_detail is not None:
fields["safe_detail"] = safe_detail
logger.warning("%s", " ".join(f"{key}={value!r}" for key, value in fields.items()))
async def _embed_url(url: str) -> List[float]:
"""Call the CLIP service to get an image embedding."""
started_at = time.perf_counter()
async with httpx.AsyncClient(timeout=30) as http:
try:
r = await http.post(f"{CLIP_URL}/embed", json={"url": url})
except httpx.RequestError as e:
_log_clip_failure("embed_url", elapsed_ms=(time.perf_counter() - started_at) * 1000, error_type=type(e).__name__)
raise HTTPException(502, f"CLIP request failed: {str(e)}")
if r.status_code >= 400:
_log_clip_failure("embed_url", elapsed_ms=(time.perf_counter() - started_at) * 1000, status=r.status_code, error_type="upstream_http_error", safe_detail=_safe_clip_detail(r))
raise HTTPException(502, f"CLIP /embed error: {r.status_code} {r.text[:200]}")
try:
return r.json()["vector"]
except Exception:
_log_clip_failure("embed_url", elapsed_ms=(time.perf_counter() - started_at) * 1000, status=r.status_code, error_type="invalid_response", safe_detail=_safe_clip_detail(r))
raise HTTPException(502, f"CLIP /embed returned non-JSON: {r.status_code} {r.text[:200]}")
async def _embed_bytes(data: bytes) -> List[float]:
"""Call the CLIP service to embed uploaded file bytes."""
started_at = time.perf_counter()
async with httpx.AsyncClient(timeout=30) as http:
files = {"file": ("image", data, "application/octet-stream")}
try:
r = await http.post(f"{CLIP_URL}/embed/file", files=files)
except httpx.RequestError as e:
_log_clip_failure("embed_file", elapsed_ms=(time.perf_counter() - started_at) * 1000, error_type=type(e).__name__)
raise HTTPException(502, f"CLIP request failed: {str(e)}")
if r.status_code >= 400:
_log_clip_failure("embed_file", elapsed_ms=(time.perf_counter() - started_at) * 1000, status=r.status_code, error_type="upstream_http_error", safe_detail=_safe_clip_detail(r))
raise HTTPException(502, f"CLIP /embed/file error: {r.status_code} {r.text[:200]}")
try:
return r.json()["vector"]
except Exception:
_log_clip_failure("embed_file", elapsed_ms=(time.perf_counter() - started_at) * 1000, status=r.status_code, error_type="invalid_response", safe_detail=_safe_clip_detail(r))
raise HTTPException(502, f"CLIP /embed/file returned non-JSON: {r.status_code} {r.text[:200]}")