fix(document): catch httpx timeout from gateway OCR backend (#892 r3)
Round-3 review on PR #892 found a real bug: the gateway backend's httpx.Timeout raises httpx.ReadTimeout (a httpx.TimeoutException, NOT a builtin TimeoutError), so the `except TimeoutError` added in r2 only covered the Mistral (anyio.fail_after) path — gateway timeouts still fell through to reason="error". Catch both (TimeoutError, httpx.TimeoutException) so either backend's timeout lands in the dedicated parse_failed_reason="timeout" bucket. Add an end-to-end test driving a gateway httpx.ReadTimeout through the processor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2f8875e736
commit
ebd0b469f5
@@ -264,11 +264,13 @@ class OcrProcessor(DocumentProcessor):
|
|||||||
text, boundaries = await backend.ocr(
|
text, boundaries = await backend.ocr(
|
||||||
content, content_type.split(";")[0].strip().lower()
|
content, content_type.split(";")[0].strip().lower()
|
||||||
)
|
)
|
||||||
except TimeoutError:
|
except (TimeoutError, httpx.TimeoutException):
|
||||||
# anyio.fail_after / httpx read-timeout raise TimeoutError with an
|
# Two timeout shapes reach here: the Mistral backend's
|
||||||
# empty message; give it its own reason bucket and a useful log so a
|
# anyio.fail_after raises the builtin TimeoutError, while the gateway
|
||||||
# too-low DOCUMENT_OCR_TIMEOUT_SECONDS is distinguishable from a
|
# backend's httpx.Timeout raises httpx.ReadTimeout (a
|
||||||
# provider that's actually erroring.
|
# httpx.TimeoutException, NOT a TimeoutError). Catch both so a
|
||||||
|
# too-low DOCUMENT_OCR_TIMEOUT_SECONDS lands in its own reason bucket
|
||||||
|
# rather than being conflated with provider errors.
|
||||||
timeout = settings.document_ocr_timeout_seconds
|
timeout = settings.document_ocr_timeout_seconds
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"OCR timed out for %s after %.1fs", filename or "<bytes>", timeout
|
"OCR timed out for %s after %.1fs", filename or "<bytes>", timeout
|
||||||
|
|||||||
@@ -149,6 +149,25 @@ async def test_processor_timeout_returns_timeout_reason(monkeypatch):
|
|||||||
assert "timed out" in r.error
|
assert "timed out" in r.error
|
||||||
|
|
||||||
|
|
||||||
|
async def test_gateway_httpx_timeout_maps_to_timeout_reason(monkeypatch):
|
||||||
|
"""A gateway httpx.ReadTimeout (not a builtin TimeoutError) must still map to
|
||||||
|
parse_failed_reason='timeout', not 'error'."""
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
class _HttpxTimeoutBackend:
|
||||||
|
async def ocr(self, content, mime_type):
|
||||||
|
raise httpx.ReadTimeout("read timed out")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
ocr, "get_settings", lambda: _settings(document_ocr_timeout_seconds=5.0)
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(ocr, "build_ocr_backend", lambda s: _HttpxTimeoutBackend())
|
||||||
|
r = await ocr.OcrProcessor().process(b"%PDF-1.7", "application/pdf")
|
||||||
|
assert r.success is False
|
||||||
|
assert r.metadata["parse_failed_reason"] == "timeout"
|
||||||
|
assert "timed out" in r.error
|
||||||
|
|
||||||
|
|
||||||
async def test_gateway_backend_uses_configured_timeout(mocker, monkeypatch):
|
async def test_gateway_backend_uses_configured_timeout(mocker, monkeypatch):
|
||||||
"""The gateway OCR call must use DOCUMENT_OCR_TIMEOUT_SECONDS (resolved per
|
"""The gateway OCR call must use DOCUMENT_OCR_TIMEOUT_SECONDS (resolved per
|
||||||
call), not the old hardcoded 180s constant."""
|
call), not the old hardcoded 180s constant."""
|
||||||
|
|||||||
Reference in New Issue
Block a user