fix(openai-like): drop unsupported Xiaomi output config

This commit is contained in:
pragnyanramtha 2026-05-16 17:45:36 +00:00
parent 57e5e4a3b7
commit 805d3cd45e
4 changed files with 159 additions and 1 deletions

View file

@ -10,6 +10,7 @@ from typing import Any, Callable, Optional, Union
import httpx
import litellm
from litellm._logging import verbose_logger
from litellm import LlmProviders
from litellm.llms.bedrock.chat.invoke_handler import MockResponseIterator
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
@ -20,6 +21,7 @@ from litellm.types.utils import CustomStreamingDecoder, ModelResponse
from litellm.utils import CustomStreamWrapper, ProviderConfigManager
from ..common_utils import OpenAILikeBase, OpenAILikeError
from ..json_loader import JSONProviderRegistry
from .transformation import OpenAILikeChatConfig
@ -112,6 +114,25 @@ class OpenAILikeChatHandler(OpenAILikeBase):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@staticmethod
def _drop_provider_unsupported_params(
custom_llm_provider: str, optional_params: dict, extra_body: dict
) -> None:
provider = JSONProviderRegistry.get(custom_llm_provider)
if provider is None:
return
for param in provider.unsupported_params:
removed_optional_param = optional_params.pop(param, None)
removed_extra_body_param = extra_body.pop(param, None)
if (
removed_optional_param is not None
or removed_extra_body_param is not None
):
verbose_logger.debug(
f"Dropping unsupported param '{param}' for provider '{custom_llm_provider}'"
)
async def acompletion_stream_function(
self,
model: str,
@ -254,9 +275,14 @@ class OpenAILikeChatHandler(OpenAILikeBase):
)
stream: bool = optional_params.pop("stream", None) or False
extra_body = optional_params.pop("extra_body", {})
extra_body = optional_params.pop("extra_body", {}) or {}
json_mode = optional_params.pop("json_mode", None)
optional_params.pop("max_retries", None)
self._drop_provider_unsupported_params(
custom_llm_provider=custom_llm_provider,
optional_params=optional_params,
extra_body=extra_body,
)
if not fake_stream:
optional_params["stream"] = stream

View file

@ -21,6 +21,7 @@ class SimpleProviderConfig:
self.param_mappings = data.get("param_mappings", {})
self.constraints = data.get("constraints", {})
self.special_handling = data.get("special_handling", {})
self.unsupported_params = data.get("unsupported_params", [])
self.supported_endpoints = data.get("supported_endpoints", [])

View file

@ -22,6 +22,9 @@
"xiaomi_mimo": {
"base_url": "https://api.xiaomimimo.com/v1",
"api_key_env": "XIAOMI_MIMO_API_KEY",
"unsupported_params": [
"output_config"
],
"param_mappings": {
"max_completion_tokens": "max_tokens"
}

View file

@ -4,6 +4,7 @@ Related to issue #18794
"""
import os
import json
import sys
from unittest.mock import MagicMock, patch
@ -46,6 +47,7 @@ class TestXiaomiMiMoProviderConfig:
assert xiaomi_mimo.base_url == "https://api.xiaomimimo.com/v1"
assert xiaomi_mimo.api_key_env == "XIAOMI_MIMO_API_KEY"
assert xiaomi_mimo.param_mappings.get("max_completion_tokens") == "max_tokens"
assert "output_config" in xiaomi_mimo.unsupported_params
def test_xiaomi_mimo_provider_resolution(self):
"""Test that provider resolution finds xiaomi_mimo"""
@ -83,6 +85,132 @@ class TestXiaomiMiMoProviderConfig:
assert len(router.model_list) == 1
assert router.model_list[0]["model_name"] == "mimo-v2-flash"
def test_xiaomi_mimo_drops_output_config_from_request_body(self):
"""Xiaomi MiMo rejects Claude-only output_config; do not forward it."""
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.llms.openai_like.chat.handler import OpenAILikeChatHandler
from litellm.types.utils import ModelResponse
client = HTTPHandler()
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.headers = {"content-type": "application/json"}
mock_response.json.return_value = {
"id": "chatcmpl-test",
"object": "chat.completion",
"created": 0,
"model": "mimo-v2-flash",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
}
mock_response.raise_for_status.return_value = None
logging_obj = MagicMock()
logging_obj.model_call_details = {}
with patch.object(client, "post", return_value=mock_response) as mock_post:
OpenAILikeChatHandler().completion(
model="mimo-v2-flash",
messages=[{"role": "user", "content": "hi"}],
api_base="https://api.xiaomimimo.com/v1",
custom_llm_provider="xiaomi_mimo",
custom_prompt_dict={},
model_response=ModelResponse(),
print_verbose=lambda *args, **kwargs: None,
encoding=None,
api_key="test-key",
logging_obj=logging_obj,
optional_params={
"output_config": {"effort": "medium"},
"extra_body": {"output_config": {"effort": "medium"}},
},
client=client,
)
request_body = json.loads(mock_post.call_args.kwargs["data"])
assert "output_config" not in request_body
assert request_body["model"] == "mimo-v2-flash"
assert request_body["messages"] == [{"role": "user", "content": "hi"}]
def test_xiaomi_mimo_handles_none_extra_body(self):
"""An explicit extra_body=None should not break unsupported-param filtering."""
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.llms.openai_like.chat.handler import OpenAILikeChatHandler
from litellm.types.utils import ModelResponse
client = HTTPHandler()
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.headers = {"content-type": "application/json"}
mock_response.json.return_value = {
"id": "chatcmpl-test",
"object": "chat.completion",
"created": 0,
"model": "mimo-v2-flash",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
}
mock_response.raise_for_status.return_value = None
logging_obj = MagicMock()
logging_obj.model_call_details = {}
with patch.object(client, "post", return_value=mock_response) as mock_post:
OpenAILikeChatHandler().completion(
model="mimo-v2-flash",
messages=[{"role": "user", "content": "hi"}],
api_base="https://api.xiaomimimo.com/v1",
custom_llm_provider="xiaomi_mimo",
custom_prompt_dict={},
model_response=ModelResponse(),
print_verbose=lambda *args, **kwargs: None,
encoding=None,
api_key="test-key",
logging_obj=logging_obj,
optional_params={
"extra_body": None,
"output_config": {"effort": "medium"},
},
client=client,
)
request_body = json.loads(mock_post.call_args.kwargs["data"])
assert "output_config" not in request_body
assert request_body["model"] == "mimo-v2-flash"
def test_xiaomi_mimo_logs_dropped_output_config(self):
"""Dropped provider params should be observable in debug logs."""
from litellm.llms.openai_like.chat.handler import OpenAILikeChatHandler
optional_params = {"output_config": {"effort": "medium"}}
extra_body = {}
with patch(
"litellm.llms.openai_like.chat.handler.verbose_logger.debug"
) as mock_debug:
OpenAILikeChatHandler._drop_provider_unsupported_params(
custom_llm_provider="xiaomi_mimo",
optional_params=optional_params,
extra_body=extra_body,
)
assert optional_params == {}
mock_debug.assert_called_once_with(
"Dropping unsupported param 'output_config' for provider 'xiaomi_mimo'"
)
class TestXiaomiMiMoIntegration:
"""Integration tests for Xiaomi MiMo provider"""