diff --git a/litellm/utils.py b/litellm/utils.py index 7732cd88cb5..4ea50ec03df 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -4790,6 +4790,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"] if _print_verbose_is_active(): print_verbose(f"Final returned optional params: {redact_credentials_in_payload(optional_params)}") optional_params = _apply_openai_param_overrides( diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index d89496a0fd0..4c9e476bdf6 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -3816,6 +3816,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."""