fix(vision): worker down o in timeout non esce più come 500 nudo (#14)

_call_worker distingue httpx.TimeoutException (504) dal resto dei
TransportError/HTTPError (502, RemoteProtocolError incluso). Un 422 del
worker si propaga come 422 con il motivo del worker, non più 502 generico.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:34:06 +02:00
parent a330f2b340
commit 67392a946c
2 changed files with 98 additions and 12 deletions
+26 -12
View File
@@ -26,12 +26,14 @@ async def _call_worker(image_bytes: bytes, graph: dict) -> dict:
The worker's fault must not become the API's fault (spec's edge-case
table: "il worker isola il guasto, l'API resta in piedi, il task
riporta l'errore"). A stopped worker raises `httpx.ConnectError`; a job
past the timeout raises `httpx.ReadTimeout` - both would otherwise
propagate past this function and surface to the operator as a naked
500. The response shape is validated here too: a proxy answering 200
with something that is not the worker's JSON contract must not turn
into a bare KeyError further down.
riporta l'errore"). httpx.TimeoutException (ConnectTimeout, ReadTimeout,
WriteTimeout, PoolTimeout) becomes 504; every other transport failure
(ConnectError, ReadError, RemoteProtocolError when the worker dies
mid-response, ...) becomes 502 - both would otherwise propagate past
this function and surface to the operator as a naked 500. The response
shape is validated here too: a proxy answering 200 with something that
is not the worker's JSON contract must not turn into a bare KeyError
further down.
"""
try:
async with httpx.AsyncClient(timeout=120.0) as client:
@@ -40,16 +42,28 @@ async def _call_worker(image_bytes: bytes, graph: dict) -> dict:
files={"image": ("frame.png", image_bytes, "image/png")},
data={"graph": json.dumps(graph)},
)
except httpx.ConnectError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"the vision worker is unreachable: {exc}",
) from exc
except httpx.ReadTimeout as exc:
except httpx.TimeoutException as exc:
raise HTTPException(
status_code=status.HTTP_504_GATEWAY_TIMEOUT,
detail=f"the vision worker did not answer in time: {exc}",
) from exc
except (httpx.TransportError, httpx.HTTPError) as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"the vision worker is unreachable: {exc}",
) from exc
if response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT:
# The worker already diagnosed a bad request (undecodable image,
# malformed graph) - forward its reason instead of flattening it
# into a generic 502.
try:
detail = response.json().get("detail", response.text)
except ValueError:
detail = response.text
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=detail,
)
if response.status_code != 200:
raise HTTPException(