Problem
The ModelResolver (src/synthorg/providers/routing/resolver.py:64-86) raises ModelResolutionError when two providers register the same model ID. This prevents users from adding duplicate providers for the same models (e.g., two Anthropic subscriptions both offering claude-sonnet-4-20250514).
Use Case
A user has two Claude subscriptions (personal + work) and wants to add both as providers. Agents should be able to use either, with selection based on budget/quota tracking.
Current Workaround
Users must use different model aliases per provider (e.g., sonnet-personal vs sonnet-work). This is ugly and leaks provider specifics into routing rules.
Proposed Solution
- Change
ModelResolver index from dict[str, ResolvedModel] to dict[str, list[ResolvedModel]]
- Add a selection strategy (budget-aware, round-robin, or rule-based) when multiple providers serve the same model
- Integrate with the existing
SubscriptionConfig quota tracking for budget-based selection
- The routing rules and fallback chain should consider provider quota state
Scope
src/synthorg/providers/routing/resolver.py -- multi-provider index
src/synthorg/providers/routing/router.py -- selection strategy
src/synthorg/budget/ -- quota-aware provider selection
- Tests for all changes
Context
Discovered during #785 (Providers page rework). The litellm_provider field added in #785 enables multiple providers to share the same LiteLLM routing prefix, but the resolver collision prevents them from sharing model IDs.
Problem
The
ModelResolver(src/synthorg/providers/routing/resolver.py:64-86) raisesModelResolutionErrorwhen two providers register the same model ID. This prevents users from adding duplicate providers for the same models (e.g., two Anthropic subscriptions both offeringclaude-sonnet-4-20250514).Use Case
A user has two Claude subscriptions (personal + work) and wants to add both as providers. Agents should be able to use either, with selection based on budget/quota tracking.
Current Workaround
Users must use different model aliases per provider (e.g.,
sonnet-personalvssonnet-work). This is ugly and leaks provider specifics into routing rules.Proposed Solution
ModelResolverindex fromdict[str, ResolvedModel]todict[str, list[ResolvedModel]]SubscriptionConfigquota tracking for budget-based selectionScope
src/synthorg/providers/routing/resolver.py-- multi-provider indexsrc/synthorg/providers/routing/router.py-- selection strategysrc/synthorg/budget/-- quota-aware provider selectionContext
Discovered during #785 (Providers page rework). The
litellm_providerfield added in #785 enables multiple providers to share the same LiteLLM routing prefix, but the resolver collision prevents them from sharing model IDs.