litellm/litellm
Krrish Dholakia b96460608d
feat(router): resolve auto-router routing plugins from proxy YAML config (#33251)
* feat(router): resolve auto-router routing plugins from proxy YAML config

Router(plugins=[...]) was Python-SDK constructor only, so proxy/YAML users
had no way to configure it, and the merged pipeline narrowed candidates
from the outer model alias rather than the auto-router's actual tier pool,
making it a no-op for auto_router deployments.

Add complexity_router_config.plugins (dotted-path strings resolved via
get_instance_fn, the same convention litellm_settings.callbacks uses) and
run the resolved plugins against ComplexityRouter's tier pool at every
model-pick site, so a policy plugin narrows what get_model_for_tier
actually returns instead of the outer alias list. adaptive=True with
plugins set now raises at config validation instead of silently ignoring
the plugins, since the bandit selector doesn't consume narrowed pools yet.

Also fixes a latent bug in Router._generate_model_id: it json.dumps every
litellm_params dict value to build a deployment hash id, which crashed
once a live plugin object could land inside complexity_router_config.

* fix(router): use stable class name, not object repr, in model-id json fallback

json.dumps(v, default=str) on a litellm_params dict containing a live
RoutingPlugin instance fell back to object.__repr__'s default
<module.Class object at 0x...>, embedding the instance's memory address.
_generate_model_id's hash (and therefore the deployment id) changed on
every process restart/hot-reload for any deployment with
complexity_router_config.plugins configured, defeating the function's own
"consistently generate the same id" contract and orphaning anything keyed
on that id across restarts (e.g. Redis-backed per-deployment state).

Use the plugin's fully-qualified class name instead, which is stable
across restarts.

* test(router): cover _json_default_stable_id for router_code_coverage gate

router_code_coverage.py's AST scanner requires every router.py function be
called by name somewhere in tests/, and flagged the new
_json_default_stable_id helper from the previous commit.

* fix(router): close two routing-plugin policy-bypass gaps flagged by Veria AI

Session-affinity pin shortcut: async_pre_routing_hook returned a session's
first-turn pinned model on every later turn without ever re-running it
through the plugin pipeline, so a policy plugin (e.g. a budget cap crossed
mid-session) was only enforced on turn one. Now the pin shortcut is
disabled whenever plugins are configured, so every turn re-runs
_classify_and_route (and therefore the plugins).

Plugin resolution validation: get_instance_fn accepts any dotted path and
returns whatever object it finds there, so a misconfigured
complexity_router_config.plugins entry passed proxy startup silently and
only surfaced as a confusing AttributeError on the first request that
reached the plugin pipeline. Extracted the resolution logic into
resolve_complexity_router_plugins() and added an isinstance(...,
RoutingPlugin) check that fails proxy startup immediately with a clear
error instead.

* fix(router): raise instead of falling back to default_model on empty plugin-narrowed tier

default_model was never checked against the configured plugins, so it
functioned as an unconditional escape hatch around whatever policy a
plugin enforces -- a tenant/budget plugin narrowing a tier to zero
candidates could still be bypassed by the fallback. Drop the fallback
entirely for this path; a plugin narrowing to zero is a policy decision,
not something to route around, matching the fail-closed behavior the
Router-level plugin pipeline already uses for the same situation.

Flagged by Veria AI on PR #33251.

* style: ruff format complexity_router.py

* style(proxy): use modern str | None instead of Optional[str] in resolve_complexity_router_plugins

* fix(router): stop default_model short-circuit from skipping plugins on no-user-message path

self.config.default_model or await self._pick_model_for_tier(...) -- Python's
`or` short-circuits on a truthy default_model, so _pick_model_for_tier (and
therefore the plugin pipeline) never ran at all for the no-user-message path
whenever default_model was configured. A tenant/budget plugin's decision was
silently bypassable this way even after the other two policy-bypass fixes,
since this call site had a different shape from the other three pick sites.

Removed the short-circuit; falls through to _pick_model_for_tier ->
get_model_for_tier, which already checks the MEDIUM tier before default_model
-- the same priority every other call site uses.

Flagged by Veria AI on PR #33251.

* fix(router): address Greptile findings on the plugin-bypass fixes

Preserve default_model-first priority in the no-user-message path when no
plugins are configured, instead of unconditionally flipping to the MEDIUM
tier -- the plugin-bypass fix must not silently change model selection for
the (much larger) population of users who don't use plugins at all. Gated
on self.config.plugins, matching the pattern already used elsewhere in
this PR, per CLAUDE.md's guidance against backwards-compat flags when a
plain conditional does the job.

Also close a gap in the plugin validation added earlier:
@runtime_checkable only checks that `run` exists as an attribute, not that
it's a coroutine function, so a synchronous `def run(self, context)`
passed isinstance(resolved_plugin, RoutingPlugin) at startup and only
failed at request time with a confusing TypeError. Added an
inspect.iscoroutinefunction check.

Both flagged by Greptile on PR #33251.
2026-07-14 21:27:14 -07:00
..
a2a_protocol fix(responses): stop scheduling sync success_handler concurrently with async_success_handler (#32239) 2026-07-07 09:13:50 -07:00
anthropic_interface style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
assistants style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
batch_completion style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
batches feat(batches): track cost for unmanaged Bedrock batches, generalize the flag (#32315) 2026-07-13 10:33:13 +05:30
caching fix(caching): pass only metadata to valkey semantic async embedding (#32295) 2026-07-07 08:52:03 +03:00
completion_extras style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
compression style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
containers style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
endpoints/speech/speech_to_completion_bridge style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
evals style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
experimental_mcp_client feat(mcp): relay upstream 401 on client-forwarded pass-through tool calls 2026-07-10 10:09:31 -07:00
files style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
fine_tuning chore(lint): zero out crash-class pyright rules and ban new type: ignore comments (#32152) 2026-07-04 16:56:12 -07:00
google_genai style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
images style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
integrations Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_websearch_responses_interception 2026-07-15 00:53:57 +00:00
interactions fix(responses): stop scheduling sync success_handler concurrently with async_success_handler (#32239) 2026-07-07 09:13:50 -07:00
litellm_core_utils feat(pricing): add gemini-omni-flash-preview with video output token pricing 2026-07-14 14:04:51 -07:00
llms Merge pull request #33315 from BerriAI/litellm_fix_empty_delta_thinking_block 2026-07-14 19:40:39 -07:00
models fix(keys): persist key_type so the UI shows correct key scope instead of "All Proxy Models" (#33115) 2026-07-13 18:08:43 -07:00
ocr test: litellm fix failing tests (#32577) 2026-07-09 13:54:45 -07:00
passthrough fix(logging): classify allm_passthrough_route as async to prevent duplicate success callbacks (#32265) 2026-07-06 15:04:05 -07:00
proxy feat(router): resolve auto-router routing plugins from proxy YAML config (#33251) 2026-07-14 21:27:14 -07:00
proxy_auth style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
rag style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
realtime_api fix(vertex): forward realtime health check params (#32550) 2026-07-08 17:30:46 -07:00
repositories fix(proxy): count only active users toward license seat limit (#31227) 2026-06-29 18:01:02 -07:00
rerank_api fix(rerank): log optional_rerank_params at debug to stop leaking request content (#32533) 2026-07-08 18:48:03 -07:00
responses fix(responses): continue MCP gateway tool turns from the final response and surface failures 2026-07-13 11:33:02 +10:00
router_strategy feat(router): resolve auto-router routing plugins from proxy YAML config (#33251) 2026-07-14 21:27:14 -07:00
router_utils feat(router): add separate ITPM/OTPM deployment rate limits (#31952) 2026-07-05 21:58:35 +05:30
rust_bridge feat(ocr): thin Rust OCR Python bridge (#31368) 2026-06-25 18:42:59 -07:00
sandbox style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
search style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
secret_managers fix(proxy): harden secret name validation for external secret manager integrations (LIT-4201) (#32092) 2026-07-08 10:36:00 -07:00
skills style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
types feat(router): resolve auto-router routing plugins from proxy YAML config (#33251) 2026-07-14 21:27:14 -07:00
vector_store_files style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
vector_stores style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
videos style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
__init__.py feat(budget): throttle keys after spend limit instead of revoking access (#31300) 2026-07-07 09:41:01 -07:00
_internal_context.py fix(proxy): harden request parameter handling 2026-04-16 01:38:12 +00:00
_lazy_imports.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
_lazy_imports_registry.py feat(tencent): add Tencent TokenHub as a provider (#31903) 2026-07-02 18:31:59 -07:00
_logging.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
_redis.py fix(redis): only use SSLConnection when ssl is truthy in connection pool kwargs (#32825) 2026-07-10 17:16:10 -07:00
_redis_credential_provider.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
_service_logger.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
_uuid.py build(deps-dev): bump black to 26.3.1 and apply formatting (#28525) 2026-05-21 17:24:18 -07:00
_version.py
anthropic_beta_headers_config.json fix(bedrock-invoke): retain clear_tool_uses_20250919 context_management edits and emit context-management-2025-06-27 beta (LIT-3393) (#32658) 2026-07-09 14:31:27 -07:00
anthropic_beta_headers_manager.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
blog_posts.json docs(blog): add WebRTC blog post link 2026-03-13 17:54:33 +05:30
budget_manager.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
constants.py feat: add Meta Model API provider and muse-spark-1.1 (day-0) (#32701) 2026-07-09 20:45:27 -07:00
cost.json
cost_calculator.py fix(proxy): bill tier-only deployments instead of $0 2026-07-11 13:47:56 -07:00
exceptions.py fix(guardrails/bedrock): honor disable_exception_on_block by raising ModifyResponseException (#32289) 2026-07-09 13:18:51 -07:00
main.py fix(completion): forward aws credential kwargs into litellm_params so the responses bridge keeps WIF auth 2026-07-11 16:10:39 -07:00
model_prices_and_context_window_backup.json Merge pull request #33274 from BerriAI/litellm_gemini_omni_flash_preview_pricing 2026-07-14 16:58:04 -07:00
policy_templates_backup.json feat(add-new-block_code_execution-guardrail): prevent agent from executing code (#22154) 2026-02-25 22:02:14 -08:00
provider_endpoints_support_backup.json chore: litellm oss staging (#30968) 2026-06-23 07:31:44 -07:00
py.typed
router.py feat(router): resolve auto-router routing plugins from proxy YAML config (#33251) 2026-07-14 21:27:14 -07:00
scheduler.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
setup_wizard.py feat(anthropic): add Claude Sonnet 5 2026-06-30 18:47:08 +00:00
timeout.py style: unify ruff format width on 120 (#31518) 2026-06-27 12:39:29 -07:00
utils.py feat(pricing): add gemini-omni-flash-preview with video output token pricing 2026-07-14 14:04:51 -07:00