mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(guardrails): resolve guardrail pipelines from the canonical metadata bucket
Policy-resolved pipelines are stored in litellm_metadata on routes like /v1/messages, but the pre_call reader fell back to the caller-supplied metadata field first, so a request that sends its own top-level metadata (Claude Code sends metadata.user_id) skipped every pipeline-managed guardrail. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
6704a105ee
commit
f86318c4c9
2 changed files with 44 additions and 3 deletions
|
|
@ -88,7 +88,10 @@ from litellm.integrations.custom_logger import CustomLogger
|
|||
from litellm.integrations.prometheus import PrometheusLogger
|
||||
from litellm.integrations.SlackAlerting.slack_alerting import SlackAlerting
|
||||
from litellm.integrations.SlackAlerting.utils import _add_langfuse_trace_id_to_alert
|
||||
from litellm.litellm_core_utils.core_helpers import coerce_token_limit
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
coerce_token_limit,
|
||||
get_metadata_variable_name_from_kwargs,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
|
||||
|
|
@ -1248,7 +1251,7 @@ class ProxyLogging:
|
|||
|
||||
Returns the (possibly modified) data dict.
|
||||
"""
|
||||
metadata: Final = data.get("metadata", data.get("litellm_metadata", {})) or {}
|
||||
metadata: Final = data.get(get_metadata_variable_name_from_kwargs(data)) or {}
|
||||
pipelines: Final = metadata.get("_guardrail_pipelines")
|
||||
if not pipelines:
|
||||
return data
|
||||
|
|
@ -1400,7 +1403,7 @@ class ProxyLogging:
|
|||
)
|
||||
|
||||
# Get pipeline-managed guardrails to skip in normal loop
|
||||
metadata: Final = data.get("metadata", data.get("litellm_metadata", {})) or {}
|
||||
metadata: Final = data.get(get_metadata_variable_name_from_kwargs(data)) or {}
|
||||
pipeline_managed: Final[set] = metadata.get("_pipeline_managed_guardrails", set())
|
||||
|
||||
caps: Final = ProxyLogging._callback_capabilities()
|
||||
|
|
|
|||
|
|
@ -350,6 +350,44 @@ async def test_maybe_execute_pipelines_skips_pipelines_with_other_mode(proxy_log
|
|||
assert out is data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_execute_pipelines_reads_litellm_metadata_when_caller_sends_own_metadata(
|
||||
proxy_logging, make_user_api_key_auth, monkeypatch
|
||||
):
|
||||
"""On /v1/messages the proxy stores policy state in ``litellm_metadata``, while the
|
||||
caller's provider-facing ``metadata`` (Claude Code sends ``metadata.user_id``) stays
|
||||
untouched. The pipeline must still run."""
|
||||
pipeline = MagicMock()
|
||||
pipeline.mode = "pre_call"
|
||||
pipeline.steps = []
|
||||
fake_result = MagicMock()
|
||||
fake_result.terminal_action = "allow"
|
||||
fake_result.modified_data = None
|
||||
fake_result.step_results = []
|
||||
data = {
|
||||
"metadata": {"user_id": "user_abc"},
|
||||
"litellm_metadata": {"_guardrail_pipelines": [("policy-1", pipeline)]},
|
||||
"messages": [],
|
||||
"model": "m",
|
||||
}
|
||||
executed = MagicMock(return_value=fake_result)
|
||||
|
||||
async def fake_execute_steps(**kwargs):
|
||||
return executed(**kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.policy_engine.pipeline_executor.PipelineExecutor.execute_steps",
|
||||
fake_execute_steps,
|
||||
)
|
||||
await proxy_logging._maybe_execute_pipelines(
|
||||
data=data,
|
||||
user_api_key_dict=make_user_api_key_auth(),
|
||||
call_type="anthropic_messages",
|
||||
event_hook="pre_call",
|
||||
)
|
||||
executed.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_execute_pipelines_blocks_on_block_terminal_action_raises(
|
||||
proxy_logging, make_user_api_key_auth, monkeypatch
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue