From 6e25294bc81421d8c56bc4a185e77da7b065e5cb Mon Sep 17 00:00:00 2001 From: Sean Turner Date: Mon, 27 Apr 2026 18:31:31 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20address=20review=20feedback=20=E2=80=94?= =?UTF-8?q?=20split=20threshold/target,=20sleep=20before=20truncation=20re?= =?UTF-8?q?try?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename max_chars → threshold_chars and add separate truncate_to_chars parameter so the threshold and truncation target can differ by design, replacing the hardcoded 1000. Callers can now shrink blocks aggressively without re-processing blocks that are already acceptable. - Add 2s sleep before the truncation-path `continue` to match the bare-retry pacing. If Bedrock is throttling after the original 400, immediately hitting it again risks a second rejection before the truncated payload is evaluated. --- strix/llm/llm.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/strix/llm/llm.py b/strix/llm/llm.py index a1a6012e..6a8ffb22 100644 --- a/strix/llm/llm.py +++ b/strix/llm/llm.py @@ -182,6 +182,10 @@ class LLM: ): if attempt >= max_retries: self._raise_error(e) + # Pace the provider — matches the 2s sleep on the bare-retry + # path so a throttled provider isn't hit back-to-back after the + # original 400. + await asyncio.sleep(2) continue if attempt >= max_retries or not self._should_retry(e): self._raise_error(e) @@ -334,12 +338,21 @@ class LLM: @staticmethod def _truncate_large_tool_results( - messages: list[dict[str, Any]], max_chars: int = 2000 + messages: list[dict[str, Any]], + threshold_chars: int = 2000, + truncate_to_chars: int = 1000, ) -> bool: """Truncate large tool_result XML blocks to recover from BadRequestError. - Scans all messages for tool_result blocks exceeding max_chars and truncates them. - Called repeatedly on each 400 until it returns False (nothing left to truncate). + Scans all messages for tool_result blocks whose body exceeds threshold_chars + and shrinks them to truncate_to_chars. Called repeatedly on each 400 until it + returns False (nothing left to truncate). + + threshold_chars and truncate_to_chars are independent: the threshold decides + which blocks qualify for truncation, and truncate_to_chars is the size of the + retained prefix. They are not the same value to allow aggressive shrinking of + blocks that are well over the threshold without re-processing blocks that are + already acceptable. """ truncated_any = False pattern = re.compile( @@ -350,10 +363,10 @@ class LLM: def _truncate_match(m: re.Match) -> str: nonlocal truncated_any prefix, body, suffix = m.group(1), m.group(2), m.group(3) - if len(body) <= max_chars: + if len(body) <= threshold_chars: return m.group(0) truncated_any = True - kept = body[:1000] + kept = body[:truncate_to_chars] return ( f"{prefix}{kept}\n\n... [content truncated from {len(body)} to {len(kept)} chars " f"due to request size limit — file requires manual review] ...{suffix}"