diff --git a/strix/llm/llm.py b/strix/llm/llm.py index 57790e40..6e951e2a 100644 --- a/strix/llm/llm.py +++ b/strix/llm/llm.py @@ -1,4 +1,5 @@ import asyncio +import re from collections.abc import AsyncIterator from dataclasses import dataclass from typing import Any @@ -171,6 +172,8 @@ class LLM: if self._is_bad_request(e): if not bad_request_retried: bad_request_retried = True + if attempt >= max_retries: + self._raise_error(e) await asyncio.sleep(2) continue truncate_enabled = Config.get("strix_truncate_on_oversize") or "" @@ -180,6 +183,8 @@ class LLM: and self._truncate_large_tool_results(messages) ): bad_request_truncated = True + if attempt >= max_retries: + self._raise_error(e) continue if attempt >= max_retries or not self._should_retry(e): self._raise_error(e) @@ -339,32 +344,33 @@ class LLM: Scans messages in reverse for tool_result XML blocks that exceed max_chars and replaces their content with a truncated version plus a skip notice. Returns True if any truncation was performed (caller should retry the request). - """ - import re + Note: All oversized tool_result blocks within a single message are truncated + in one pass — this is intentional to maximise payload size reduction per retry. + """ truncated_any = False pattern = re.compile( r"(\s*[^<]*\s*)(.*?)(\s*)", re.DOTALL, ) + 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: + return m.group(0) + truncated_any = True + kept = body[:1000] + 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}" + ) + for msg in reversed(messages): content = msg.get("content") if not isinstance(content, str) or "" not in content: continue - def _truncate_match(m: re.Match) -> str: - prefix, body, suffix = m.group(1), m.group(2), m.group(3) - if len(body) <= max_chars: - return m.group(0) - nonlocal truncated_any - truncated_any = True - kept = body[:1000] - 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}" - ) - msg["content"] = pattern.sub(_truncate_match, content) if truncated_any: break