mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge bb9a71c285 into e52f05566d
This commit is contained in:
commit
7adefa0ac4
4 changed files with 94 additions and 18 deletions
|
|
@ -673,6 +673,7 @@ wandb_models: Set = set(WANDB_MODELS)
|
|||
ovhcloud_models: Set = set()
|
||||
ovhcloud_embedding_models: Set = set()
|
||||
lemonade_models: Set = set()
|
||||
lm_studio_models: Set = set() # mutable-ok: dynamic-discovery-only, like lemonade_models
|
||||
docker_model_runner_models: Set = set()
|
||||
amazon_nova_models: Set = set()
|
||||
stability_models: Set = set()
|
||||
|
|
@ -1191,6 +1192,7 @@ def _build_models_by_provider() -> dict:
|
|||
"wandb": wandb_models,
|
||||
"ovhcloud": ovhcloud_models | ovhcloud_embedding_models,
|
||||
"lemonade": lemonade_models,
|
||||
"lm_studio": lm_studio_models,
|
||||
"clarifai": clarifai_models,
|
||||
"amazon_nova": amazon_nova_models,
|
||||
"stability": stability_models,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,25 @@ class LMStudioChatConfig(OpenAIGPTConfig):
|
|||
) # LM Studio does not require an api key, but OpenAI client requires non-None value
|
||||
return api_base, dynamic_api_key
|
||||
|
||||
def get_models(self, api_key: str | None = None, api_base: str | None = None) -> list[str]:
|
||||
"""
|
||||
Calls LM Studio's `/v1/models` endpoint and returns the list of models,
|
||||
prefixed with "lm_studio/" (matching the OllamaModelInfo convention)
|
||||
so discovered ids are directly callable without the caller having to
|
||||
add the provider prefix themselves.
|
||||
|
||||
Reuses the same api_base/api_key resolution as chat completions
|
||||
(LM_STUDIO_API_BASE / LM_STUDIO_API_KEY env vars, "fake-api-key"
|
||||
fallback) instead of OpenAIGPTConfig's default of
|
||||
https://api.openai.com, which would be wrong for a local/self-hosted
|
||||
LM Studio server.
|
||||
"""
|
||||
api_base, api_key = self._get_openai_compatible_provider_info(api_base=api_base, api_key=api_key)
|
||||
models: Final = super().get_models(api_key=api_key, api_base=api_base)
|
||||
return [ # mutable-ok: matches OllamaModelInfo.get_models' list-of-prefixed-ids contract
|
||||
m if m.startswith("lm_studio/") else f"lm_studio/{m}" for m in models
|
||||
]
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
|
|
|
|||
|
|
@ -8664,24 +8664,31 @@ class ProviderConfigManager:
|
|||
model: str | None,
|
||||
provider: LlmProviders,
|
||||
) -> BaseLLMModelInfo | None:
|
||||
if LlmProviders.FIREWORKS_AI == provider:
|
||||
return litellm.FireworksAIConfig()
|
||||
elif LlmProviders.OPENAI == provider:
|
||||
return litellm.OpenAIGPTConfig()
|
||||
elif LlmProviders.GEMINI == provider:
|
||||
return litellm.GeminiModelInfo()
|
||||
# Providers whose BaseLLMModelInfo needs no local import and no `model`
|
||||
# arg: a single dict lookup instead of N `elif` branches keeps this
|
||||
# dispatcher's cyclomatic complexity from growing with every provider
|
||||
# added here (see the elif chain below for providers that DO need a
|
||||
# local import or the `model` arg).
|
||||
simple_provider_configs: Final[
|
||||
dict[LlmProviders, Callable[[], BaseLLMModelInfo]]
|
||||
] = { # mutable-ok: built once per call, read-only lookup table replacing 9 trivial elif branches
|
||||
LlmProviders.FIREWORKS_AI: litellm.FireworksAIConfig,
|
||||
LlmProviders.OPENAI: litellm.OpenAIGPTConfig,
|
||||
LlmProviders.GEMINI: litellm.GeminiModelInfo,
|
||||
LlmProviders.LITELLM_PROXY: litellm.LiteLLMProxyChatConfig,
|
||||
LlmProviders.TOPAZ: litellm.TopazModelInfo,
|
||||
LlmProviders.ANTHROPIC: litellm.AnthropicModelInfo,
|
||||
LlmProviders.XAI: litellm.XAIModelInfo,
|
||||
LlmProviders.LEMONADE: litellm.LemonadeChatConfig,
|
||||
LlmProviders.CLARIFAI: litellm.ClarifaiConfig,
|
||||
LlmProviders.LM_STUDIO: litellm.LMStudioChatConfig,
|
||||
}
|
||||
if provider in simple_provider_configs:
|
||||
return simple_provider_configs[provider]()
|
||||
elif LlmProviders.VERTEX_AI == provider:
|
||||
from litellm.llms.vertex_ai.common_utils import VertexAIModelInfo
|
||||
|
||||
return VertexAIModelInfo()
|
||||
elif LlmProviders.LITELLM_PROXY == provider:
|
||||
return litellm.LiteLLMProxyChatConfig()
|
||||
elif LlmProviders.TOPAZ == provider:
|
||||
return litellm.TopazModelInfo()
|
||||
elif LlmProviders.ANTHROPIC == provider:
|
||||
return litellm.AnthropicModelInfo()
|
||||
elif LlmProviders.XAI == provider:
|
||||
return litellm.XAIModelInfo()
|
||||
elif LlmProviders.OLLAMA == provider or LlmProviders.OLLAMA_CHAT == provider:
|
||||
# Dynamic model listing for Ollama server
|
||||
from litellm.llms.ollama.common_utils import OllamaModelInfo
|
||||
|
|
@ -8693,10 +8700,6 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return VLLMModelInfo()
|
||||
elif LlmProviders.LEMONADE == provider:
|
||||
return litellm.LemonadeChatConfig()
|
||||
elif LlmProviders.CLARIFAI == provider:
|
||||
return litellm.ClarifaiConfig()
|
||||
elif LlmProviders.BEDROCK == provider:
|
||||
from litellm.llms.bedrock.common_utils import BedrockModelInfo
|
||||
|
||||
|
|
|
|||
|
|
@ -2887,6 +2887,58 @@ class TestGetValidModelsWithCLI:
|
|||
assert headers.get("Authorization") == "Bearer sk-test-cli-key-123"
|
||||
|
||||
|
||||
class TestGetValidModelsLMStudio:
|
||||
"""Test get_valid_models(check_provider_endpoint=True) for lm_studio.
|
||||
|
||||
get_provider_model_info() previously had no LM_STUDIO branch, so
|
||||
discovery silently returned an empty list regardless of
|
||||
check_provider_endpoint; and lm_studio was missing from
|
||||
models_by_provider, so the proxy's wildcard-model expansion
|
||||
(get_provider_models) bailed out before ever calling get_valid_models.
|
||||
"""
|
||||
|
||||
def test_get_valid_models_lm_studio_discovery(self):
|
||||
"""Discovery hits LM Studio's own /v1/models, not OpenAI's default api_base, and prefixes results with lm_studio/."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": [
|
||||
{"id": "qwen/qwen3-a3b", "object": "model"},
|
||||
{"id": "lm_studio/already-prefixed", "object": "model"},
|
||||
]
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
litellm.module_level_client, "get", return_value=mock_response
|
||||
) as mock_get:
|
||||
result = litellm.get_valid_models(
|
||||
check_provider_endpoint=True,
|
||||
custom_llm_provider="lm_studio",
|
||||
api_key="lm-studio-key",
|
||||
api_base="http://my-lm-studio-host:1234",
|
||||
)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert result == [
|
||||
"lm_studio/qwen/qwen3-a3b",
|
||||
"lm_studio/already-prefixed",
|
||||
]
|
||||
|
||||
mock_get.assert_called_once()
|
||||
_, call_kwargs = mock_get.call_args
|
||||
|
||||
# Must hit the passed-in LM Studio host, not OpenAIGPTConfig's
|
||||
# default of https://api.openai.com
|
||||
assert call_kwargs["url"] == "http://my-lm-studio-host:1234/v1/models"
|
||||
assert call_kwargs["headers"]["Authorization"] == "Bearer lm-studio-key"
|
||||
|
||||
def test_lm_studio_in_models_by_provider(self):
|
||||
"""lm_studio must be a key in models_by_provider or the proxy's
|
||||
wildcard-model expansion (get_provider_models) bails out before
|
||||
ever reaching get_valid_models/check_provider_endpoint."""
|
||||
assert "lm_studio" in litellm.models_by_provider
|
||||
|
||||
|
||||
class TestIsCachedMessage:
|
||||
"""Test is_cached_message function for context caching detection.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue