From ab3ac4a8a097ed7be6a52649b8caf6f4d5d2deec Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Mon, 9 Mar 2026 15:20:58 -0700 Subject: [PATCH 01/12] adding crusoe to litellm --- docs/my-website/docs/providers/crusoe.md | 186 ++++++++++++++++++ litellm/__init__.py | 4 + litellm/_lazy_imports_registry.py | 1 + litellm/constants.py | 4 + .../get_llm_provider_logic.py | 10 + litellm/llms/crusoe/__init__.py | 0 litellm/llms/crusoe/chat/__init__.py | 0 litellm/llms/crusoe/chat/transformation.py | 42 ++++ model_prices_and_context_window.json | 89 +++++++++ tests/llm_translation/test_crusoe.py | 164 +++++++++++++++ 10 files changed, 500 insertions(+) create mode 100644 docs/my-website/docs/providers/crusoe.md create mode 100644 litellm/llms/crusoe/__init__.py create mode 100644 litellm/llms/crusoe/chat/__init__.py create mode 100644 litellm/llms/crusoe/chat/transformation.py create mode 100644 tests/llm_translation/test_crusoe.py diff --git a/docs/my-website/docs/providers/crusoe.md b/docs/my-website/docs/providers/crusoe.md new file mode 100644 index 00000000000..696da3fa1bc --- /dev/null +++ b/docs/my-website/docs/providers/crusoe.md @@ -0,0 +1,186 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Crusoe + +## Overview + +| Property | Details | +|-------|-------| +| Description | Crusoe Cloud provides GPU-accelerated inference for open-source large language models, optimized for performance and cost efficiency. | +| Provider Route on LiteLLM | `crusoe/` | +| Link to Provider Doc | [Crusoe Managed Inference Documentation ↗](https://docs.crusoecloud.com/managed-inference/overview/index.html) | +| Base URL | `https://managed-inference-api-proxy.crusoecloud.com/v1/` | +| Supported Operations | [`/chat/completions`](#sample-usage) | + +
+
+ +**We support ALL Crusoe models, just set `crusoe/` as a prefix when sending completion requests** + +## Available Models + +| Model | Description | Context Window | +|-------|-------------|----------------| +| `crusoe/deepseek-ai/DeepSeek-R1-0528` | DeepSeek R1 reasoning model (May 2025) | 163,840 tokens | +| `crusoe/deepseek-ai/DeepSeek-V3-0324` | DeepSeek V3 chat model (March 2025) | 163,840 tokens | +| `crusoe/google/gemma-3-12b-it` | Google Gemma 3 12B instruction-tuned | 131,072 tokens | +| `crusoe/meta-llama/Llama-3.3-70B-Instruct` | Llama 3.3 70B instruction-tuned | 131,072 tokens | +| `crusoe/moonshotai/Kimi-K2-Thinking` | Kimi K2 extended thinking model | 262,144 tokens | +| `crusoe/openai/gpt-oss-120b` | OpenAI 120B open-source model | 131,072 tokens | +| `crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507` | Qwen3 235B MoE instruction-tuned | 262,144 tokens | + +## Required Variables + +```python showLineNumbers title="Environment Variables" +os.environ["CRUSOE_API_KEY"] = "" # your Crusoe API key +``` + +## Usage - LiteLLM Python SDK + +### Non-streaming + +```python showLineNumbers title="Crusoe Non-streaming Completion" +import os +import litellm +from litellm import completion + +os.environ["CRUSOE_API_KEY"] = "" # your Crusoe API key + +messages = [{"content": "Hello, how are you?", "role": "user"}] + +# Crusoe call +response = completion( + model="crusoe/meta-llama/Llama-3.3-70B-Instruct", + messages=messages +) + +print(response) +``` + +### Streaming + +```python showLineNumbers title="Crusoe Streaming Completion" +import os +import litellm +from litellm import completion + +os.environ["CRUSOE_API_KEY"] = "" # your Crusoe API key + +messages = [{"content": "Write a short story about AI", "role": "user"}] + +# Crusoe call with streaming +response = completion( + model="crusoe/meta-llama/Llama-3.1-70B-Instruct", + messages=messages, + stream=True +) + +for chunk in response: + print(chunk) +``` + +### Function Calling + +```python showLineNumbers title="Crusoe Function Calling" +import os +import litellm +from litellm import completion + +os.environ["CRUSOE_API_KEY"] = "" # your Crusoe API key + +tools = [{ + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + } + }, + "required": ["location"] + } + } +}] + +messages = [{"role": "user", "content": "What's the weather in Boston?"}] + +response = completion( + model="crusoe/meta-llama/Llama-3.3-70B-Instruct", + messages=messages, + tools=tools, + tool_choice="auto" +) + +print(response) +``` + +## Usage - LiteLLM Proxy Server + +```yaml showLineNumbers title="config.yaml" +model_list: + - model_name: llama-3.3-70b + litellm_params: + model: crusoe/meta-llama/Llama-3.3-70B-Instruct + api_key: os.environ/CRUSOE_API_KEY + - model_name: deepseek-r1 + litellm_params: + model: crusoe/deepseek-ai/DeepSeek-R1-0528 + api_key: os.environ/CRUSOE_API_KEY + - model_name: deepseek-v3 + litellm_params: + model: crusoe/deepseek-ai/DeepSeek-V3-0324 + api_key: os.environ/CRUSOE_API_KEY + - model_name: qwen3-235b + litellm_params: + model: crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507 + api_key: os.environ/CRUSOE_API_KEY + - model_name: kimi-k2 + litellm_params: + model: crusoe/moonshotai/Kimi-K2-Thinking + api_key: os.environ/CRUSOE_API_KEY +``` + +## Custom API Base + +```python showLineNumbers title="Custom API Base" +import os +import litellm +from litellm import completion + +# Using environment variable +os.environ["CRUSOE_API_BASE"] = "https://custom.crusoecloud.com/v1/" +os.environ["CRUSOE_API_KEY"] = "" # your API key + +# Or pass directly +response = completion( + model="crusoe/meta-llama/Llama-3.3-70B-Instruct", + messages=[{"content": "Hello!", "role": "user"}], + api_base="https://custom.crusoecloud.com/v1/", + api_key="your-api-key" +) +``` + +## Supported OpenAI Parameters + +- `temperature` +- `max_tokens` +- `max_completion_tokens` +- `top_p` +- `frequency_penalty` +- `presence_penalty` +- `stop` +- `n` +- `stream` +- `tools` +- `tool_choice` +- `response_format` +- `seed` +- `user` +- `logit_bias` +- `logprobs` +- `top_logprobs` diff --git a/litellm/__init__.py b/litellm/__init__.py index 7f72e0b0e89..4931b8cc77a 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -577,6 +577,7 @@ 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() @@ -824,6 +825,8 @@ 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": @@ -1059,6 +1062,7 @@ 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, diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 9164a3c8ae4..93dd32df495 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -1135,6 +1135,7 @@ _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", diff --git a/litellm/constants.py b/litellm/constants.py index 89c59ee9326..6663ee59395 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -560,6 +560,7 @@ LITELLM_CHAT_PROVIDERS = [ "oci", "morph", "lambda_ai", + "crusoe", "vercel_ai_gateway", "wandb", "ovhcloud", @@ -718,6 +719,7 @@ 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", @@ -773,6 +775,7 @@ openai_compatible_providers: List = [ "helicone", "morph", "lambda_ai", + "crusoe", "hyperbolic", "vercel_ai_gateway", "aiml", @@ -801,6 +804,7 @@ openai_text_completion_compatible_providers: List = ( "chutes", "v0", "lambda_ai", + "crusoe", "hyperbolic", "wandb", ] diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 36218417377..ad5297ef53e 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -315,6 +315,9 @@ 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") @@ -880,6 +883,13 @@ 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, diff --git a/litellm/llms/crusoe/__init__.py b/litellm/llms/crusoe/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/litellm/llms/crusoe/chat/__init__.py b/litellm/llms/crusoe/chat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/litellm/llms/crusoe/chat/transformation.py b/litellm/llms/crusoe/chat/transformation.py new file mode 100644 index 00000000000..dab9a9f833b --- /dev/null +++ b/litellm/llms/crusoe/chat/transformation.py @@ -0,0 +1,42 @@ +""" +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", + ] diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 879dd42be47..58b5a582e02 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -20444,6 +20444,95 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, + "crusoe/deepseek-ai/DeepSeek-R1-0528": { + "input_cost_per_token": 3e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 7e-06, + "supports_function_calling": false, + "supports_system_messages": true, + "supports_tool_choice": false + }, + "crusoe/deepseek-ai/DeepSeek-V3-0324": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 1.5e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/google/gemma-3-12b-it": { + "input_cost_per_token": 1e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 1e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/meta-llama/Llama-3.3-70B-Instruct": { + "input_cost_per_token": 2e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 2e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/moonshotai/Kimi-K2-Thinking": { + "input_cost_per_token": 2.5e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "supports_function_calling": false, + "supports_system_messages": true, + "supports_tool_choice": false + }, + "crusoe/openai/gpt-oss-120b": { + "input_cost_per_token": 8e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 8e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507": { + "input_cost_per_token": 3e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, "lambda_ai/deepseek-llama3.3-70b": { "input_cost_per_token": 2e-07, "litellm_provider": "lambda_ai", diff --git a/tests/llm_translation/test_crusoe.py b/tests/llm_translation/test_crusoe.py new file mode 100644 index 00000000000..254d6530a42 --- /dev/null +++ b/tests/llm_translation/test_crusoe.py @@ -0,0 +1,164 @@ +""" +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_get_openai_compatible_provider_info(): + """Test Crusoe provider info retrieval""" + config = CrusoeChatConfig() + + # Test with default values (no env vars set) + with mock.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 + + # Test with environment variables + with mock.patch.dict( + os.environ, + { + "CRUSOE_API_KEY": "test-key", + "CRUSOE_API_BASE": "https://custom.crusoecloud.com/v1/", + }, + ): + api_base, api_key = config._get_openai_compatible_provider_info(None, None) + assert api_base == "https://custom.crusoecloud.com/v1/" + assert api_key == "test-key" + + # Test with explicit parameters (should override env vars) + with mock.patch.dict( + os.environ, + { + "CRUSOE_API_KEY": "env-key", + "CRUSOE_API_BASE": "https://env.crusoecloud.com/v1/", + }, + ): + api_base, api_key = config._get_openai_compatible_provider_info( + "https://param.crusoecloud.com/v1/", "param-key" + ) + assert api_base == "https://param.crusoecloud.com/v1/" + assert api_key == "param-key" + + +def test_get_llm_provider_crusoe(): + """Test that get_llm_provider correctly identifies Crusoe""" + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + # Test with crusoe/model-name format + model, provider, api_key, api_base = get_llm_provider( + "crusoe/meta-llama/Llama-3.3-70B-Instruct" + ) + 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""" + from litellm import get_model_info + + 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", + "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 crusoe_models: + model_info = get_model_info(model) + assert model_info is not None, f"Model info not found for {model}" + assert model_info.get("litellm_provider") == "crusoe", ( + 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 From b382650ec544ee37aa959dd63f26128950b260fd Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Thu, 12 Mar 2026 14:15:44 -0700 Subject: [PATCH 02/12] 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 --- litellm/__init__.py | 4 - litellm/_lazy_imports_registry.py | 1 - litellm/constants.py | 4 - .../get_llm_provider_logic.py | 10 -- litellm/llms/crusoe/__init__.py | 0 litellm/llms/crusoe/chat/__init__.py | 0 litellm/llms/crusoe/chat/transformation.py | 42 ------- litellm/llms/openai_like/providers.json | 5 + tests/llm_translation/test_crusoe.py | 93 +++------------ tests/test_litellm/llms/crusoe/test_crusoe.py | 111 ++++++++++++++++++ 10 files changed, 130 insertions(+), 140 deletions(-) delete mode 100644 litellm/llms/crusoe/__init__.py delete mode 100644 litellm/llms/crusoe/chat/__init__.py delete mode 100644 litellm/llms/crusoe/chat/transformation.py create mode 100644 tests/test_litellm/llms/crusoe/test_crusoe.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 4931b8cc77a..7f72e0b0e89 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -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, diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 93dd32df495..9164a3c8ae4 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -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", diff --git a/litellm/constants.py b/litellm/constants.py index 6663ee59395..89c59ee9326 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -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", ] diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index ad5297ef53e..36218417377 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -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, diff --git a/litellm/llms/crusoe/__init__.py b/litellm/llms/crusoe/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/litellm/llms/crusoe/chat/__init__.py b/litellm/llms/crusoe/chat/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/litellm/llms/crusoe/chat/transformation.py b/litellm/llms/crusoe/chat/transformation.py deleted file mode 100644 index dab9a9f833b..00000000000 --- a/litellm/llms/crusoe/chat/transformation.py +++ /dev/null @@ -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", - ] diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 275c352b39e..9021f9fc52c 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -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" } } diff --git a/tests/llm_translation/test_crusoe.py b/tests/llm_translation/test_crusoe.py index 254d6530a42..064f4f053bc 100644 --- a/tests/llm_translation/test_crusoe.py +++ b/tests/llm_translation/test_crusoe.py @@ -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 diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py new file mode 100644 index 00000000000..6ca73178047 --- /dev/null +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -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" From bce4e81ad71a6ddf59c358c9a96533bea516dc98 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Thu, 12 Mar 2026 14:26:29 -0700 Subject: [PATCH 03/12] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- tests/test_litellm/llms/crusoe/test_crusoe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py index 6ca73178047..899dab256e3 100644 --- a/tests/test_litellm/llms/crusoe/test_crusoe.py +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -1,7 +1,6 @@ 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/" From 05b3f48b18504fefb5740e32cf7a1b8e24de222f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 12 Mar 2026 18:51:15 -0700 Subject: [PATCH 04/12] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/llms/openai_like/providers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 9021f9fc52c..2f64553c50a 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -103,7 +103,7 @@ } }, "crusoe": { - "base_url": "https://managed-inference-api-proxy.crusoecloud.com/v1/", + "base_url": "https://managed-inference-api-proxy.crusoecloud.com/v1", "api_key_env": "CRUSOE_API_KEY", "api_base_env": "CRUSOE_API_BASE" } From b6bd565f550ab625e485d0d4d191d6cbe3e7906e Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 13 Mar 2026 14:13:07 -0700 Subject: [PATCH 05/12] fix: remove trailing slash from CRUSOE_API_BASE and unused sys import --- tests/llm_translation/test_crusoe.py | 2 +- tests/test_litellm/llms/crusoe/test_crusoe.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/llm_translation/test_crusoe.py b/tests/llm_translation/test_crusoe.py index 064f4f053bc..66854b2869f 100644 --- a/tests/llm_translation/test_crusoe.py +++ b/tests/llm_translation/test_crusoe.py @@ -6,7 +6,7 @@ from unittest import mock import litellm -CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1/" +CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1" def test_crusoe_json_registry(): diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py index 899dab256e3..485c00b357c 100644 --- a/tests/test_litellm/llms/crusoe/test_crusoe.py +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -1,9 +1,7 @@ import os -import sys - from unittest.mock import patch -CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1/" +CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1" def test_crusoe_json_registry(): From 6996cf39523611728428ca2b44b15fd3caaa1825 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 20 Mar 2026 09:15:19 -0700 Subject: [PATCH 06/12] fix(crusoe): fix docs trailing slash, test state pollution, missing __init__.py - Remove trailing slash from docs Base URL to match providers.json - Wrap model_cost mutations in try/finally to prevent test state leakage - Add missing __init__.py to crusoe test package --- docs/my-website/docs/providers/crusoe.md | 2 +- tests/llm_translation/test_crusoe.py | 29 ++++++++++----- tests/test_litellm/llms/crusoe/__init__.py | 0 tests/test_litellm/llms/crusoe/test_crusoe.py | 37 ++++++++++++------- 4 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 tests/test_litellm/llms/crusoe/__init__.py diff --git a/docs/my-website/docs/providers/crusoe.md b/docs/my-website/docs/providers/crusoe.md index 696da3fa1bc..dbcd0264981 100644 --- a/docs/my-website/docs/providers/crusoe.md +++ b/docs/my-website/docs/providers/crusoe.md @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem'; | Description | Crusoe Cloud provides GPU-accelerated inference for open-source large language models, optimized for performance and cost efficiency. | | Provider Route on LiteLLM | `crusoe/` | | Link to Provider Doc | [Crusoe Managed Inference Documentation ↗](https://docs.crusoecloud.com/managed-inference/overview/index.html) | -| Base URL | `https://managed-inference-api-proxy.crusoecloud.com/v1/` | +| Base URL | `https://managed-inference-api-proxy.crusoecloud.com/v1` | | Supported Operations | [`/chat/completions`](#sample-usage) |
diff --git a/tests/llm_translation/test_crusoe.py b/tests/llm_translation/test_crusoe.py index 66854b2869f..e570e314cd8 100644 --- a/tests/llm_translation/test_crusoe.py +++ b/tests/llm_translation/test_crusoe.py @@ -77,10 +77,13 @@ def test_crusoe_models_configuration(): """Test that Crusoe models are configured correctly""" from litellm import get_model_info - os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" - litellm.model_cost = litellm.get_model_cost_map(url="") + original_model_cost = litellm.model_cost + original_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP") + try: + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") - crusoe_models = [ + crusoe_models = [ "crusoe/meta-llama/Llama-3.3-70B-Instruct", "crusoe/deepseek-ai/DeepSeek-R1-0528", "crusoe/deepseek-ai/DeepSeek-V3-0324", @@ -90,10 +93,16 @@ def test_crusoe_models_configuration(): "crusoe/google/gemma-3-12b-it", ] - for model in crusoe_models: - model_info = get_model_info(model) - assert model_info is not None, f"Model info not found for {model}" - assert model_info.get("litellm_provider") == "crusoe", ( - f"{model} should have crusoe as provider" - ) - assert model_info.get("mode") == "chat", f"{model} should be in chat mode" + for model in crusoe_models: + model_info = get_model_info(model) + assert model_info is not None, f"Model info not found for {model}" + assert model_info.get("litellm_provider") == "crusoe", ( + f"{model} should have crusoe as provider" + ) + assert model_info.get("mode") == "chat", f"{model} should be in chat mode" + finally: + litellm.model_cost = original_model_cost + if original_env is None: + os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None) + else: + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = original_env diff --git a/tests/test_litellm/llms/crusoe/__init__.py b/tests/test_litellm/llms/crusoe/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py index 485c00b357c..70c538d915a 100644 --- a/tests/test_litellm/llms/crusoe/test_crusoe.py +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -91,18 +91,27 @@ 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="") + original_model_cost = litellm.model_cost + original_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP") + try: + 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" + 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" + finally: + litellm.model_cost = original_model_cost + if original_env is None: + os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None) + else: + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = original_env From 1ecc229e3154a2eed9a843b2cb709a192d77fd25 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 20 Mar 2026 09:50:30 -0700 Subject: [PATCH 07/12] fix(crusoe): sync backup model cost map with main file The backup JSON was missing Crusoe model entries, causing test_crusoe_model_list_populated to fail with AssertionError. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...odel_prices_and_context_window_backup.json | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index e7ff57f27e1..43caee55425 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -20444,6 +20444,95 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, + "crusoe/deepseek-ai/DeepSeek-R1-0528": { + "input_cost_per_token": 3e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 7e-06, + "supports_function_calling": false, + "supports_system_messages": true, + "supports_tool_choice": false + }, + "crusoe/deepseek-ai/DeepSeek-V3-0324": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 163840, + "max_output_tokens": 163840, + "max_tokens": 163840, + "mode": "chat", + "output_cost_per_token": 1.5e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/google/gemma-3-12b-it": { + "input_cost_per_token": 1e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 1e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/meta-llama/Llama-3.3-70B-Instruct": { + "input_cost_per_token": 2e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 2e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/moonshotai/Kimi-K2-Thinking": { + "input_cost_per_token": 2.5e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "supports_function_calling": false, + "supports_system_messages": true, + "supports_tool_choice": false + }, + "crusoe/openai/gpt-oss-120b": { + "input_cost_per_token": 8e-07, + "litellm_provider": "crusoe", + "max_input_tokens": 131072, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 8e-07, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507": { + "input_cost_per_token": 3e-06, + "litellm_provider": "crusoe", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, "lambda_ai/deepseek-llama3.3-70b": { "input_cost_per_token": 2e-07, "litellm_provider": "lambda_ai", From e0bb0206eb626820cd9bf4a83a4f9d3250b141ee Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 20 Mar 2026 09:53:00 -0700 Subject: [PATCH 08/12] feat(crusoe): add supports_reasoning flag for DeepSeek-R1 and Kimi-K2-Thinking These are reasoning/thinking models but were missing the flag, causing litellm.supports_reasoning() to return False and reasoning-token handling to not activate. Co-Authored-By: Claude Opus 4.6 (1M context) --- litellm/model_prices_and_context_window_backup.json | 2 ++ model_prices_and_context_window.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 43caee55425..db7265dd3fe 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -20453,6 +20453,7 @@ "mode": "chat", "output_cost_per_token": 7e-06, "supports_function_calling": false, + "supports_reasoning": true, "supports_system_messages": true, "supports_tool_choice": false }, @@ -20504,6 +20505,7 @@ "mode": "chat", "output_cost_per_token": 2.5e-06, "supports_function_calling": false, + "supports_reasoning": true, "supports_system_messages": true, "supports_tool_choice": false }, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 58b5a582e02..a852d3bef01 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -20453,6 +20453,7 @@ "mode": "chat", "output_cost_per_token": 7e-06, "supports_function_calling": false, + "supports_reasoning": true, "supports_system_messages": true, "supports_tool_choice": false }, @@ -20504,6 +20505,7 @@ "mode": "chat", "output_cost_per_token": 2.5e-06, "supports_function_calling": false, + "supports_reasoning": true, "supports_system_messages": true, "supports_tool_choice": false }, From b99dfe70d18ff6841b8c1045f05c0e9619c762c6 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 20 Mar 2026 10:00:57 -0700 Subject: [PATCH 09/12] =?UTF-8?q?fix(crusoe):=20add=20param=5Fmappings=20f?= =?UTF-8?q?or=20max=5Fcompletion=5Ftokens=20=E2=86=92=20max=5Ftokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Crusoe's vLLM-based endpoint accepts max_tokens, not max_completion_tokens. Without this mapping, callers using the OpenAI-standard param would get errors. --- litellm/llms/openai_like/providers.json | 5 ++++- tests/test_litellm/llms/crusoe/test_crusoe.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 2f64553c50a..8c2f5fa4ffb 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -105,6 +105,9 @@ "crusoe": { "base_url": "https://managed-inference-api-proxy.crusoecloud.com/v1", "api_key_env": "CRUSOE_API_KEY", - "api_base_env": "CRUSOE_API_BASE" + "api_base_env": "CRUSOE_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + } } } diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py index 70c538d915a..877d13451be 100644 --- a/tests/test_litellm/llms/crusoe/test_crusoe.py +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -78,6 +78,24 @@ def test_crusoe_supported_params(): assert "stream" in params +def test_crusoe_param_mapping_max_completion_tokens(): + """Test max_completion_tokens is mapped to max_tokens for Crusoe""" + 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"))() + optional_params = config.map_openai_params( + non_default_params={"max_completion_tokens": 1024}, + optional_params={}, + model="meta-llama/Llama-3.3-70B-Instruct", + drop_params=False, + ) + + assert "max_tokens" in optional_params, "max_completion_tokens should be mapped to max_tokens" + assert optional_params["max_tokens"] == 1024 + assert "max_completion_tokens" not in optional_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 From b3a07e3669210459997c42e81bb1f14cbc3098ef Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Fri, 20 Mar 2026 10:10:26 -0700 Subject: [PATCH 10/12] fix(crusoe): remove trailing slashes from API base URLs and fix list indentation Trailing slashes on custom API base examples cause double-slash in get_complete_url. Also fixes inconsistent list indentation in test_crusoe_models_configuration. --- docs/my-website/docs/providers/crusoe.md | 4 +-- tests/llm_translation/test_crusoe.py | 26 +++++++++---------- tests/test_litellm/llms/crusoe/test_crusoe.py | 8 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/my-website/docs/providers/crusoe.md b/docs/my-website/docs/providers/crusoe.md index dbcd0264981..3079c593121 100644 --- a/docs/my-website/docs/providers/crusoe.md +++ b/docs/my-website/docs/providers/crusoe.md @@ -153,14 +153,14 @@ import litellm from litellm import completion # Using environment variable -os.environ["CRUSOE_API_BASE"] = "https://custom.crusoecloud.com/v1/" +os.environ["CRUSOE_API_BASE"] = "https://custom.crusoecloud.com/v1" os.environ["CRUSOE_API_KEY"] = "" # your API key # Or pass directly response = completion( model="crusoe/meta-llama/Llama-3.3-70B-Instruct", messages=[{"content": "Hello!", "role": "user"}], - api_base="https://custom.crusoecloud.com/v1/", + api_base="https://custom.crusoecloud.com/v1", api_key="your-api-key" ) ``` diff --git a/tests/llm_translation/test_crusoe.py b/tests/llm_translation/test_crusoe.py index e570e314cd8..56aa4e4cd42 100644 --- a/tests/llm_translation/test_crusoe.py +++ b/tests/llm_translation/test_crusoe.py @@ -39,11 +39,11 @@ def test_crusoe_get_openai_compatible_provider_info(): os.environ, { "CRUSOE_API_KEY": "test-key", - "CRUSOE_API_BASE": "https://custom.crusoecloud.com/v1/", + "CRUSOE_API_BASE": "https://custom.crusoecloud.com/v1", }, ): api_base, api_key = config._get_openai_compatible_provider_info(None, None) - assert api_base == "https://custom.crusoecloud.com/v1/" + assert api_base == "https://custom.crusoecloud.com/v1" assert api_key == "test-key" # Test with explicit parameters (should override env vars) @@ -51,13 +51,13 @@ def test_crusoe_get_openai_compatible_provider_info(): os.environ, { "CRUSOE_API_KEY": "env-key", - "CRUSOE_API_BASE": "https://env.crusoecloud.com/v1/", + "CRUSOE_API_BASE": "https://env.crusoecloud.com/v1", }, ): api_base, api_key = config._get_openai_compatible_provider_info( - "https://param.crusoecloud.com/v1/", "param-key" + "https://param.crusoecloud.com/v1", "param-key" ) - assert api_base == "https://param.crusoecloud.com/v1/" + assert api_base == "https://param.crusoecloud.com/v1" assert api_key == "param-key" @@ -84,14 +84,14 @@ def test_crusoe_models_configuration(): litellm.model_cost = litellm.get_model_cost_map(url="") crusoe_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", - ] + "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 crusoe_models: model_info = get_model_info(model) diff --git a/tests/test_litellm/llms/crusoe/test_crusoe.py b/tests/test_litellm/llms/crusoe/test_crusoe.py index 877d13451be..0a05126919a 100644 --- a/tests/test_litellm/llms/crusoe/test_crusoe.py +++ b/tests/test_litellm/llms/crusoe/test_crusoe.py @@ -39,11 +39,11 @@ def test_crusoe_dynamic_config_env_vars(): with patch.dict( os.environ, - {"CRUSOE_API_KEY": "test-key", "CRUSOE_API_BASE": "https://custom.crusoe.com/v1/"}, + {"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_base == "https://custom.crusoe.com/v1" assert api_key == "test-key" @@ -56,10 +56,10 @@ def test_crusoe_dynamic_config_explicit_params(): 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" + "https://override.crusoe.com/v1", "override-key" ) - assert api_base == "https://override.crusoe.com/v1/" + assert api_base == "https://override.crusoe.com/v1" assert api_key == "override-key" From added6fab8093ddfdb22dd87462f15ce1dab87e6 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Mon, 23 Mar 2026 12:53:10 -0700 Subject: [PATCH 11/12] fix(crusoe): split Custom API Base docs into two independent examples The previous example set CRUSOE_API_BASE via env var and also passed api_base= in the same call, making it look like both were required. They are independent alternatives. --- docs/my-website/docs/providers/crusoe.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/my-website/docs/providers/crusoe.md b/docs/my-website/docs/providers/crusoe.md index 3079c593121..b28903c27c0 100644 --- a/docs/my-website/docs/providers/crusoe.md +++ b/docs/my-website/docs/providers/crusoe.md @@ -147,21 +147,31 @@ model_list: ## Custom API Base -```python showLineNumbers title="Custom API Base" +**Option 1: Environment variable** + +```python showLineNumbers title="Custom API Base via env var" import os -import litellm from litellm import completion -# Using environment variable os.environ["CRUSOE_API_BASE"] = "https://custom.crusoecloud.com/v1" os.environ["CRUSOE_API_KEY"] = "" # your API key -# Or pass directly +response = completion( + model="crusoe/meta-llama/Llama-3.3-70B-Instruct", + messages=[{"content": "Hello!", "role": "user"}], +) +``` + +**Option 2: Pass directly** + +```python showLineNumbers title="Custom API Base via parameter" +from litellm import completion + response = completion( model="crusoe/meta-llama/Llama-3.3-70B-Instruct", messages=[{"content": "Hello!", "role": "user"}], api_base="https://custom.crusoecloud.com/v1", - api_key="your-api-key" + api_key="your-api-key", ) ``` From f421fdd6b0a6958ba805fdfc4b3424593f6c5751 Mon Sep 17 00:00:00 2001 From: Emmanuel Acheampong Date: Mon, 23 Mar 2026 13:43:25 -0700 Subject: [PATCH 12/12] fix(crusoe): fix streaming doc model typo and add supports_vision for Gemma 3 - Streaming example referenced Llama-3.1 instead of Llama-3.3 - Add supports_vision: true for gemma-3-12b-it in both JSON files, matching other providers (bedrock, novita) --- docs/my-website/docs/providers/crusoe.md | 2 +- litellm/model_prices_and_context_window_backup.json | 3 ++- model_prices_and_context_window.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/my-website/docs/providers/crusoe.md b/docs/my-website/docs/providers/crusoe.md index b28903c27c0..aa737cbdcd8 100644 --- a/docs/my-website/docs/providers/crusoe.md +++ b/docs/my-website/docs/providers/crusoe.md @@ -71,7 +71,7 @@ messages = [{"content": "Write a short story about AI", "role": "user"}] # Crusoe call with streaming response = completion( - model="crusoe/meta-llama/Llama-3.1-70B-Instruct", + model="crusoe/meta-llama/Llama-3.3-70B-Instruct", messages=messages, stream=True ) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index db7265dd3fe..77b1e0aab64 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -20481,7 +20481,8 @@ "supports_function_calling": true, "supports_parallel_function_calling": true, "supports_system_messages": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "supports_vision": true }, "crusoe/meta-llama/Llama-3.3-70B-Instruct": { "input_cost_per_token": 2e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index a852d3bef01..214db4733d9 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -20481,7 +20481,8 @@ "supports_function_calling": true, "supports_parallel_function_calling": true, "supports_system_messages": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "supports_vision": true }, "crusoe/meta-llama/Llama-3.3-70B-Instruct": { "input_cost_per_token": 2e-07,