feat(router): tag routing denylist support via ! prefix - #31728
Conversation
Adds `!` prefix negation to tag-based routing so callers can exclude deployments by exact tag value without enumerating every allowed alternative. `!provider:anthropic` removes all deployments tagged exactly `provider:anthropic` before routing, and positive and negation tags compose. Matching is exact literal membership (frozenset intersection), so there is no regex or ReDoS surface for client-supplied tags. Ban-only requests that carry only negation tags stay within the default pool, mirroring untagged-request semantics so callers can't use negation to escape it. Fallback chains keep working because get_deployments_for_tag runs on each routing hop Copy of #31680; implementation credit to @deepanshululla
|
Generated by Claude Code |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR adds
Confidence Score: 5/5Safe to merge — the change is self-contained to tag routing, exact-literal matching prevents any new injection surface, and the full positive-tag and regex paths are untouched for callers who do not send negation tags. All new code paths are exercised by 18 focused tests covering edge cases (bare !, partial-string non-match, ban-only pool confinement, fallback chain exhaustion, tag_regex interaction). Untagged deployments are correctly preserved through the exclusion filter. The ban-only path correctly confines to the default pool and raises when the pool is exhausted. No pre-existing behavior is altered for requests that contain no !-prefixed tags. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/router_strategy/tag_based_routing.py | Adds _split_tags, _exclude_deployments, _require_candidates, and _ban_only_base_pool helpers; integrates negation-tag (! prefix) logic into get_deployments_for_tag with ban-only semantics and default-pool confinement. Logic is correct and pre-existing behavior is preserved. |
| tests/test_litellm/router_strategy/test_router_tag_routing.py | Adds 18 unit and integration tests covering _split_tags edge cases, single/multiple negation, mixed positive+negation, literal-only semantics, ban-only pool confinement, fallback chain exhaustion, and tag_regex interaction. Existing tests are lightly cleaned up (print removal, unused imports) without weakening assertions. |
Reviews (3): Last reviewed commit: "feat(router): tag routing denylist suppo..." | Re-trigger Greptile
|
On the summary's "Ban-only negation tags still reject instead of routing to non-denied deployments": this is not reproducible on the head commit. Using the exact scenario described (plain provider tags, no Single negation selects openai, double selects vertex, and only the deny-all case raises, which is the contract the summary says it expected. These are asserted by
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8894dbf. Configure here.
|
Generated by Claude Code |
Adds `!` prefix negation to tag-based routing so callers can exclude deployments by exact tag value without enumerating every allowed alternative. `!provider:anthropic` removes all deployments tagged exactly `provider:anthropic` before routing, and positive and negation tags compose. Matching is exact literal membership (frozenset intersection), so there is no regex or ReDoS surface for client-supplied tags. Ban-only requests that carry only negation tags stay within the default pool, mirroring untagged-request semantics so callers can't use negation to escape it. Fallback chains keep working because get_deployments_for_tag runs on each routing hop Copy of BerriAI#31680; implementation credit to @deepanshululla Co-authored-by: deepanshululla <15312873+deepanshululla@users.noreply.github.com>
Relevant issues
Fixes #31676
Copy of #31680; the implementation is by @deepanshululla. This branch re-pushes that change to a
litellm_-prefixed branch on the main repo so the full CircleCI pipeline runs against it. All credit for the feature goes to the original authorLinear ticket
N/A
Pre-Submission checklist
@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Run the proxy with a config that tags deployments by provider, then send requests with negation tags.
mock_responseis used so the proof exercises the routing decision (which deployment is selected) without an upstream call, since the selectedmodel_idis what this feature changesExclude Anthropic; the remaining pool (openai + vertex) is used:
Exclude both anthropic and openai with two negation tags; only vertex remains:
Exclude every provider, the pool is empty and the request errors:
Type
New Feature
Changes
Adds
!prefix negation tox-litellm-tagsandmetadata.tagsfor tag-based routing. Callers can exclude deployments by exact tag value without having to enumerate every allowed alternativeIn
litellm/router_strategy/tag_based_routing.py:_split_tags(tags)partitions the request tag list into positive tags (unchanged, fed to the existing inclusion logic) and excluded literals (the string after a!prefix). A bare!with no suffix is ignored_exclude_deployments(deployments, excluded_set)filters out deployments whosetagsintersect the excluded set. It runs before tag or regex matching so negation always applies first_ban_only_base_pool(deployments)returns the default-tagged pool when one exists, otherwise all deployments. The ban-only path uses it to mirror untagged-request semantics so callers can't use negation tags to escape the default poolget_deployments_for_tagbuilds afrozensetof excluded literals, excludes before any inclusion logic, and for ban-only requests routes within the default pool. It raisesno_deployments_with_tag_routingwhen the exclusion filter empties the poolMatching is exact literal membership via frozenset intersection, so there is no regex engine for client-supplied tags and no ReDoS surface.
!provider:(anthropic|openai)only excludes a deployment tagged exactlyprovider:(anthropic|openai); to exclude multiple providers, send separate tags["!provider:anthropic", "!provider:openai"]. Operator-configuredtag_regexin deployment config is unaffected, and negation exclusion runs before regex matching so a deployment whose plain tag is negated drops out before itstag_regexis consideredFallback chains work without extra wiring because
get_deployments_for_tagruns on each routing hop, so a banned primary group triggers the existing fallback mechanismThe change adds 18 unit and integration tests covering
_split_tagsedge cases, single and multiple literal negation tags, mixed positive plus negation, literal-only semantics (no partial match, no regex interpretation), ban-only exhausting all candidates, ban-only confined to the default pool, untagged deployments being kept, the fallback chain firing when the primary is banned, the full chain exhausting with an error, and twotag_regexplus negation interaction cases