From 13e871f2c0ff986016ac4f34e1b273f7745668d0 Mon Sep 17 00:00:00 2001 From: David Traina <44659830+DavidTraina@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:39:30 -0600 Subject: [PATCH] fix(batch-evaluation): use a 60 s timeout for observation page fetches The observations v2 API can scan for up to 45 s before returning a page, but page fetches used the client-wide default timeout of 5 s. Timeouts are not retried, so the run stopped, and resuming repeated the same request. Send a per-request timeout above the server budget without changing the global timeout, and drop the 'after N retries' wording from the fetch failure log, since timeouts and 422s are never retried. --- langfuse/batch_evaluation.py | 12 ++++++++---- tests/unit/test_batch_evaluation.py | 5 ++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/langfuse/batch_evaluation.py b/langfuse/batch_evaluation.py index 4d7bf3371..3e7540a31 100644 --- a/langfuse/batch_evaluation.py +++ b/langfuse/batch_evaluation.py @@ -38,6 +38,9 @@ metadata, model, usage, prompt, metrics, trace_context. """ +# The v2 observations API scans for up to 45 s before it returns a page. +_OBSERVATIONS_FETCH_TIMEOUT_SECONDS = 60 + def _with_time_fields(fields: Optional[str]) -> str: """Add the ``time`` group, whose ``updatedAt`` picks the newest duplicate row.""" @@ -962,9 +965,7 @@ def build_resume_token() -> BatchEvaluationResumeToken: fields=fields, ) except Exception as e: - logger.error( - "Failed to fetch batch after %s retries: %s", max_retries, e - ) + logger.error("Failed to fetch batch: %s", e) return self._build_result( total_items_fetched=total_items_fetched, @@ -1117,7 +1118,10 @@ async def _fetch_batch_with_retry( limit=limit, cursor=cursor, filter=filter, - request_options={"max_retries": max_retries}, + request_options={ + "max_retries": max_retries, + "timeout_in_seconds": _OBSERVATIONS_FETCH_TIMEOUT_SECONDS, + }, ) return list(response.data), response.meta.cursor diff --git a/tests/unit/test_batch_evaluation.py b/tests/unit/test_batch_evaluation.py index f6c29f4f6..3f3265ba1 100644 --- a/tests/unit/test_batch_evaluation.py +++ b/tests/unit/test_batch_evaluation.py @@ -255,7 +255,10 @@ async def test_fetch_failure_returns_resume_token_for_failed_page(): assert resumed.completed is True assert set(resumed.item_evaluations) == {"obs-3", "obs-2", "obs-1", "obs-0"} - assert api.calls[-1]["request_options"] == {"max_retries": 3} + assert api.calls[-1]["request_options"] == { + "max_retries": 3, + "timeout_in_seconds": 60, + } @pytest.mark.asyncio