mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
refactor(crusoe): simplify to JSON-based provider registration
Replace hand-written CrusoeChatConfig class and manual registrations across constants.py, __init__.py, get_llm_provider_logic.py, and _lazy_imports_registry.py with a single entry in litellm/llms/openai_like/providers.json, consistent with the recommended pattern for OpenAI-compatible providers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ab3ac4a8a0
commit
b382650ec5
10 changed files with 130 additions and 140 deletions
|
|
@ -577,7 +577,6 @@ publicai_models: Set = set()
|
|||
v0_models: Set = set()
|
||||
morph_models: Set = set()
|
||||
lambda_ai_models: Set = set()
|
||||
crusoe_models: Set = set()
|
||||
hyperbolic_models: Set = set()
|
||||
black_forest_labs_models: Set = set()
|
||||
recraft_models: Set = set()
|
||||
|
|
@ -825,8 +824,6 @@ def add_known_models(model_cost_map: Optional[Dict] = None):
|
|||
morph_models.add(key)
|
||||
elif value.get("litellm_provider") == "lambda_ai":
|
||||
lambda_ai_models.add(key)
|
||||
elif value.get("litellm_provider") == "crusoe":
|
||||
crusoe_models.add(key)
|
||||
elif value.get("litellm_provider") == "hyperbolic":
|
||||
hyperbolic_models.add(key)
|
||||
elif value.get("litellm_provider") == "black_forest_labs":
|
||||
|
|
@ -1062,7 +1059,6 @@ models_by_provider: dict = {
|
|||
"v0": v0_models,
|
||||
"morph": morph_models,
|
||||
"lambda_ai": lambda_ai_models,
|
||||
"crusoe": crusoe_models,
|
||||
"hyperbolic": hyperbolic_models,
|
||||
"black_forest_labs": black_forest_labs_models,
|
||||
"recraft": recraft_models,
|
||||
|
|
|
|||
|
|
@ -1135,7 +1135,6 @@ _LLM_CONFIGS_IMPORT_MAP = {
|
|||
"MorphChatConfig": (".llms.morph.chat.transformation", "MorphChatConfig"),
|
||||
"RAGFlowConfig": (".llms.ragflow.chat.transformation", "RAGFlowConfig"),
|
||||
"LambdaAIChatConfig": (".llms.lambda_ai.chat.transformation", "LambdaAIChatConfig"),
|
||||
"CrusoeChatConfig": (".llms.crusoe.chat.transformation", "CrusoeChatConfig"),
|
||||
"HyperbolicChatConfig": (
|
||||
".llms.hyperbolic.chat.transformation",
|
||||
"HyperbolicChatConfig",
|
||||
|
|
|
|||
|
|
@ -560,7 +560,6 @@ LITELLM_CHAT_PROVIDERS = [
|
|||
"oci",
|
||||
"morph",
|
||||
"lambda_ai",
|
||||
"crusoe",
|
||||
"vercel_ai_gateway",
|
||||
"wandb",
|
||||
"ovhcloud",
|
||||
|
|
@ -719,7 +718,6 @@ openai_compatible_endpoints: List = [
|
|||
"https://api.v0.dev/v1",
|
||||
"https://api.morphllm.com/v1",
|
||||
"https://api.lambda.ai/v1",
|
||||
"https://managed-inference-api-proxy.crusoecloud.com/v1/",
|
||||
"https://api.hyperbolic.xyz/v1",
|
||||
"https://ai-gateway.helicone.ai/",
|
||||
"https://ai-gateway.vercel.sh/v1",
|
||||
|
|
@ -775,7 +773,6 @@ openai_compatible_providers: List = [
|
|||
"helicone",
|
||||
"morph",
|
||||
"lambda_ai",
|
||||
"crusoe",
|
||||
"hyperbolic",
|
||||
"vercel_ai_gateway",
|
||||
"aiml",
|
||||
|
|
@ -804,7 +801,6 @@ openai_text_completion_compatible_providers: List = (
|
|||
"chutes",
|
||||
"v0",
|
||||
"lambda_ai",
|
||||
"crusoe",
|
||||
"hyperbolic",
|
||||
"wandb",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -315,9 +315,6 @@ def get_llm_provider( # noqa: PLR0915
|
|||
elif endpoint == "https://api.lambda.ai/v1":
|
||||
custom_llm_provider = "lambda_ai"
|
||||
dynamic_api_key = get_secret_str("LAMBDA_API_KEY")
|
||||
elif endpoint == "https://managed-inference-api-proxy.crusoecloud.com/v1/":
|
||||
custom_llm_provider = "crusoe"
|
||||
dynamic_api_key = get_secret_str("CRUSOE_API_KEY")
|
||||
elif endpoint == "https://api.hyperbolic.xyz/v1":
|
||||
custom_llm_provider = "hyperbolic"
|
||||
dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY")
|
||||
|
|
@ -883,13 +880,6 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915
|
|||
) = litellm.LambdaAIChatConfig()._get_openai_compatible_provider_info(
|
||||
api_base, api_key
|
||||
)
|
||||
elif custom_llm_provider == "crusoe":
|
||||
(
|
||||
api_base,
|
||||
dynamic_api_key,
|
||||
) = litellm.CrusoeChatConfig()._get_openai_compatible_provider_info(
|
||||
api_base, api_key
|
||||
)
|
||||
elif custom_llm_provider == "hyperbolic":
|
||||
(
|
||||
api_base,
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
Translate from OpenAI's `/v1/chat/completions` to Crusoe's `/v1/chat/completions`
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from ...openai_like.chat.transformation import OpenAILikeChatConfig
|
||||
|
||||
|
||||
class CrusoeChatConfig(OpenAILikeChatConfig):
|
||||
"""
|
||||
Crusoe is OpenAI-compatible with standard endpoints.
|
||||
|
||||
Docs: https://docs.crusoecloud.com/managed-inference/overview/index.html
|
||||
"""
|
||||
|
||||
@property
|
||||
def custom_llm_provider(self) -> Optional[str]:
|
||||
return "crusoe"
|
||||
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: Optional[str], api_key: Optional[str]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
api_base = (
|
||||
api_base
|
||||
or get_secret_str("CRUSOE_API_BASE")
|
||||
or "https://managed-inference-api-proxy.crusoecloud.com/v1/"
|
||||
) # type: ignore
|
||||
dynamic_api_key = api_key or get_secret_str("CRUSOE_API_KEY")
|
||||
return api_base, dynamic_api_key
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> list:
|
||||
return [
|
||||
"messages",
|
||||
"model",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"frequency_penalty",
|
||||
"presence_penalty",
|
||||
]
|
||||
|
|
@ -101,5 +101,10 @@
|
|||
"param_mappings": {
|
||||
"max_completion_tokens": "max_tokens"
|
||||
}
|
||||
},
|
||||
"crusoe": {
|
||||
"base_url": "https://managed-inference-api-proxy.crusoecloud.com/v1/",
|
||||
"api_key_env": "CRUSOE_API_KEY",
|
||||
"api_base_env": "CRUSOE_API_BASE"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,29 @@ Tests for Crusoe provider integration
|
|||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm import completion
|
||||
from litellm.llms.crusoe.chat.transformation import CrusoeChatConfig
|
||||
|
||||
CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1/"
|
||||
|
||||
|
||||
def test_crusoe_config_initialization():
|
||||
"""Test CrusoeChatConfig initializes correctly"""
|
||||
config = CrusoeChatConfig()
|
||||
assert config.custom_llm_provider == "crusoe"
|
||||
def test_crusoe_json_registry():
|
||||
"""Test CrusoeChatConfig is loaded from JSON provider registry"""
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
assert JSONProviderRegistry.exists("crusoe")
|
||||
config = JSONProviderRegistry.get("crusoe")
|
||||
assert config is not None
|
||||
assert config.base_url == CRUSOE_API_BASE
|
||||
assert config.api_key_env == "CRUSOE_API_KEY"
|
||||
assert config.api_base_env == "CRUSOE_API_BASE"
|
||||
|
||||
|
||||
def test_crusoe_get_openai_compatible_provider_info():
|
||||
"""Test Crusoe provider info retrieval"""
|
||||
config = CrusoeChatConfig()
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("crusoe"))()
|
||||
|
||||
# Test with default values (no env vars set)
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
|
|
@ -67,22 +72,6 @@ def test_get_llm_provider_crusoe():
|
|||
assert model == "meta-llama/Llama-3.3-70B-Instruct"
|
||||
assert provider == "crusoe"
|
||||
|
||||
# Test with api_base containing Crusoe endpoint
|
||||
model, provider, api_key, api_base = get_llm_provider(
|
||||
"meta-llama/Llama-3.3-70B-Instruct",
|
||||
api_base=CRUSOE_API_BASE,
|
||||
)
|
||||
assert model == "meta-llama/Llama-3.3-70B-Instruct"
|
||||
assert provider == "crusoe"
|
||||
assert api_base == CRUSOE_API_BASE
|
||||
|
||||
|
||||
def test_crusoe_in_provider_lists():
|
||||
"""Test that Crusoe is registered in all necessary provider lists"""
|
||||
assert "crusoe" in litellm.openai_compatible_providers
|
||||
assert "crusoe" in litellm.provider_list
|
||||
assert CRUSOE_API_BASE in litellm.openai_compatible_endpoints
|
||||
|
||||
|
||||
def test_crusoe_models_configuration():
|
||||
"""Test that Crusoe models are configured correctly"""
|
||||
|
|
@ -91,9 +80,6 @@ def test_crusoe_models_configuration():
|
|||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
litellm.crusoe_models = set()
|
||||
litellm.add_known_models()
|
||||
|
||||
crusoe_models = [
|
||||
"crusoe/meta-llama/Llama-3.3-70B-Instruct",
|
||||
"crusoe/deepseek-ai/DeepSeek-R1-0528",
|
||||
|
|
@ -111,54 +97,3 @@ def test_crusoe_models_configuration():
|
|||
f"{model} should have crusoe as provider"
|
||||
)
|
||||
assert model_info.get("mode") == "chat", f"{model} should be in chat mode"
|
||||
|
||||
|
||||
def test_crusoe_model_list_populated():
|
||||
"""Test that crusoe_models list is populated correctly"""
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
litellm.crusoe_models = set()
|
||||
litellm.add_known_models()
|
||||
|
||||
assert len(litellm.crusoe_models) > 0, "crusoe_models list should not be empty"
|
||||
|
||||
for model in litellm.crusoe_models:
|
||||
assert model.startswith("crusoe/"), (
|
||||
f"Model {model} should start with 'crusoe/'"
|
||||
)
|
||||
|
||||
expected_models = [
|
||||
"crusoe/meta-llama/Llama-3.3-70B-Instruct",
|
||||
"crusoe/deepseek-ai/DeepSeek-R1-0528",
|
||||
"crusoe/deepseek-ai/DeepSeek-V3-0324",
|
||||
"crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||
"crusoe/moonshotai/Kimi-K2-Thinking",
|
||||
"crusoe/openai/gpt-oss-120b",
|
||||
"crusoe/google/gemma-3-12b-it",
|
||||
]
|
||||
|
||||
for model in expected_models:
|
||||
assert model in litellm.crusoe_models, (
|
||||
f"{model} should be in crusoe_models list"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crusoe_completion_call():
|
||||
"""Test completion call with Crusoe provider (requires CRUSOE_API_KEY)"""
|
||||
if not os.getenv("CRUSOE_API_KEY"):
|
||||
pytest.skip("CRUSOE_API_KEY not set")
|
||||
|
||||
try:
|
||||
response = await litellm.acompletion(
|
||||
model="crusoe/meta-llama/Llama-3.3-70B-Instruct",
|
||||
messages=[{"role": "user", "content": "Hello, this is a test"}],
|
||||
max_tokens=10,
|
||||
)
|
||||
assert response.choices[0].message.content
|
||||
assert response.model
|
||||
assert response.usage
|
||||
except Exception as e:
|
||||
if "crusoe" not in str(e) and "provider" not in str(e).lower():
|
||||
raise
|
||||
|
|
|
|||
111
tests/test_litellm/llms/crusoe/test_crusoe.py
Normal file
111
tests/test_litellm/llms/crusoe/test_crusoe.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../../.."))
|
||||
from unittest.mock import patch
|
||||
|
||||
CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1/"
|
||||
|
||||
|
||||
def test_crusoe_json_registry():
|
||||
"""Test Crusoe is registered in the JSON provider registry"""
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
assert JSONProviderRegistry.exists("crusoe")
|
||||
config = JSONProviderRegistry.get("crusoe")
|
||||
assert config is not None
|
||||
assert config.base_url == CRUSOE_API_BASE
|
||||
assert config.api_key_env == "CRUSOE_API_KEY"
|
||||
assert config.api_base_env == "CRUSOE_API_BASE"
|
||||
|
||||
|
||||
def test_crusoe_dynamic_config_defaults():
|
||||
"""Test dynamic config returns correct default API base"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("crusoe"))()
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
|
||||
|
||||
assert api_base == CRUSOE_API_BASE
|
||||
assert api_key is None
|
||||
|
||||
|
||||
def test_crusoe_dynamic_config_env_vars():
|
||||
"""Test dynamic config reads CRUSOE_API_KEY and CRUSOE_API_BASE from env"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("crusoe"))()
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"CRUSOE_API_KEY": "test-key", "CRUSOE_API_BASE": "https://custom.crusoe.com/v1/"},
|
||||
):
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
|
||||
|
||||
assert api_base == "https://custom.crusoe.com/v1/"
|
||||
assert api_key == "test-key"
|
||||
|
||||
|
||||
def test_crusoe_dynamic_config_explicit_params():
|
||||
"""Test explicit params override env vars"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("crusoe"))()
|
||||
|
||||
with patch.dict(os.environ, {"CRUSOE_API_KEY": "env-key"}):
|
||||
api_base, api_key = config._get_openai_compatible_provider_info(
|
||||
"https://override.crusoe.com/v1/", "override-key"
|
||||
)
|
||||
|
||||
assert api_base == "https://override.crusoe.com/v1/"
|
||||
assert api_key == "override-key"
|
||||
|
||||
|
||||
def test_crusoe_supported_params():
|
||||
"""Test dynamic config returns standard OpenAI params"""
|
||||
from litellm.llms.openai_like.dynamic_config import create_config_class
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
config = create_config_class(JSONProviderRegistry.get("crusoe"))()
|
||||
params = config.get_supported_openai_params(model="meta-llama/Llama-3.3-70B-Instruct")
|
||||
|
||||
assert isinstance(params, list)
|
||||
assert len(params) > 0
|
||||
assert "temperature" in params
|
||||
assert "max_tokens" in params
|
||||
assert "stream" in params
|
||||
|
||||
|
||||
def test_crusoe_provider_detection_by_prefix():
|
||||
"""Test crusoe/model prefix is correctly routed"""
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
model, provider, _, _ = get_llm_provider("crusoe/meta-llama/Llama-3.3-70B-Instruct")
|
||||
assert provider == "crusoe"
|
||||
assert model == "meta-llama/Llama-3.3-70B-Instruct"
|
||||
|
||||
|
||||
def test_crusoe_model_list_populated():
|
||||
"""Test Crusoe models are present in model_prices_and_context_window.json"""
|
||||
import litellm
|
||||
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
expected = [
|
||||
"crusoe/meta-llama/Llama-3.3-70B-Instruct",
|
||||
"crusoe/deepseek-ai/DeepSeek-R1-0528",
|
||||
"crusoe/deepseek-ai/DeepSeek-V3-0324",
|
||||
"crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||
"crusoe/moonshotai/Kimi-K2-Thinking",
|
||||
"crusoe/openai/gpt-oss-120b",
|
||||
"crusoe/google/gemma-3-12b-it",
|
||||
]
|
||||
for model in expected:
|
||||
assert model in litellm.model_cost, f"{model} not found in model_cost"
|
||||
assert litellm.model_cost[model].get("litellm_provider") == "crusoe"
|
||||
Loading…
Add table
Reference in a new issue