From aec84020e5a2882e6ad6515354e55e25ccd8b6fa Mon Sep 17 00:00:00 2001 From: Graham Neubig <398875+neubig@users.noreply.github.com> Date: Thu, 21 May 2026 08:54:53 -0400 Subject: [PATCH] Normalize Lemonade runtime model metadata --- litellm/llms/lemonade/chat/transformation.py | 24 +++++++++++++-- .../llms/lemonade/test_lemonade.py | 30 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/litellm/llms/lemonade/chat/transformation.py b/litellm/llms/lemonade/chat/transformation.py index ff8069395b0..1576dbd0a16 100644 --- a/litellm/llms/lemonade/chat/transformation.py +++ b/litellm/llms/lemonade/chat/transformation.py @@ -121,16 +121,31 @@ class LemonadeChatConfig(OpenAILikeChatConfig): return parsed return None + @staticmethod + def _get_provider_specific_entry(model_info: dict) -> dict: + provider_specific_entry = model_info.get("provider_specific_entry") + if not isinstance(provider_specific_entry, dict): + provider_specific_entry = {} + else: + provider_specific_entry = provider_specific_entry.copy() + + for key in ("recipe_options", "context_window", "max_context_window"): + if key in model_info: + provider_specific_entry[key] = model_info[key] + + return provider_specific_entry + def _get_context_window(self, model_info: dict) -> Optional[int]: - recipe_options = model_info.get("recipe_options") + provider_specific_entry = self._get_provider_specific_entry(model_info) + recipe_options = provider_specific_entry.get("recipe_options") if not isinstance(recipe_options, dict): recipe_options = {} for value in ( recipe_options.get("ctx_size"), model_info.get("max_input_tokens"), - model_info.get("context_window"), - model_info.get("max_context_window"), + provider_specific_entry.get("context_window"), + provider_specific_entry.get("max_context_window"), ): parsed = self._get_positive_int(value) if parsed is not None: @@ -172,6 +187,7 @@ class LemonadeChatConfig(OpenAILikeChatConfig): max_input_tokens = self._get_context_window(model_info) max_output_tokens = self._get_positive_int(model_info.get("max_output_tokens")) max_tokens = self._get_positive_int(model_info.get("max_tokens")) + provider_specific_entry = self._get_provider_specific_entry(model_info) model_info_response = self._get_default_model_info(model) model_info_response.update( @@ -181,6 +197,8 @@ class LemonadeChatConfig(OpenAILikeChatConfig): "max_output_tokens": max_output_tokens, } ) + if provider_specific_entry: + model_info_response["provider_specific_entry"] = provider_specific_entry return model_info_response def _get_openai_compatible_provider_info( diff --git a/tests/test_litellm/llms/lemonade/test_lemonade.py b/tests/test_litellm/llms/lemonade/test_lemonade.py index 491e4aac18d..d6aae6e53f9 100644 --- a/tests/test_litellm/llms/lemonade/test_lemonade.py +++ b/tests/test_litellm/llms/lemonade/test_lemonade.py @@ -109,6 +109,10 @@ def test_get_model_info_uses_loaded_context_size(): assert model_info["key"] == "lemonade/Qwen3.6-35B-A3B-GGUF" assert model_info["litellm_provider"] == "lemonade" assert model_info["max_input_tokens"] == 65536 + assert model_info["provider_specific_entry"] == { + "recipe_options": {"ctx_size": 65536}, + "max_context_window": 262144, + } assert "supports_function_calling" not in model_info assert "supports_response_schema" not in model_info assert "supports_tool_choice" not in model_info @@ -140,6 +144,32 @@ def test_get_model_info_falls_back_when_server_unavailable(): assert "supports_tool_choice" not in model_info +def test_get_model_info_reads_context_from_provider_specific_entry(): + """Test that Lemonade model info uses provider-specific runtime metadata.""" + config = LemonadeChatConfig() + response = MagicMock() + response.status_code = 200 + response.json.return_value = { + "id": "Qwen3.6-35B-A3B-GGUF", + "provider_specific_entry": { + "recipe_options": {"ctx_size": "32768"}, + "max_context_window": 262144, + }, + } + + with patch.object(litellm.module_level_client, "get", return_value=response): + model_info = config.get_model_info( + model="lemonade/Qwen3.6-35B-A3B-GGUF", + api_base="http://lemonade.test/v1", + ) + + assert model_info["max_input_tokens"] == 32768 + assert model_info["provider_specific_entry"] == { + "recipe_options": {"ctx_size": "32768"}, + "max_context_window": 262144, + } + + def test_get_model_info_sends_lemonade_api_key(monkeypatch): """Test that Lemonade model info uses auth for authenticated servers.""" monkeypatch.setenv("LEMONADE_API_KEY", "test-key")