From 7c01421e5652bbf82b55cb4444f2f5e29f0f7132 Mon Sep 17 00:00:00 2001 From: yucheng Date: Thu, 5 Nov 2026 01:41:11 -0800 Subject: [PATCH] fix(proxy): keep a plain key's object-form body guardrails where the client sent them --- .../pass_through_endpoints.py | 6 +++--- .../test_pass_through_endpoints.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 69105bcbb71..281d2e8d3e1 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -968,7 +968,7 @@ async def _inherit_auth_guardrails( runs_guardrails: Final = bool(entries) or bool((as_str_mapping(metadata) or {}).get("_guardrail_pipelines")) if entries and isinstance(metadata, dict): metadata["guardrails"] = entries - elif isinstance(merged, list) and isinstance(metadata, dict): + elif isinstance(merged, (list, dict)) and isinstance(metadata, dict): _restore_unmerged_guardrail_fields( parsed_body, cast("dict[str, object]", metadata), # cast-ok: the request body's JSON metadata object @@ -1025,8 +1025,8 @@ def _restore_unmerged_guardrail_fields( client_body_guardrails: Mapping[str, object], client_metadata_guardrails: Mapping[str, object], ) -> None: - """Put back the client's ``guardrails`` and ``metadata.guardrails`` when the merge found no guardrail to run, so - the body keeps main's shape""" + """Put back the client's ``guardrails`` and ``metadata.guardrails`` when the merge produced no list of guardrails, + such as an empty list or the client's object form on a plain key, so the body keeps main's shape""" metadata.pop("guardrails", None) metadata.update(client_metadata_guardrails) parsed_body.update(client_body_guardrails) diff --git a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py index 0f3428ccffd..d1782b1fe21 100644 --- a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -13380,10 +13380,21 @@ class TestInheritAuthGuardrails: {"guardrails": [], "metadata": {"user": "u"}}, {"guardrails": [], "metadata": {"guardrails": []}}, {"metadata": {"guardrails": []}}, + {"guardrails": {"g": {}}}, + {"guardrails": {"g": {}}, "metadata": {"headers": 5}}, + {"guardrails": {"g": {}}, "metadata": {"guardrails": {"h": {}}}}, + ], + ids=[ + "guardrails", + "with-metadata", + "with-metadata-guardrails", + "metadata-guardrails-only", + "object-form", + "object-form-with-metadata", + "object-form-with-metadata-object-form", ], - ids=["guardrails", "with-metadata", "with-metadata-guardrails", "metadata-guardrails-only"], ) - async def test_an_empty_guardrails_list_on_a_plain_key_keeps_the_body_as_sent(self, extra: dict[str, object]): + async def test_guardrails_that_merge_to_no_list_on_a_plain_key_keep_the_body_as_sent(self, extra: dict[str, object]): body: Final[dict[str, object]] = self._chat_body(**extra) assert await _inherit_auth_guardrails(body, UserAPIKeyAuth(api_key="sk-test"), None, True) is None