Skip to main content

Model Routes

Concepts

A model route sends requests for a set of models to an ordered list of providers. Routes match on the model only: the most specific model pattern wins, an Access Binding grant can steer its callers to a named route, and a request no route serves is refused.

AIControls splits LLM routing into three objects, each with one job:

ObjectOwnsWhere you manage it
ProviderThe vendor, endpoint, and credential. See Providers.Settings → Providers
Model routeModel patterns and an ordered list of provider members, with a strategy and failover.Settings → Providers → Routes
Access BindingWho may use which models, optionally via a named route. See Team Access.Settings → Access Bindings

How a request is routed​

For every LLM request, AIControls:

  1. Resolves the caller's grant. It finds the caller's winning Access Binding grant for the requested model. When no binding grants any model, every caller has an implicit Everyone grant. See Access Bindings steer routing.
  2. Picks the route. A grant that names a route selects that route. Otherwise the route whose model pattern most specifically matches the model wins. See Model patterns and specificity.
  3. Filters members by credential. A caller that brings its own provider credential is served only by passthrough members, unless the route falls back to its org-key members for such callers; every other caller only by org-key members. See Credential mode.
  4. Orders the members. The route's strategy picks the member a new session starts on. See Strategies.
  5. Fails over. If a member fails with a retryable error, the next member is tried. See Failover.

If no route serves the model, no grant covers it, or no member can serve the caller, the request fails closed. See Fail closed.

Model patterns and specificity​

A route's Model patterns are exact model names or globs with a single * at the start, the end, or both, such as claude-opus-4-1, claude-*, *-mini, or *gpt-4*. A * in the middle of a pattern is rejected. Matching ignores case. A route needs at least one model pattern.

The most specific matching pattern wins:

  • An exact name beats any glob.
  • Among globs, the pattern with more literal characters wins.

So claude-opus-4-1 beats claude-opus-*, which beats claude-*, which beats *. There are no priorities and no tiers: the order you create routes in doesn't matter.

Each model pattern belongs to one route. Saving a route whose pattern overlaps another enabled route's pattern at the same specificity is refused with 400, naming both routes and both patterns. For example, claude-* and *-mini-4 overlap: both have seven literal characters, and both match claude-mini-4. claude-* and claude* don't conflict, because claude-* has more literal characters and wins. Patterns inside one route may overlap. A disabled route doesn't route and isn't part of the check. To send some callers somewhere other than the most specific route, give the other route a pattern that matches the same models at a lower specificity, such as claude* next to claude-*, and name it in those callers' Access Binding grant (see Access Bindings steer routing).

A route's name shares one name space with every provider and route, including config-file entries and the routes the router adds (see Implicit routes). A route name that is taken, by a route or a provider, is refused with 409, and so is a provider whose name a route already has.

Implicit routes​

The router adds two kinds of route of its own. The Routes table lists them read-only with the tag implicit:

  • A provider's own route. A provider that is a member of no enabled route serves its own Model Patterns, or, when it has none, its family's default patterns: claude-*, anthropic*, *.anthropic.*, and anthropic/* for Anthropic, Amazon Bedrock (Claude), and Claude on Vertex; gpt-*, o1-*, o3-*, o4-*, chatgpt-*, and openai/* for OpenAI; gemini-* and google/* for Google Vertex. A provider AIControls can't tie to one family, such as Azure OpenAI or a custom endpoint, matches any model. The route is named after the provider and drops any pattern a stored route already has, so a route you create always wins its own patterns. Adding a first Anthropic or OpenAI provider therefore needs no route: its own route serves its family's models.
  • Family routes. Each Anthropic and OpenAI provider also serves its family's default patterns that no enabled route covers, in routes named anthropic-family and openai-family. Coding agents pick their own model IDs, such as Claude Code's background Haiku calls, and family routes keep those requests from failing closed when your routes name only some models. A family route never changes where an already-routed model goes.

When two providers serve the same models, put them in one route. Two providers of the same family that each serve the same patterns through their own routes tie, and requests either could serve fail with 503 and the code UPSTREAM_AMBIGUOUS.

Access Bindings steer routing​

An allow-mode Access Binding's model grant can name a route: Via route in the binding's Models section, or route on a model selector in the API. A request from one of the binding's subjects for a model the grant covers then goes to that route, instead of the most specific route, as long as the named route's model patterns match the model. For example, with a route claude (claude-*, Anthropic) and a route claude-bedrock (claude*, Amazon Bedrock), everyone reaches claude, the more specific route, and a grant of claude-* via claude-bedrock sends the platform team to Bedrock.

When several grants cover a caller and a model, one wins:

  1. Subject precedence: a grant through the caller's workload, then through its Workload Blueprint, then through a team, group, user, agent, or agent-label subject, then through Everyone.
  2. Within one level, the grant with the most specific model pattern, then a grant that names a route, then binding name, then route name. Resolution is deterministic and cached per caller until a binding changes.

Rules:

  • Implicit Everyone grant. While no enabled allow-mode binding grants any model, every caller is granted every model and routing uses the most specific route. Once any binding grants a model, a caller with no grant for the requested model is refused with MODEL_DENIED (see Fail closed).
  • Conflicts are refused on save. Saving a binding whose grant names a route is refused with 409 route_grant_conflict when another binding grants the same subject the same model pattern at the same level through a different route. The error names both bindings. Saving a workload's access that references two bindings that grant the same model through different routes is refused the same way.
  • The route must exist and serve the grant. A grant must name a route the router serves, and the route's model patterns or model aliases must match some of the grant's models; otherwise the save returns 400. While a grant names a route, a change that removes the route is refused with 409, naming the binding: deleting or disabling the route, deleting or disabling the provider whose implicit route it is, adding that provider to another route, or deleting or disabling the pool it comes from. If the route goes away another way, such as a change to the configuration file, requests the grant wins fail closed with NO_ROUTE instead of using another route. A request the grant wins for a model the route doesn't serve also fails closed with NO_ROUTE: the grant can cover models outside the route's model patterns and model aliases, and those requests don't use another route. A model that only one of the route's members aliases still uses the route the caller would get without the grant.
  • Allow mode only. Only an allow-mode binding's grant can name a route. Audit, Warn, Hold, and Deny bindings apply their mode to the models and don't steer routing.
  • Other model limits still apply. Team and personal access token model scopes, a virtual key's allowed models, and the Global Model Allowlist stay separate ceilings. The binding editor shows them read-only under Other model limits for these callers.

See Steer a team through a route for the steps.

Credential mode​

Every provider has a credential mode, shown on each route member as org key or passthrough:

ModeProviderServes
org keyHas its own API key or an organization-managed credential (AWS IAM, Google workload identity, Azure managed identity), or is a keyless local vendor such as OllamaEvery caller except one that brings its own provider credential
passthroughHas no API key and no managed credential, so it forwards the caller's own credentialCallers that bring their own credential, such as Claude Code signed in to a Claude plan, a ChatGPT sign-in, or GitHub Copilot's token

Inside a route, AIControls drops the members that can't serve the caller before it orders the rest. A caller that brings its own credential is served only by passthrough members, unless the route has the org key fallback on. Under global passthrough (Settings → Mode), the route's first configured member also serves such a caller, with the caller's credential in place of its key. A caller with a virtual key uses the virtual key's credential, so it counts as an org-key caller.

Org key fallback for callers' own credentials​

A route can let callers who bring their own credential fall back to its org-key members. Turn on Fall back to org key members for callers' own credentials on the route's Routing step (orgKeyFallbackForCallerCredentials). It's off by default and applies only when the route has both a passthrough member and an org-key member.

With the setting on, a caller who brings their own credential, such as Claude Code signed in to a Claude plan, is served like this:

  • The route's passthrough members are tried first, in member order, with the caller's credential. The strategy orders only these members.
  • When every passthrough member fails with a retryable error (a rate limit, a server error, a connection error, or a timeout) or a quota or credit error, the request moves to the route's org-key members, in member order. A rejected credential (401 or 403), a malformed request, or another non-retryable error is returned to the caller.
  • A passthrough member skipped by a policy, a data-handling constraint, or an open circuit breaker isn't a failure: when it's the route's last passthrough member, the request ends as it would with the setting off.
  • An org-key member is never tried first for such a caller. In a route with cross-provider members, only org-key members of the client's provider family are fallbacks.
  • Under global passthrough, a route's first member that goes first serves such a caller with the caller's credential, as a passthrough member does, and isn't retried with its own key. When the strategy starts on another member, it is one of the org-key fallbacks.
  • An org-key member authenticates with its own credential. The caller's credential is removed from the request, so it never reaches that member.
  • The org key is billed for these requests. The audit event records credentialFallback: true, and the Activity event detail shows a Credential row. On Health → Upstreams → Failover events, the failover carries an org key fallback tag.
  • A rate limit or quota error on the caller's own credential doesn't trip the org-key member's circuit breaker or park it.

With the setting off, such a caller is served only by passthrough members, and when those fail, the request fails.

The most specific route is chosen first and then filtered. When the filter leaves the route with no member, the request fails closed with NO_ROUTE and a message that the route has no passthrough member; it doesn't fall through to a less specific route. To serve subscription callers, such as Claude Pro or Max or ChatGPT sign-ins, add a passthrough member to the route. A member's circuit breaker counts failures on the member's own credential only (see Circuit breaker).

Fail closed​

There is no fallback route. A request that no route and grant serve is refused, and the audit log records a deny:

SituationHTTP statusError codeAudit policyMessage
No route's model patterns match the model503NO_ROUTEno-routeNames the model and the closest route and pattern, such as no route serves model "claude-sonnet-4-6": the closest route is "claude-opus" (model pattern claude-opus-*); add the model to a route's model patterns
The route has no member for the caller's credential mode503NO_ROUTEno-routeno route can serve model "…" for a caller with its own credential: route "…" has no passthrough member, or, for a caller without its own credential, no route can serve model "…" for a caller without its own credential: route "…" has no org-key member for it
The caller's winning grant names a route the router doesn't have503NO_ROUTEno-routethe Access Binding grant for model "…" names route "…", which doesn't exist or is disabled: restore the route or change the binding
The caller's winning grant names a route that doesn't serve the model503NO_ROUTEno-routethe Access Binding grant for model "…" names route "…", which doesn't serve it: add the model to the route's model patterns or change the binding
Bindings grant models, but none grants this model to the caller400MODEL_DENIEDno-grantno Access Binding grants model "…" to this caller (route "…" serves it); ask an administrator to grant the model
Access Bindings failed to load at startup and haven't loaded since400MODEL_DENIEDno-grantmodel "…" is refused because Access Bindings haven't loaded since startup; retry once they load, or ask an administrator to check the server log
Two routes tie for the model and neither matches its family503UPSTREAM_AMBIGUOUSupstream-ambiguousNames the tied routes

A no-grant denial is a 400, not a 403, so clients such as Claude Code don't prompt the user to sign in again. A route whose first member has no credential for a caller that brings none fails with 503 and no-upstream-key; see When the selected provider has no API key.

GET /v1/models lists only the models the caller could use: a route serves the model for the caller's credential mode, and an Access Binding grants it. To discover models live, AIControls forwards a caller's own credential only to passthrough providers in a route the caller's grants reach; a caller with no grant has its credential sent to none.

Strategies​

A route's Strategy picks the member each new session starts on. The other members follow in member order, Fallback only members stay last, and failover on errors or exhausted quota works the same with every strategy.

Strategystrategy.typeA new session starts on
Failover (default)failoverThe first member. Later members serve only when earlier ones fail or run out of quota.
Round robinround-robinA member chosen evenly across the members that aren't Fallback only.
Lowest costlowest-costThe member with the lowest input plus output list price per token for the model it receives: the cheapest provider for the requested model, not a cheaper model. A member with no known price goes last; ties keep member order. When no member has a price, member order is used.
Lowest latencylowest-latencyThe member with the lowest P95 latency of live traffic over a rolling window. Options: windowMinutes (default 15, from 1 to 1440), minRequests (default 20, from 1 to 1,000,000), and explorationShare (default 0.05, from 0 to 0.5). Members with fewer than minRequests requests in the window rank after the measured members, and an explorationShare of new sessions (about 5% by default) starts on one of them in turn so it gets measured; 0 turns this off. When every member that can go first is below minRequests, member order is used. Health probe latency isn't used.
WeightedweightedA member chosen at random in proportion to its weight. Each member's weight is a whole number from 0 to 1,000,000, default 1. Weight 0 is never first but stays a failover target. A weighted route needs at least one member that isn't Fallback only with a weight above 0.

A session keeps its starting member for every turn:

  • Round robin and weighted derive the member from the session ID, so a session starts on the same member on every replica and after a restart. A request without a session ID uses the next member in turn.
  • Lowest cost depends only on the price table, so a session's member doesn't change.
  • Lowest latency remembers each session's member for 24 hours on the replica that served it. A session that moves to another replica can start on a different member; its provider-family pin still holds.

Parked, circuit-broken, constraint-failing, and policy-denied members are skipped in the strategy's order. Under global passthrough, only the route's first configured member forwards the caller's own credential, and only when the session starts on it. For a caller that brings its own credential, a first member that has its own key is skipped when the session starts on another member, so it never sends its key for that caller.

The audit event of every attempt records the route's strategy and why it started on its member (routeStrategy, routeStrategyReason). An unknown strategy or option, an option value out of range, or a negative or too-large weight returns 400 on save and fails startup in the configuration file. A weight on a strategy other than Weighted is kept and ignored.

Failover​

If a member fails with one of these errors, AIControls tries the next member before failing the request:

Provider errorWhat AIControls does
Provider-side error (5xx), timeout, or connection errorTries the next member.
Rate limit (429)Tries the next member.
Exhausted quota or credit, such as Anthropic's "credit balance is too low", OpenAI's insufficient_quota, or a vendor's own signal, such as DeepSeek's 402 or Kimi's exceeded_current_quota_error (see credit and quota signals)Tries the next member, which uses its own key, and parks the exhausted member until its quota resets. On a forwarded developer credential, tries the next member only if a later member has its own credential; otherwise returns the error. Nothing is parked.
Rejected key (401 or 403)Tries the next member, which uses its own key. On a forwarded developer credential, tries the next member only if a later member has its own credential; otherwise returns the error.
Any other 4xx, such as a malformed requestReturns the error. It would fail the same way on every member.

A vendor's 429 is a rate limit unless the vendor's catalog entry says that response means exhausted credit, as Kimi, Z.ai, and xAI do.

Because every member uses its own credential, an out-of-credit or revoked key on one member doesn't stop the route. You can list a subscription key first and a pay-as-you-go key after it, for example. To spend free or prepaid vendor credit first, list those members first and the paid member last.

A streaming request fails over only before the response starts. Once the first byte reaches the client, a failure ends the stream. Failover doesn't apply to /v1/responses or to count_tokens: those requests use the route's first member. In a cross-provider route, count_tokens uses the first member of the family the session is pinned to.

Policies are evaluated against the route's first member. Before AIControls dials a later member, it re-checks the data-handling constraint (zero data retention, no prompt training, and jurisdiction) and, when a policy reads object.llm.upstream, re-evaluates the request's policies with that member's provider and data-handling fields. A member that fails either check is skipped, and the audit log records the skip. This includes the first member: with a deny on object.llm.upstream.provider == "openai", a route of an OpenAI member followed by an Anthropic member is served by the Anthropic member. A deny that applies to every member, such as a content or identity rule, still denies the request before any member is dialed. If no member is left, the request gets the policy's denial. In observe mode, a deny for a later member is recorded as a shadow decision and the member is tried. A workload access denial concerns the caller, not the member, so it ends the request with no failover.

note

The re-evaluation for a later member decides only whether the request is allowed or denied. Mutations, holds for approval, and inline confirmations from it are ignored: the first member's evaluation already applied them.

Membership rules​

Every member of a route must speak the client's wire format, or be reachable through translation:

  • Extra API keys for the same provider, such as two Anthropic keys on separate billing accounts, always work.
  • Anthropic, Amazon Bedrock (Claude models), and Claude on Google Vertex mix freely in one route. They speak the Anthropic Messages format over different transports and credentials, and AIControls translates the model ID for each member.
  • An Azure OpenAI or Azure AI Foundry member needs an explicit wire format, because a deployment can be OpenAI- or Anthropic-shaped. A member without one is excluded with a warning.
  • A ChatGPT OAuth provider serves only the OpenAI Responses API, so it can't share a route with OpenAI Chat Completions members. Whichever comes second is excluded with a warning.
  • A member of another provider family, such as an OpenAI member in an Anthropic route, is excluded with a warning unless the route allows cross-provider members. See Cross-provider routes.

The route editor shows each member's compatibility as you add it. A provider can be a member of several routes. Deleting a provider that a route uses is refused with 409; remove it from the route first.

Model aliases​

A route can declare model aliases: client-facing model names, such as fast-coder or sonnet, that route to it. Each member gets its own model for the alias, so one name can mean a Claude model on an Anthropic member, a GPT model on an OpenAI member, and a vendor model on a DeepSeek or Groq member.

  • Ahead of model patterns. A request for an alias goes to the alias's route before any route's model patterns are considered.
  • Per-member model. Each member receives its own model from the alias's wireModels, keyed by provider name. A member with no entry for the alias is left out for requests that use the alias. A member's wire model comes first, then the provider's own Model Aliases, then the built-in model IDs.
  • Explicit overrides. A patchType: Defaults policy or a virtual key that sends the request to one provider still applies the alias's model for that provider. If the route has no model for that provider, the request fails with 503 and a message naming the alias, the route, and the provider.
  • Conflicts. An alias can't contain a wildcard, can't repeat within a route, and can't equal another route's exact model pattern. Saving a route that creates such a pair returns 400.

Pricing uses the model a member actually served. Budgets and the audit log's requested model use the alias. See Route a model alias.

Cross-provider routes​

Turn on Allow cross-provider members to mix provider families in one route, such as Anthropic and OpenAI members. AIControls translates each request for the member it's sent to, so a Claude Code session can fall back to an OpenAI model when every Anthropic member is unavailable. For what translation carries and rejects, see Cross-format routing.

A session stays on one provider family:

  • A session is pinned to the family of the member that serves its first successful request. Later requests reach members of that family first.
  • When the pinned family's credit or quota runs out, the session moves to the other family. See Switching family when quota runs out.
  • When no member of the pinned family is left, the request fails with an error that names the family. Start a new session to use another family.
  • A request without a session ID reaches members of the first configured member's family, and the other family only when that family runs out of credit or quota.
  • When a new session's first request has a feature translation can't carry, such as a server tool, AIControls skips the members that would need the translation and serves the request from a member of the client's own family. The session then pins to that family.

When every member of the pinned family fails and more than one was tried, AIControls returns one error that names the family and lists each member with its failure category, for example:

every member of the "anthropic" provider family this session is pinned to failed (team-a: rate_limit, team-b: quota_exhausted)

The error uses the client's own format and carries the code UPSTREAM_POOL_EXHAUSTED. Its status is the status every attempt returned, such as 429, or 503 when the attempts differed. A member the circuit breaker skipped is listed as circuit open, skipped.

A member of another family needs its own credential, because forwarding the developer's credential to another provider would leak it; a member without one is excluded with a warning. It also needs a Model Aliases entry, on the provider or as an alias wire model, that maps the models the route serves to its own model names, such as claude-sonnet-4-6 to gpt-5. A member of another family is skipped for any model it doesn't map. Azure OpenAI and Azure AI Foundry members can't be a member of another family yet.

A pin lasts 24 hours after the session's last request. Set llmProxy.sessionPinTTLSeconds in the deployment configuration, or the Helm value of the same name, to change it. Pins are per route and shared by every replica. A patchType: Defaults policy can't move a pinned session to another family; AIControls ignores that re-route and logs a warning.

Switching family when quota runs out​

A cross-provider route switches a session to another family when its pinned family runs out of credit or quota. A Claude Code session on a Claude team plan, for example, moves to a vendor or OpenAI member when the plan limit is reached, instead of failing.

  • Only exhausted credit or quota moves a session: a quota_exhausted failure, or a parked member. A server error, rate limit, rejected key, or translation refusal never does.
  • The other family's members are tried in member order after the pinned family's members. When one serves the request, the session is pinned to that family.
  • History the new family can't read, such as Claude's signed thinking blocks, is dropped. Text and tool calls carry over.
  • A quota failure on a developer's own forwarded credential switches nothing unless a later member has its own credential.

The setting, Switch family when credit runs out (switchFamilyOnQuota), is on by default and applies only while Allow cross-provider members is on.

Credentials during failover​

Every failover member authenticates with its own credential, never the one the previous member used. A member with its own key is a separate account with its own quota and billing, so failing over to it moves the request somewhere else.

  • A passthrough member (no key and no managed credential) always forwards the caller's own credential.
  • Under global passthrough, on the Anthropic Messages path, the route's first member forwards the credential of a caller that presents its own real provider credential, even when the member has a key. A caller with a virtual key never gets this treatment: the virtual key's provider credential is used instead, and the virtual key itself is never forwarded.
  • Every member after the first authenticates with its own key, whatever the passthrough setting. This also holds when a session pin or a missing alias removes the first member: the member tried first is still a fallback.
  • An Amazon Bedrock or Google Vertex member always authenticates with its managed credential.

So a seat-based or subscription credential that hits its cap fails over to a member with its own credential, which is billed for the call, instead of reproducing the same rate limit.

Circuit breaker​

Each route member has a circuit breaker, so a failing member doesn't make every request wait for its error or timeout.

  • Closed: requests reach the member. AIControls counts consecutive retryable failures: server errors, connection errors, and rate limits on the member's own credential.
  • Open: after 5 consecutive failures, the route skips the member for 30 seconds without contacting it. The skip is recorded in the audit log.
  • Half-open: after 30 seconds, one request goes to the member as a probe. Success closes the breaker; failure opens it again.

The breaker doesn't count a failure on the caller's own forwarded credential, or a request the client cancels. A route never fails without trying: when every member's breaker is open, the members are tried anyway. Breakers are per provider and per replica, and reset on restart.

Tune it under llmProxy.circuitBreaker in the deployment configuration, or the Helm values of the same names, and restart AIControls:

KeyDefaultDescription
enabledtrueSet to false to turn the breaker off.
failureThreshold5Consecutive retryable failures that open the breaker.
cooldownSeconds30Seconds the breaker stays open before it admits a probe.

Quota parking​

When a member's own key runs out of credit or quota, the route parks that member: later requests skip it without contacting it. This lets a route spend free or prepaid credit first, such as DeepSeek credit, then Groq's free tier, then a paid Claude key, with no delay once the first member is out.

A parked member stays parked until the first of these the failure provides:

  1. The time in the response's Retry-After header. Retry-After: 0, or a date already past, means the next request probes it.
  2. The reset time the vendor reports in its error, for vendors whose catalog entry reads one, such as Z.ai's "Your limit will reset at …".
  3. 15 minutes after the failure.

A park lasts at most 24 hours, then one request probes the member. A quota failure on a developer's own forwarded credential parks nothing. The last member that could serve a request is tried even when parked. Parking is per provider and per replica, resets on restart, and is off when the circuit breaker is turned off. Each skip records a skipped-member row in the audit log, for example upstream skipped: quota_parked: upstream deepseek-credits is parked until 2026-10-12T14:05:00Z after quota_exhausted (HTTP 402).

What shows up in the audit log​

Every LLM call records the provider that served it and the route it went through (routeName; poolName carries the same value), with the attempt that served it, such as "attempt 2 of 3", and a row for every failed or skipped attempt. A confirmed failover is tagged in Security Events. Policies read the route as object.llm.upstream.route; object.llm.upstream.pool is the same value.

Fail-closed denials appear with the policy no-route or no-grant.

Checking whether a route is used​

Select a route row in Settings → Providers → Routes to see its model patterns, strategy, Granted by, members in try order, and activity. A provider's own route shows the provider's activity. Setup → Health → Upstreams shows each route with two or more providers under Failover, with its active member, healthy members, traffic, and Failovers (24h), and lists every provider with the routes it serves. The Granted by column lists the Access Bindings whose grants reach the route: grants that name it, grants that name no route and whose model pattern overlaps it, and Everyone while no binding grants any model.

Test routing previews where a request goes for a model and a caller: model → route → provider, the binding that grants it, and where it would go without a grant that names a route. See Monitor health for the Upstreams tab.

Routes from an upgrade​

AIControls converts the upstream configuration to stored routes once: on the first start of a release with model routes when providers are saved in Settings (or none is configured), or on the first route you add, edit, or delete. The conversion uses the providers and pools that route at that moment:

  • Every upstream becomes a provider of the same name. One with no API key and no managed credential gets the passthrough credential mode; every other one gets org key.
  • Every pool becomes a route of the same name with the same members, member order, Fallback only, strategy and weights, cross-provider and family settings, and model aliases. A disabled pool becomes a disabled route.
  • A provider outside every enabled route keeps its own route (see Implicit routes).
  • Routes with the same model patterns merge into one route, with members ordered by their old ranking.
  • Routing predicates — OIDC groups, namespaces, service accounts, identity labels, Identity Source — and priority are dropped. No Access Binding grant is created: until a binding grants a model, the implicit Everyone grant keeps every caller's traffic flowing. The startup log lists each dropped predicate, so you can add a binding grant where you relied on one.
  • A converted route whose name or pattern clashes with an existing route is skipped and logged.

From then on the stored routes decide routing, on every replica and without a restart.

A deployment whose upstreams come from the configuration file converts llmProxy.upstreams and llmProxy.pools the same way on every start, and each route converted from a pool shows the tag live (not stored) on the Routes tab. Add Route works there too: the first route you save stores the conversion of the file's upstreams and pools next to your route, and the stored routes decide routing from then on. Saved providers replace the configuration file's upstreams, so once a stored route uses a config-file provider, saving the first enabled provider in Settings is refused with 409, naming the route and the provider, until you remove that provider from the route or save a provider of the same name. See Model Routes reference.

llmProxy.routes and llmProxy.providers in the configuration file don't route. A llmProxy.routes entry is listed read-only on the Routes tab and reserves its name and model patterns, so a route you save can't take them, and AIControls logs a warning at startup when either is set. When upstreams and pools are converted to stored routes, a converted route whose model patterns overlap a llmProxy.routes entry is still stored, so those models keep routing, and AIControls logs a warning naming the entry. To route from the configuration file, use llmProxy.upstreams and llmProxy.pools.

See also​