When the primary strategy exhausts all retries, routes to a registered fallback handler. Fallbacks are called in registration order. First non-empty response wins.
| 657 | # ============================================================================= |
| 658 | |
| 659 | class FallbackRouter: |
| 660 | """ |
| 661 | When the primary strategy exhausts all retries, routes to a |
| 662 | registered fallback handler. |
| 663 | |
| 664 | Fallbacks are called in registration order. First non-empty response wins. |
| 665 | """ |
| 666 | |
| 667 | def __init__(self): |
| 668 | self._strategies: Dict[str, Callable[[str], str]] = {} |
| 669 | self._order: List[str] = [] |
| 670 | |
| 671 | def register(self, name: str, fn: Callable[[str], str]) -> None: |
| 672 | self._strategies[name] = fn |
| 673 | if name not in self._order: |
| 674 | self._order.append(name) |
| 675 | |
| 676 | def route( |
| 677 | self, user_input: str, failure_mode: FailureMode |
| 678 | ) -> Tuple[str, str]: |
| 679 | for name in self._order: |
| 680 | try: |
| 681 | response = self._strategies[name](user_input) |
| 682 | if response: |
| 683 | log.info( |
| 684 | "fallback.used", |
| 685 | strategy=name, |
| 686 | failure_mode=failure_mode.value, |
| 687 | ) |
| 688 | return name, response |
| 689 | except Exception as exc: |
| 690 | log.error("fallback.error", strategy=name, error=str(exc)) |
| 691 | return "none", "" |
| 692 | |
| 693 | |
| 694 | # ============================================================================= |
no outgoing calls