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.
This commit is contained in:
sarmientoF 2026-06-19 11:30:57 +09:00
parent 30f33a949b
commit 0bbb1ea13b
2 changed files with 52 additions and 0 deletions

View file

@ -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(

View file

@ -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."""