Retries failed LLM calls with prompt mutation targeted at the specific failure mode detected. Production fix over v1: - Uses tenacity for battle-tested retry logic - Jittered exponential backoff (prevents thundering herd) - Injection and circuit-open failures are neve
| 613 | |
| 614 | |
| 615 | class RetryEngine: |
| 616 | """ |
| 617 | Retries failed LLM calls with prompt mutation targeted at the |
| 618 | specific failure mode detected. |
| 619 | |
| 620 | Production fix over v1: |
| 621 | - Uses tenacity for battle-tested retry logic |
| 622 | - Jittered exponential backoff (prevents thundering herd) |
| 623 | - Injection and circuit-open failures are never retried |
| 624 | """ |
| 625 | |
| 626 | def __init__(self, config: ControlLayerConfig): |
| 627 | self.config = config |
| 628 | |
| 629 | def get_mutation_hint(self, failure_mode: FailureMode) -> str: |
| 630 | return MUTATION_HINTS.get( |
| 631 | failure_mode, |
| 632 | "Follow the instructions carefully and try again.", |
| 633 | ) |
| 634 | |
| 635 | def should_retry(self, failure_mode: FailureMode, attempt: int) -> bool: |
| 636 | if attempt >= self.config.max_attempts: |
| 637 | return False |
| 638 | if failure_mode in NO_RETRY_MODES: |
| 639 | return False |
| 640 | return True |
| 641 | |
| 642 | def jittered_delay_s(self, attempt: int) -> float: |
| 643 | """ |
| 644 | Exponential backoff with random jitter. |
| 645 | Prevents thundering herd when multiple requests retry simultaneously. |
| 646 | """ |
| 647 | base_s = self.config.base_delay_ms / 1000 |
| 648 | max_s = self.config.max_delay_ms / 1000 |
| 649 | jitter_s = self.config.jitter_ms / 1000 |
| 650 | delay = min(base_s * (2 ** (attempt - 1)), max_s) |
| 651 | delay += random.uniform(0, jitter_s) |
| 652 | return delay |
| 653 | |
| 654 | |
| 655 | # ============================================================================= |
no outgoing calls