mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(guardrails): preserve guardrail_info when storing YAML guardrails
Address PR review feedback. InMemoryGuardrailHandler.initialize_guardrail was
constructing the parsed Guardrail without guardrail_info, so even after the
endpoint fallback fix, /guardrails/usage/{overview,detail} couldn't render
type or description for YAML-defined guardrails — both fields would silently
default to "Guardrail" / None at runtime.
Pass guardrail_info through into IN_MEMORY_GUARDRAILS, and add a regression
test that exercises the real handler (not a mock) end-to-end.
Also refactors _get_guardrail_attrs to use the new _get_guardrail_field helper
for consistency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
44923f260c
commit
d8c0674135
3 changed files with 62 additions and 11 deletions
|
|
@ -487,6 +487,7 @@ class InMemoryGuardrailHandler:
|
|||
guardrail_id=guardrail.get("guardrail_id"),
|
||||
guardrail_name=guardrail["guardrail_name"],
|
||||
litellm_params=litellm_params,
|
||||
guardrail_info=guardrail.get("guardrail_info"),
|
||||
)
|
||||
|
||||
# store references to the guardrail in memory
|
||||
|
|
|
|||
|
|
@ -137,17 +137,6 @@ def _chart_from_metrics(metrics: Any) -> List[Dict[str, Any]]:
|
|||
]
|
||||
|
||||
|
||||
def _get_guardrail_attrs(g: Any) -> tuple[Any, str]:
|
||||
"""Get (guardrail_id, display_name) from guardrail - handles Prisma model or dict."""
|
||||
gid = getattr(g, "guardrail_id", None) or (
|
||||
g.get("guardrail_id") if isinstance(g, dict) else None
|
||||
)
|
||||
name = getattr(g, "guardrail_name", None) or (
|
||||
g.get("guardrail_name") if isinstance(g, dict) else None
|
||||
)
|
||||
return gid, (name or gid or "")
|
||||
|
||||
|
||||
def _get_guardrail_field(g: Any, field: str) -> Any:
|
||||
"""Read `field` off a guardrail (Prisma row attr or dict/TypedDict key)."""
|
||||
if isinstance(g, dict):
|
||||
|
|
@ -155,6 +144,13 @@ def _get_guardrail_field(g: Any, field: str) -> Any:
|
|||
return getattr(g, field, None)
|
||||
|
||||
|
||||
def _get_guardrail_attrs(g: Any) -> tuple[Any, str]:
|
||||
"""Get (guardrail_id, display_name) from guardrail - handles Prisma model or dict."""
|
||||
gid = _get_guardrail_field(g, "guardrail_id")
|
||||
name = _get_guardrail_field(g, "guardrail_name")
|
||||
return gid, (name or gid or "")
|
||||
|
||||
|
||||
def _to_dict(value: Any) -> Dict[str, Any]:
|
||||
"""Coerce a LitellmParams / dict / None into a plain dict."""
|
||||
if isinstance(value, LitellmParams):
|
||||
|
|
|
|||
|
|
@ -304,3 +304,57 @@ async def test_usage_logs_includes_logical_name_for_yaml_guardrail(
|
|||
), f"Expected 'in' filter to include UUID + logical name, got {gid_filter}"
|
||||
assert "yaml-uuid-xyz" in gid_filter["in"]
|
||||
assert "my-yaml-pii" in gid_filter["in"]
|
||||
|
||||
|
||||
# ---- Integration: real InMemoryGuardrailHandler ----------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_usage_detail_with_real_in_memory_handler_preserves_guardrail_info(
|
||||
mock_prisma, mocker
|
||||
):
|
||||
"""
|
||||
Regression: `initialize_guardrail` must persist `guardrail_info` into
|
||||
IN_MEMORY_GUARDRAILS so that /usage/detail can render `type` and `description`
|
||||
for YAML-defined guardrails. Exercises the real handler (not a Mock).
|
||||
"""
|
||||
from litellm.proxy.guardrails.guardrail_registry import (
|
||||
IN_MEMORY_GUARDRAIL_HANDLER,
|
||||
)
|
||||
|
||||
# Bypass callback initialization; we only care about what gets stored.
|
||||
mocker.patch.object(
|
||||
IN_MEMORY_GUARDRAIL_HANDLER,
|
||||
"initialize_custom_guardrail",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
yaml_input = {
|
||||
"guardrail_id": "real-handler-yaml",
|
||||
"guardrail_name": "real-handler-pii",
|
||||
# `module.Class` form routes to initialize_custom_guardrail (mocked above)
|
||||
"litellm_params": {
|
||||
"guardrail": "my_module.MyCustomGuardrail",
|
||||
"mode": "pre_call",
|
||||
},
|
||||
"guardrail_info": {"type": "PII", "description": "Real-handler-defined"},
|
||||
}
|
||||
try:
|
||||
IN_MEMORY_GUARDRAIL_HANDLER.initialize_guardrail(guardrail=yaml_input)
|
||||
|
||||
response = await guardrails_usage_detail(
|
||||
guardrail_id="real-handler-yaml",
|
||||
start_date=START_DATE,
|
||||
end_date=END_DATE,
|
||||
user_api_key_dict=ADMIN_AUTH,
|
||||
)
|
||||
|
||||
assert response.guardrail_id == "real-handler-yaml"
|
||||
assert response.provider == "my_module.MyCustomGuardrail"
|
||||
assert response.type == "PII"
|
||||
assert response.description == "Real-handler-defined"
|
||||
finally:
|
||||
IN_MEMORY_GUARDRAIL_HANDLER.IN_MEMORY_GUARDRAILS.pop("real-handler-yaml", None)
|
||||
IN_MEMORY_GUARDRAIL_HANDLER.guardrail_id_to_custom_guardrail.pop(
|
||||
"real-handler-yaml", None
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue