From a1731c9a91c300ed08fdbe990904274a64d7a4ad Mon Sep 17 00:00:00 2001 From: "david.lustig" Date: Mon, 11 May 2026 17:34:15 -0400 Subject: [PATCH] do not retry on 413s --- py/src/braintrust/logger.py | 16 ++++++++++ py/src/braintrust/test_logger.py | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/py/src/braintrust/logger.py b/py/src/braintrust/logger.py index 522b31e1..df246593 100644 --- a/py/src/braintrust/logger.py +++ b/py/src/braintrust/logger.py @@ -1389,6 +1389,22 @@ def _submit_logs_request(self, items: Sequence[LogItemWithMeta], max_request_siz else: resp_errmsg = str(error) + # 413 Payload Too Large: retrying with the same batch cannot succeed; skip backoff. + if error is None and resp is not None and resp.status_code == 413: + errmsg = ( + f"log request failed. Elapsed time: {time.time() - start_time} seconds. " + f"Payload size: {payload_bytes}. Error: {resp_errmsg}" + ) + if self.failed_publish_payloads_dir: + _HTTPBackgroundLogger._write_payload_to_dir( + payload_dir=self.failed_publish_payloads_dir, payload=dataStr + ) + self._log_failed_payloads_dir() + if self.sync_flush: + raise Exception(errmsg) + print(errmsg, file=self.outfile) + return + is_retrying = i + 1 < self.num_tries retrying_text = "" if is_retrying else " Retrying" errmsg = f"log request failed. Elapsed time: {time.time() - start_time} seconds. Payload size: {payload_bytes}.{retrying_text} Error: {resp_errmsg}" diff --git a/py/src/braintrust/test_logger.py b/py/src/braintrust/test_logger.py index e8c22bdc..c6518bf8 100644 --- a/py/src/braintrust/test_logger.py +++ b/py/src/braintrust/test_logger.py @@ -187,6 +187,56 @@ def test_init_with_saved_parameters_attaches_reference(self): assert payload["parameters_version"] == "v1" +class TestHTTPBackgroundLoggerLogs3(TestCase): + def test_submit_logs_request_413_skips_retries(self) -> None: + """Any 413 while publishing ``/logs3`` cannot succeed on retry with the same payload. + + ``sync_flush`` controls whether the terminal failure raises instead of printing. + """ + from braintrust.logger import ( + LogItemWithMeta, + Logs3OverflowInputRow, + _HTTPBackgroundLogger, + ) + + item = LogItemWithMeta( + str_value="{}", + overflow_meta=Logs3OverflowInputRow( + object_ids={}, + has_comment=False, + is_delete=False, + byte_size=2, + ), + ) + max_result = {"max_request_size": 10**9, "can_use_overflow": True} + + for response_text in ("Request Too Long", "", "Payload Too Large"): + for sync_flush in (False, True): + with self.subTest(response_text=response_text, sync_flush=sync_flush): + mock_resp = MagicMock() + mock_resp.ok = False + mock_resp.status_code = 413 + mock_resp.text = response_text + + mock_conn = MagicMock() + mock_conn.post.return_value = mock_resp + + bg = _HTTPBackgroundLogger(LazyValue(lambda: mock_conn, use_mutex=False)) + bg.num_tries = 5 + bg.sync_flush = sync_flush + + with patch("braintrust.logger.time.sleep") as mock_sleep: + if sync_flush: + with self.assertRaises(Exception) as cm: + bg._submit_logs_request([item], max_result) + self.assertIn("413", str(cm.exception)) + else: + bg._submit_logs_request([item], max_result) + + self.assertEqual(mock_conn.post.call_count, 1) + mock_sleep.assert_not_called() + + class TestLogger(TestCase): def test_load_prompt_prefers_version_over_environment_for_project_slug(self): mock_api_conn = MagicMock()