diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 303cf7284cc..25e3aae8639 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4049,7 +4049,7 @@ def resolve_complexity_router_plugins( classifier_plugin_path: Final = complexity_router_config.get("classifier_plugin") if isinstance(classifier_plugin_path, str): resolved_classifier: Final = resolve_classifier_plugin( - plugin_path=classifier_plugin_path, + plugin_reference=classifier_plugin_path, config_file_path=config_file_path, source_label=f"complexity_router_config.classifier_plugin on model {model_name!r}", ) @@ -4079,7 +4079,7 @@ def pin_complexity_router_model_id(model: dict) -> None: # mutable-ok: out-para def resolve_classifier_plugin( - plugin_path: str, + plugin_reference: str, config_file_path: str | None, source_label: str, ) -> ClassifierPlugin: @@ -4089,15 +4089,15 @@ def resolve_classifier_plugin( sync `def classify` passes the runtime_checkable isinstance and would only fail on the first classified request, so reject it here where the error names the config key. """ - registered: Final = litellm.classifier_plugin_registry.get(plugin_path) + registered: Final = litellm.classifier_plugin_registry.get(plugin_reference) if registered is not None: return registered - resolved: Final = get_instance_fn(value=plugin_path, config_file_path=config_file_path) + resolved: Final = get_instance_fn(value=plugin_reference, config_file_path=config_file_path) if not isinstance(resolved, ClassifierPlugin) or not inspect.iscoroutinefunction( getattr(resolved, "classify", None) ): raise ValueError( - f"{source_label} entry {plugin_path!r} resolved to {resolved!r}, which does not " + f"{source_label} entry {plugin_reference!r} resolved to {resolved!r}, which does not " "implement the ClassifierPlugin interface (an async `classify(context)` method). Fix " "the referenced module before starting the proxy." ) @@ -5323,7 +5323,7 @@ class ProxyConfig: ( str(plugin_name), resolve_classifier_plugin( - plugin_path=plugin_path, + plugin_reference=plugin_path, config_file_path=config_file_path, source_label=f"classifier_plugins.{plugin_name}", ), @@ -5332,10 +5332,10 @@ class ProxyConfig: ) # Replace, never merge: a reload that drops a name, empties the block, or removes it # entirely must evict the stale entries, or a deleted plugin stays selectable until - # the next restart. Resolution runs before the clear, so a module broken at reload - # time keeps the old registry intact. - litellm.classifier_plugin_registry.clear() - litellm.classifier_plugin_registry.update(resolved_classifier_entries) + # the next restart. Rebinding swaps the mapping atomically, so an in-flight request + # validating mid-reload sees the old set or the new one, never an empty window, and + # a module broken at reload time keeps the old registry intact. + litellm.classifier_plugin_registry = dict(resolved_classifier_entries) # mutable-ok: the registry global's type ## MODEL LIST model_list: Final = config.get("model_list", None) diff --git a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py index 84929c0ca2a..94b1c0fa1c0 100644 --- a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py +++ b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py @@ -251,7 +251,7 @@ def test_resolve_classifier_plugin_prefers_the_registry_over_dotted_import(monke instance = _Classifier() monkeypatch.setitem(litellm.classifier_plugin_registry, "tier-by-team", instance) resolved = resolve_classifier_plugin( - plugin_path="tier-by-team", config_file_path=None, source_label="classifier_plugins.tier-by-team" + plugin_reference="tier-by-team", config_file_path=None, source_label="classifier_plugins.tier-by-team" ) assert resolved is instance @@ -280,7 +280,6 @@ def test_classifier_plugins_config_key_replaces_the_registry_on_reload(monkeypat " stale-name: reg_classifier.instance\n" ) monkeypatch.setattr(litellm, "classifier_plugin_registry", {}, raising=True) - asyncio.get_event_loop_policy() proxy_config = ProxyConfig() asyncio.run(proxy_config.load_config(router=None, config_file_path=str(config_path))) assert set(litellm.classifier_plugin_registry) == {"stale-name"}