Skip to content

feat: reach external providers through a LiteLLM proxy with declared fallback - #35

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/22-external-provider
Oct 3, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/22-external-provider

Conversation

@Yash-Chindam

Copy link
Copy Markdown
Owner

Eighth PR closing gaps between the design spec and the implementation.

Gap this closes

§6/§7.1 name LiteLLM as the layer that normalizes external endpoints and centralizes fallback; §10 has "apply declared fallback if required". There was no external provider path: an external decision was sent to the local engine under the name approved-external-fallback.

What changed

  • DispatchingBackend sends local decisions to the engine and external ones to a LiteLLM proxy (ROUTER_EXTERNAL_BASE_URL, ROUTER_EXTERNAL_API_KEY). Provider credentials live in the proxy.
  • The privacy rule is enforced again at the dispatch boundary: a non-public request for an external model is refused (policy_violation) without contacting the provider.
  • Separate circuit breakers for the engine and the provider.
  • Fallback: when the local engine fails, a request already entitled to external routing (public, opted in, operator and tenant allow it) is re-routed. The route reason says so and router_fallbacks_total counts it.
  • Start-up refuses external_fallback_enabled with the vLLM backend and no proxy address.
  • config/litellm.yaml, and deploy/kubernetes/external-provider.yaml: proxy Deployment, Service, and a NetworkPolicy making it the only workload with internet egress, reachable only from the gateway.
  • The manifest secret check now allows a secret-shaped key only when its value is an os.environ/ reference.

Not done

  • Fallback applies to buffered requests only; a streamed request that fails is not re-routed.
  • Nothing has been run against a real LiteLLM proxy or provider. The provider model in config/litellm.yaml is a placeholder for whichever provider is approved.
  • The README does not document this yet.
  • The Playwright suite was not re-run for this PR locally; CI runs it.

Test plan

  • 24 new tests: dispatch, boundary refusal for private and restricted data, fallback on engine failure and out-of-memory, no fallback for ineligible requests, per-target circuits, manifest and proxy-config contracts
  • ruff format, ruff check ., mypy clean
  • pytest tests/unit tests/integration: 338 passed, coverage 98%

🤖 Generated with Claude Code

…fallback

The design names LiteLLM as the layer that normalizes external endpoints
and centralizes fallback. There was no external provider path: a decision
for the approved external model was sent to the local engine under that
name.

External decisions now go to a LiteLLM proxy, which holds the provider
credentials so they never enter the gateway. The privacy rule is enforced
a second time at the dispatch boundary, so a routing defect cannot send
private or restricted data out. Each target has its own circuit.

When the local engine fails, a request already entitled to external
routing falls back to it; the switch is attributed in the route reason
and counted. Enabling external fallback against a real engine without a
proxy address is refused at start-up.

The proxy is the only workload allowed to reach the internet, and only
the gateway may call it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions
github-actions Bot merged commit a2856fa into main Oct 3, 2026
6 checks passed
@github-actions
github-actions Bot deleted the feat/22-external-provider branch October 3, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant