Skip to content

feat(resilience): provider fallback chain for retryable failures - #128

Merged
CybotTM merged 3 commits into
mainfrom
feature/provider-fallback-chain
Apr 22, 2026
Merged

feat(resilience): provider fallback chain for retryable failures#128
CybotTM merged 3 commits into
mainfrom
feature/provider-fallback-chain

Conversation

@CybotTM

@CybotTM CybotTM commented Apr 22, 2026

Copy link
Copy Markdown
Member

Summary

Adds a per-configuration fallback chain so a retryable provider failure (connection error, HTTP 5xx, 429 rate-limit) transparently re-runs the request against the next configuration in the chain.

  • FallbackChain DTO (readonly value object, JSON-persisted) stored on a new nullable tx_nrllm_configuration.fallback_chain column
  • FallbackChainExecutor service wraps LlmServiceManager::chatWithConfiguration() / completeWithConfiguration(); only retryable exceptions trigger fallback — misconfig / 4xx / unsupported-feature bubble up immediately
  • Streaming is intentionally not wrapped (can't swap providers mid-stream); documented in the method and ADR-021
  • Shallow resolution: a fallback's own chain is ignored to prevent cycles and attempt blow-up
  • FallbackChainExhaustedException carries every attempt so callers can surface the full failure sequence
  • Optional constructor dep on LlmServiceManager keeps existing tests passing without changes

Test plan

  • Unit: 46 new tests (DTO / executor / exception) — passing
  • Integration: 5 new tests covering LlmServiceManager → executor → real OpenAI/Claude providers with stubbed HTTP (primary-success, 503-to-fallback, 429-to-fallback, 401-no-fallback, exhausted chain)
  • Functional: 4 persistence round-trip tests for the new column (local env blocked by pre-existing TCA deprecation; CI will cover)
  • PHPStan level 10 clean
  • Architecture tests (PHPat) pass
  • PHP-CS-Fixer clean

Design notes

See ADR-021 for the Retryable vs. non-retryable decision, why streaming is excluded, and why fallback is shallow (not recursive).

Introduce FallbackChain DTO + FallbackChainExecutor so an LlmConfiguration
can list other configurations to retry when the primary fails with a
connection error, HTTP 5xx, or rate-limit (429). Non-retryable errors
(4xx other than 429, UnsupportedFeature, Configuration) still bubble up
unchanged.

Storage: new nullable fallback_chain TEXT column on tx_nrllm_configuration
holding a JSON list of configuration identifiers. Fallback is shallow
(recursion disabled) and does not apply to streaming.

Wired through LlmServiceManager::chatWithConfiguration and
completeWithConfiguration; streaming is intentionally untouched and
documented. Optional constructor dependency keeps existing tests passing
without changes.

See ADR-021 for rationale and scope limitations.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Copilot AI review requested due to automatic review settings April 22, 2026 09:44
@github-actions

github-actions Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Test-related changes configuration Configuration changes providers LLM provider changes services Service layer changes labels Apr 22, 2026
github-actions[bot]
github-actions Bot previously approved these changes Apr 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.87006% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.65%. Comparing base (ff92b1e) to head (0f87101).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
Classes/Service/FallbackChainExecutor.php 98.71% 1 Missing ⚠️
Classes/Service/LlmServiceManager.php 95.45% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #128      +/-   ##
============================================
+ Coverage     93.54%   93.65%   +0.11%     
- Complexity     2146     2192      +46     
============================================
  Files            82       85       +3     
  Lines          7905     8074     +169     
============================================
+ Hits           7395     7562     +167     
- Misses          510      512       +2     
Flag Coverage Δ
unit 93.65% <98.87%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Classes/Domain/DTO/FallbackChain.php 100.00% <100.00%> (ø)
...ider/Exception/FallbackChainExhaustedException.php 100.00% <100.00%> (ø)
Classes/Service/FallbackChainExecutor.php 98.71% <98.71%> (ø)
Classes/Service/LlmServiceManager.php 99.38% <95.45%> (-0.62%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
github-actions[bot]
github-actions Bot previously approved these changes Apr 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a per-configuration provider fallback chain so retryable provider failures (connection/5xx/429) automatically retry the request against alternate LlmConfigurations in order, plus persistence/UI support and test coverage.

Changes:

  • Adds fallback_chain storage (DB + Extbase model + TCA + i18n labels) backed by a JSON DTO (FallbackChain).
  • Introduces FallbackChainExecutor and wires it into LlmServiceManager::chatWithConfiguration() / completeWithConfiguration() (streaming explicitly excluded).
  • Adds unit/integration/functional tests and ADR-021 documenting the design.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
ext_tables.sql Adds tx_nrllm_configuration.fallback_chain column for persisted fallback chain JSON.
Configuration/TCA/tx_nrllm_configuration.php Adds backend form field + new “Fallback Chain” tab.
Resources/Private/Language/locallang_tca.xlf Adds labels/descriptions for fallback chain UI (EN).
Resources/Private/Language/de.locallang_tca.xlf Adds labels/descriptions for fallback chain UI (DE).
Classes/Domain/Model/LlmConfiguration.php Adds raw JSON field + DTO accessors/mutators and hasFallbackChain().
Classes/Domain/DTO/FallbackChain.php Adds DTO/value object for ordered, deduped fallback identifiers with JSON (de)serialization.
Classes/Service/FallbackChainExecutor.php Adds executor that applies fallback behavior on retryable exceptions.
Classes/Service/LlmServiceManager.php Wraps non-streaming config-based calls with fallback executor when available.
Classes/Provider/Exception/FallbackChainExhaustedException.php Adds exception carrying attempt history when the chain is exhausted.
Documentation/Adr/Index.rst Registers ADR-021 in the ADR index.
Documentation/Adr/Adr021ProviderFallbackChain.rst Documents fallback-chain rationale, scope, and constraints.
Tests/Unit/Domain/DTO/FallbackChainTest.php Unit tests for DTO behavior and JSON round-trips.
Tests/Unit/Service/FallbackChainExecutorTest.php Unit tests for retryability rules, skipping behavior, and exhaustion.
Tests/Unit/Provider/Exception/FallbackChainExhaustedExceptionTest.php Unit tests for exception messaging and attempt capture.
Tests/Integration/Service/FallbackChainIntegrationTest.php Integration tests across manager → executor → providers using stubbed HTTP.
Tests/Functional/Repository/LlmConfigurationFallbackChainTest.php Functional persistence round-trip tests for the new DB column.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Classes/Service/FallbackChainExecutor.php
Comment thread Classes/Service/FallbackChainExecutor.php
Comment thread Classes/Service/FallbackChainExecutor.php
Comment thread Classes/Provider/Exception/FallbackChainExhaustedException.php Outdated
Comment thread Configuration/TCA/tx_nrllm_configuration.php
Comment thread Classes/Domain/DTO/FallbackChain.php Outdated
Comment thread ext_tables.sql
Comment thread Classes/Domain/DTO/FallbackChain.php Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a provider fallback mechanism for LLM requests, allowing automatic retries against a chain of alternative configurations when primary providers encounter connection issues or rate limits. It introduces a FallbackChain DTO, a FallbackChainExecutor service, and integrates these into the LlmServiceManager, though streaming requests are intentionally excluded. Feedback focuses on enhancing the FallbackChain DTO by normalizing identifiers—specifically trimming whitespace and converting to lowercase—to ensure consistency and robustness against manual configuration edits.

Comment thread Classes/Domain/DTO/FallbackChain.php
Comment thread Classes/Domain/DTO/FallbackChain.php
Comment thread Classes/Domain/DTO/FallbackChain.php
Comment thread Classes/Domain/DTO/FallbackChain.php
- PSR-3 logging: pass Throwable in reserved `exception` context key;
  add separate `exceptionClass` for the class name.
- Rethrow primary's retryable error verbatim when the fallback chain
  contains only the primary's own identifier (otherwise we wrap a
  single attempt as "every configuration failed", which is misleading).
- Reword exhausted-chain message ("All N configuration(s) in the
  fallback chain failed") so it matches getAttemptedConfigurations()
  (which includes the primary).
- TCA fallback_chain field gets eval=trim for consistency with other
  text columns on this table.
- FallbackChain::sanitize() now tolerates non-string entries in the
  input array (skips them) instead of crashing with "Illegal offset
  type" during hydration of malformed JSON.
- FallbackChain normalises identifiers (trim + lowercase) on sanitize,
  contains, withLink, and without so they match the TCA-enforced
  lowercase-trimmed form of tx_nrllm_configuration.identifier.
  Hand-edited JSON with stray whitespace / capitals now still resolves.
- Class docblock updated to describe actual sanitisation behaviour
  (constructor does NOT sanitise; fromArray/fromJson + withLink do).
- New tests for every adjusted path: non-string sanitize drop, empty
  post-filter chain rethrow, case-insensitive contains / withLink /
  without, non-array configurationIdentifiers bail-out.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@CybotTM
CybotTM added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 625b6b3 Apr 22, 2026
40 of 42 checks passed
@CybotTM
CybotTM deleted the feature/provider-fallback-chain branch April 22, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

configuration Configuration changes documentation Improvements or additions to documentation providers LLM provider changes services Service layer changes tests Test-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants