fix(guardrails): keep stable config id collision-safe for same-named guardrails

A deterministic per-name id made two config guardrails sharing a guardrail_name
collide on the same id and hit the in-memory dedup early-return, so the second was
dropped. Use the stable id for the first occurrence and fall back to a unique id on
clash, preserving prior multi-registration behavior while keeping the id stable for
the normal (unique-name, initialized-once) case.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-07-30 20:12:50 +00:00
parent a4feb921fa
commit ac1c69663f
2 changed files with 39 additions and 1 deletions

View file

@ -438,7 +438,8 @@ class InMemoryGuardrailHandler:
if provided_id:
guardrail_id = provided_id
elif source == "config":
guardrail_id = get_stable_config_guardrail_id(guardrail["guardrail_name"])
stable_id = get_stable_config_guardrail_id(guardrail["guardrail_name"])
guardrail_id = stable_id if stable_id not in self.IN_MEMORY_GUARDRAILS else str(uuid.uuid4())
else:
guardrail_id = str(uuid.uuid4())
guardrail["guardrail_id"] = guardrail_id

View file

@ -405,6 +405,43 @@ def test_config_guardrail_id_is_stable_across_boots():
registry_module.guardrail_initializer_registry.pop("stable_id_test", None)
def test_same_name_config_guardrails_in_one_process_get_distinct_ids():
"""
The deterministic id must not collapse two same-named guardrails registered
in one process into one entry (that would drop the second guardrail via the
early-return dedup). The first occurrence gets the stable id; a later clash
falls back to a unique id so both stay registered.
"""
from litellm.proxy.guardrails import guardrail_registry as registry_module
def _initializer(litellm_params, guardrail):
return CustomGuardrail(
guardrail_name=guardrail["guardrail_name"],
event_hook=GuardrailEventHooks.pre_call,
default_on=False,
)
registry_module.guardrail_initializer_registry["dup_name_test"] = _initializer
try:
handler = InMemoryGuardrailHandler()
params = {"guardrail": "dup_name_test", "mode": "pre_call"}
first = handler.initialize_guardrail(
guardrail={"guardrail_name": "dup", "litellm_params": dict(params)},
source="config",
)
second = handler.initialize_guardrail(
guardrail={"guardrail_name": "dup", "litellm_params": dict(params)},
source="config",
)
assert first["guardrail_id"] == registry_module.get_stable_config_guardrail_id("dup")
assert second["guardrail_id"] != first["guardrail_id"]
assert len(handler.IN_MEMORY_GUARDRAILS) == 2
finally:
registry_module.guardrail_initializer_registry.pop("dup_name_test", None)
def test_explicit_config_guardrail_id_is_preserved():
"""An operator-provided guardrail_id must win over the derived-from-name id."""
from litellm.proxy.guardrails import guardrail_registry as registry_module