From d251178b57a5d327fb508f96d6909a4a173f8020 Mon Sep 17 00:00:00 2001 From: sarmientoF Date: Fri, 19 Jun 2026 11:30:57 +0900 Subject: [PATCH] fix(utils): preserve stream_options for providers that omit it from supported params Providers whose get_supported_openai_params override hardcodes a list without stream_options (fireworks_ai, deepinfra, mistral, perplexity, gemini, xai and ~160 other chat configs) had stream_options silently dropped in map_openai_params. The validation path already force-keeps it (_check_valid_arg special-cases stream_options/stream/user), but the mapping path never did, so strict providers that gate the trailing usage chunk on stream_options.include_usage stopped reporting cached/prompt token usage. Re-inject stream_options after mapping, mirroring the existing validation special-case. Explicit drops via additional_drop_params are still honored because the param is removed from non_default_params upstream. --- litellm/utils.py | 5 ++++ tests/test_litellm/test_utils.py | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/litellm/utils.py b/litellm/utils.py index c9001e7d906..93120dcef41 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -4935,6 +4935,11 @@ def get_optional_params( openai_params=list(DEFAULT_CHAT_COMPLETION_PARAM_VALUES.keys()), additional_drop_params=additional_drop_params, ) + if ( + "stream_options" not in optional_params + and non_default_params.get("stream_options") is not None + ): + optional_params["stream_options"] = non_default_params["stream_options"] print_verbose(f"Final returned optional params: {optional_params}") optional_params = _apply_openai_param_overrides( optional_params=optional_params, diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index 44e0b55ee3b..83b7ee29d6f 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -3866,6 +3866,53 @@ class TestDropParamsWithPromptCacheKey: assert result.get("temperature") == 0.7 +class TestStreamOptionsPreserved: + """ + stream_options must survive get_optional_params for every provider, even ones + whose get_supported_openai_params override omits it (fireworks_ai, deepinfra, + mistral, perplexity, gemini, ...). Before this fix map_openai_params silently + dropped it, so providers that gate the trailing usage chunk on + stream_options.include_usage never reported cached/prompt token usage. + """ + + @pytest.mark.parametrize( + "model, custom_llm_provider", + [ + ("accounts/fireworks/models/llama-v3p1-8b-instruct", "fireworks_ai"), + ("deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct", "deepinfra"), + ("mistral/mistral-small-latest", "mistral"), + ("perplexity/sonar", "perplexity"), + ("gemini/gemini-2.5-flash", "gemini"), + ("xai/grok-4", "xai"), + ("gpt-4o", "openai"), + ], + ) + def test_stream_options_kept(self, model, custom_llm_provider): + from litellm.utils import get_optional_params + + result = get_optional_params( + model=model, + custom_llm_provider=custom_llm_provider, + stream=True, + stream_options={"include_usage": True}, + ) + + assert result.get("stream_options") == {"include_usage": True} + + def test_explicit_drop_still_respected(self): + from litellm.utils import get_optional_params + + result = get_optional_params( + model="accounts/fireworks/models/llama-v3p1-8b-instruct", + custom_llm_provider="fireworks_ai", + stream=True, + stream_options={"include_usage": True}, + additional_drop_params=["stream_options"], + ) + + assert "stream_options" not in result + + class TestGetOptionalParamsDeepSeek: """Tests that deepseek provider uses DeepSeekChatConfig for parameter mapping."""