From 6584fd75c4ca4e1e4d7ace6d325358024b377357 Mon Sep 17 00:00:00 2001 From: IvanShang <77005282+qdivan@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:21:54 +0800 Subject: [PATCH 1/3] fix(headroom): drop stream options after CCR conversion --- .../proxy/guardrails/guardrail_hooks/headroom/headroom.py | 2 +- .../proxy/guardrails/guardrail_hooks/test_headroom.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index fa113aa4d33..d68be773457 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -890,7 +890,7 @@ class HeadroomGuardrail(CustomGuardrail): if not has_headroom_retrieve_tool(effective.get("tools")): return base_result return { # mutable-ok: the hook contract is a plain dict the router merges into the request kwargs - **effective, + **{key: value for key, value in effective.items() if key != "stream_options"}, # mutable-ok: removes stream-only options "stream": False, HEADROOM_CONVERTED_STREAM_KEY: True, } diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index d8eeb8d2b8a..8afd7b10369 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -2165,7 +2165,7 @@ async def test_pre_call_deployment_hook_converts_stream_only_for_ccr_chat_comple tools: Optional[list], expect_conversion: bool, ): - kwargs = {"model": "gpt-4o", "stream": stream, "tools": tools} + kwargs = {"model": "gpt-4o", "stream": stream, "stream_options": {"include_usage": True}, "tools": tools} result = await guardrail.async_pre_call_deployment_hook(kwargs=kwargs, call_type=call_type) @@ -2173,12 +2173,15 @@ async def test_pre_call_deployment_hook_converts_stream_only_for_ccr_chat_comple assert result is kwargs assert HEADROOM_CONVERTED_STREAM_KEY not in kwargs assert kwargs["stream"] is stream + assert kwargs["stream_options"] == {"include_usage": True} return assert result is not None assert result["stream"] is False + assert "stream_options" not in result assert result[HEADROOM_CONVERTED_STREAM_KEY] is True assert kwargs["stream"] is True + assert kwargs["stream_options"] == {"include_usage": True} @pytest.mark.asyncio @@ -2233,6 +2236,7 @@ async def test_pre_call_deployment_hook_converts_stream_after_deployment_level_c "model": "gpt-4o", "messages": [dict(m) for m in ORIGINAL_MESSAGES], "stream": True, + "stream_options": {"include_usage": True}, "guardrails": ["headroom"], "metadata": {}, } @@ -2248,6 +2252,7 @@ async def test_pre_call_deployment_hook_converts_stream_after_deployment_level_c assert result is not None assert has_headroom_retrieve_tool(result["tools"]) assert result["stream"] is False + assert "stream_options" not in result assert result[HEADROOM_CONVERTED_STREAM_KEY] is True From 66a3516c8ffcaf7335044b0977df80e182a4bb12 Mon Sep 17 00:00:00 2001 From: IvanShang <77005282+qdivan@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:57:49 +0800 Subject: [PATCH 2/3] style(headroom): format stream options conversion --- litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index d68be773457..e48c00c0437 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -890,7 +890,9 @@ class HeadroomGuardrail(CustomGuardrail): if not has_headroom_retrieve_tool(effective.get("tools")): return base_result return { # mutable-ok: the hook contract is a plain dict the router merges into the request kwargs - **{key: value for key, value in effective.items() if key != "stream_options"}, # mutable-ok: removes stream-only options + **{ + key: value for key, value in effective.items() if key != "stream_options" + }, # mutable-ok: removes stream-only options "stream": False, HEADROOM_CONVERTED_STREAM_KEY: True, } From 8ecd31d76c42c757685100aaa76be782931c60ae Mon Sep 17 00:00:00 2001 From: IvanShang <77005282+qdivan@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:20:03 +0800 Subject: [PATCH 3/3] fix(headroom): satisfy type discipline gate --- litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index e48c00c0437..4b9be35e1c0 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -890,9 +890,9 @@ class HeadroomGuardrail(CustomGuardrail): if not has_headroom_retrieve_tool(effective.get("tools")): return base_result return { # mutable-ok: the hook contract is a plain dict the router merges into the request kwargs - **{ + **{ # mutable-ok: removes stream-only options key: value for key, value in effective.items() if key != "stream_options" - }, # mutable-ok: removes stream-only options + }, "stream": False, HEADROOM_CONVERTED_STREAM_KEY: True, }