Merge pull request #25856 from BerriAI/litellm_clean_litellm_oss_staging_04_01_2026

Litellm clean litellm oss staging 04 01 2026
This commit is contained in:
Mateo Wang
2026-05-01 16:10:15 -07:00
committed by GitHub
31 changed files with 1526 additions and 86 deletions
+196
View File
@@ -0,0 +1,196 @@
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) |
<br />
<br />
**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.3-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
**Option 1: Environment variable**
```python showLineNumbers title="Custom API Base via env var"
import os
from litellm import completion
os.environ["CRUSOE_API_BASE"] = "https://custom.crusoecloud.com/v1"
os.environ["CRUSOE_API_KEY"] = "" # your API key
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",
)
```
## 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`
+3 -66
View File
@@ -1,12 +1,12 @@
import ast
import logging
import os
import re
import sys
from datetime import datetime
from logging import Formatter
from typing import Any, Dict, List, Optional
from typing import Any, Dict, Optional
from litellm.litellm_core_utils.secret_redaction import redact_string
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
@@ -21,74 +21,11 @@ _ENABLE_SECRET_REDACTION = (
os.getenv("LITELLM_DISABLE_REDACT_SECRETS", "").lower() != "true"
)
_REDACTED = "REDACTED"
def _build_secret_patterns() -> re.Pattern:
patterns: List[str] = [
# ── PEM private key / certificate blocks ──
r"-----BEGIN[A-Z \-]*PRIVATE KEY-----[\s\S]*?-----END[A-Z \-]*PRIVATE KEY-----",
# ── GCP OAuth2 access tokens (ya29.*) ──
r"\bya29\.[A-Za-z0-9_.~+/-]+",
# ── Credential %s formatting (space separator, no key= prefix) ──
r"(?:client_secret|azure_password|azure_username)\s+[^\s,'\"})\]{}>]+",
# AWS access key IDs
r"(?:AKIA|ASIA)[0-9A-Z]{16}",
# AWS secrets / session tokens / access key IDs (key=value)
r"(?:aws_secret_access_key|aws_session_token|aws_access_key_id)"
r"\s*[:=]\s*[A-Za-z0-9/+=]{20,}",
# Bearer tokens (OAuth, JWT, etc.)
r"Bearer\s+[A-Za-z0-9\-._~+/]{10,}=*",
# Basic auth headers
r"Basic\s+[A-Za-z0-9+/]{10,}={0,2}",
# OpenAI / Anthropic sk- prefixed keys
r"sk-[A-Za-z0-9\-_]{20,}",
# Generic api_key / api-key / apikey (handles 'key': 'value' dict repr)
r"(?:api[_-]?key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]{8,}",
# x-api-key / api-key header values (handles 'key': 'value' dict repr)
r"(?:x-api-key|api-key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+",
# Anthropic internal header keys
r"x-ak-[A-Za-z0-9\-_]{20,}",
# Google API keys
r"AIza[0-9A-Za-z\-_]{35}",
# Password / secret params (handles key=value and 'key': 'value')
# Word boundary prevents O(n^2) backtracking on long word-char runs.
r"(?:^|(?<=\W))\w*(?:password|passwd|client_secret|secret_key|_secret)"
r"['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+",
# Database connection string credentials (scheme://user:pass@host)
r"(?<=://)[^\s'\"]*:[^\s'\"@]+(?=@)",
# Databricks personal access tokens
r"dapi[0-9a-f]{32}",
# ── Key-name-based redaction ──
# Catches secrets inside dicts/config dumps by matching on the KEY name
# regardless of what the value looks like.
# e.g. 'master_key': 'any-value-here', "database_url": "postgres://..."
# private_key with PEM-aware value capture
r"""private_key['\"]?\s*[:=]\s*['\"]?(?:-----BEGIN[A-Z \-]*PRIVATE KEY-----[\s\S]*?-----END[A-Z \-]*PRIVATE KEY-----|[^\s,'\"})\]{}>]+)""",
r"(?:master_key|database_url|db_url|connection_string|"
r"signing_key|encryption_key|"
r"auth_token|access_token|refresh_token|"
r"slack_webhook_url|webhook_url|"
r"database_connection_string|"
r"huggingface_token|jwt_secret)"
r"""['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+""",
# ── Raw JWTs (without Bearer prefix) ──
r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*",
# ── Azure SAS tokens in URLs ──
r"[?&]sig=[A-Za-z0-9%+/=]+",
# ── Full JSON service-account blobs (single-line and multi-line) ──
r'\{[^{}]*"type"\s*:\s*"service_account"[^{}]*(?:\{[^{}]*\}[^{}]*)*\}',
]
return re.compile("|".join(patterns), re.IGNORECASE)
_SECRET_RE = _build_secret_patterns()
def _redact_string(value: str) -> str:
if not _ENABLE_SECRET_REDACTION:
return value
return _SECRET_RE.sub(_REDACTED, value)
return redact_string(value)
def redact_secrets(value: str) -> str:
+5 -1
View File
@@ -513,7 +513,10 @@ def cost_per_token( # noqa: PLR0915
return fireworks_ai_cost_per_token(model=model, usage=usage_block)
elif custom_llm_provider == "azure":
return azure_openai_cost_per_token(
model=model, usage=usage_block, response_time_ms=response_time_ms
model=model,
usage=usage_block,
response_time_ms=response_time_ms,
service_tier=service_tier,
)
elif custom_llm_provider == "gemini":
return gemini_cost_per_token(
@@ -539,6 +542,7 @@ def cost_per_token( # noqa: PLR0915
usage=usage_block,
response_time_ms=response_time_ms,
request_model=request_model,
service_tier=service_tier,
)
else:
model_info = _cached_get_model_info_helper(
@@ -6,7 +6,8 @@ from typing import Any, Optional
import httpx
import litellm
from litellm._logging import _redact_string, verbose_logger
from litellm._logging import _ENABLE_SECRET_REDACTION, _redact_string, verbose_logger
from litellm.litellm_core_utils.secret_redaction import redact_string
from litellm.types.utils import LlmProviders
from ..exceptions import (
@@ -261,10 +262,18 @@ def exception_type( # type: ignore # noqa: PLR0915
original_exception=original_exception
)
try:
error_str = str(original_exception)
error_str = (
redact_string(str(original_exception))
if _ENABLE_SECRET_REDACTION
else str(original_exception)
)
if model:
if hasattr(original_exception, "message"):
error_str = str(original_exception.message)
error_str = (
redact_string(str(original_exception.message))
if _ENABLE_SECRET_REDACTION
else str(original_exception.message)
)
if isinstance(original_exception, BaseException):
exception_type = type(original_exception).__name__
else:
@@ -2431,7 +2440,8 @@ def exception_type( # type: ignore # noqa: PLR0915
else:
raise APIConnectionError(
message="{}\n{}".format(
str(original_exception), _redact_string(traceback.format_exc())
str(original_exception),
_redact_string(traceback.format_exc()),
),
llm_provider=custom_llm_provider,
model=model,
@@ -2461,7 +2471,8 @@ def exception_type( # type: ignore # noqa: PLR0915
raise e # it's already mapped
raised_exc = APIConnectionError(
message="{}\n{}".format(
original_exception, _redact_string(traceback.format_exc())
original_exception,
_redact_string(traceback.format_exc()),
),
llm_provider="",
model="",
@@ -0,0 +1,81 @@
"""
Credential/secret redaction utilities.
This module owns the compiled regex and the public `redact_string` helper so
that any part of the codebase (logging, exception mapping, etc.) can scrub
secrets from strings without depending on the logging-configuration module.
"""
import re
from typing import List
_REDACTED = "REDACTED"
def _build_secret_patterns() -> "re.Pattern[str]":
patterns: List[str] = [
# PEM private key / certificate blocks
r"-----BEGIN[A-Z \-]*PRIVATE KEY-----[\s\S]*?-----END[A-Z \-]*PRIVATE KEY-----",
# GCP OAuth2 access tokens (ya29.*)
r"\bya29\.[A-Za-z0-9_.~+/-]+",
# Credential %s formatting (space separator, no key= prefix)
r"(?:client_secret|azure_password|azure_username)\s+[^\s,'\"})\]{}>]+",
# AWS access key IDs
r"(?:AKIA|ASIA)[0-9A-Z]{16}",
# AWS secrets / session tokens / access key IDs (key=value)
r"(?:aws_secret_access_key|aws_session_token|aws_access_key_id)"
r"\s*[:=]\s*[A-Za-z0-9/+=]{20,}",
# Bearer tokens (OAuth, JWT, etc.)
r"Bearer\s+[A-Za-z0-9\-._~+/]{10,}=*",
# Basic auth headers
r"Basic\s+[A-Za-z0-9+/]{10,}={0,2}",
# OpenAI / Anthropic sk- prefixed keys
r"sk-[A-Za-z0-9\-_]{20,}",
# Generic api_key / api-key / apikey (handles 'key': 'value' dict repr)
r"(?:api[_-]?key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]{8,}",
# x-api-key / api-key header values (handles 'key': 'value' dict repr)
r"(?:x-api-key|api-key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+",
# Anthropic internal header keys
r"x-ak-[A-Za-z0-9\-_]{20,}",
# Google API keys (bare key value)
r"AIza[0-9A-Za-z\-_]{35}",
# URL query-param key=VALUE (e.g. ?key=AIza... or &key=...) — catches the
# full "key=<secret>" fragment so the value is redacted regardless of format.
r"(?<=[?&])key=[^\s&'\"]{8,}",
# Password / secret params (handles key=value and 'key': 'value')
# Word boundary prevents O(n^2) backtracking on long word-char runs.
r"(?:^|(?<=\W))\w*(?:password|passwd|client_secret|secret_key|_secret)"
r"['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+",
# Database connection string credentials (scheme://user:pass@host)
r"(?<=://)[^\s'\"]*:[^\s'\"@]+(?=@)",
# Databricks personal access tokens
r"dapi[0-9a-f]{32}",
# ── Key-name-based redaction ──
# Catches secrets inside dicts/config dumps by matching on the KEY name
# regardless of what the value looks like.
# e.g. 'master_key': 'any-value-here', "database_url": "postgres://..."
# private_key with PEM-aware value capture
r"""private_key['\"]?\s*[:=]\s*['\"]?(?:-----BEGIN[A-Z \-]*PRIVATE KEY-----[\s\S]*?-----END[A-Z \-]*PRIVATE KEY-----|[^\s,'\"})\]{}>]+)""",
r"(?:master_key|database_url|db_url|connection_string|"
r"signing_key|encryption_key|"
r"auth_token|access_token|refresh_token|"
r"slack_webhook_url|webhook_url|"
r"database_connection_string|"
r"huggingface_token|jwt_secret)"
r"""['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+""",
# Raw JWTs (without Bearer prefix)
r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*",
# Azure SAS tokens in URLs
r"[?&]sig=[A-Za-z0-9%+/=]+",
# Full JSON service-account blobs (single-line and multi-line)
r'\{[^{}]*"type"\s*:\s*"service_account"[^{}]*(?:\{[^{}]*\}[^{}]*)*\}',
]
return re.compile("|".join(patterns), re.IGNORECASE)
_SECRET_RE = _build_secret_patterns()
def redact_string(value: str) -> str:
"""Scrub known secret/credential patterns from *value* and return the result."""
return _SECRET_RE.sub(_REDACTED, value)
+1
View File
@@ -793,6 +793,7 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM):
client=client,
litellm_params=litellm_params,
api_base=api_base,
api_version=api_version,
)
azure_client = self.get_azure_openai_client(
api_version=api_version,
+5 -1
View File
@@ -12,7 +12,10 @@ from litellm.utils import get_model_info
def cost_per_token(
model: str, usage: Usage, response_time_ms: Optional[float] = 0.0
model: str,
usage: Usage,
response_time_ms: Optional[float] = 0.0,
service_tier: Optional[str] = None,
) -> Tuple[float, float]:
"""
Calculates the cost per token for a given model, prompt tokens, and completion tokens.
@@ -47,4 +50,5 @@ def cost_per_token(
model=model,
usage=usage,
custom_llm_provider="azure",
service_tier=service_tier,
)
+2
View File
@@ -65,6 +65,7 @@ def cost_per_token(
usage: Usage,
response_time_ms: Optional[float] = 0.0,
request_model: Optional[str] = None,
service_tier: Optional[str] = None,
) -> Tuple[float, float]:
"""
Calculate the cost per token for Azure AI models.
@@ -102,6 +103,7 @@ def cost_per_token(
model=model,
usage=usage,
custom_llm_provider="azure_ai",
service_tier=service_tier,
)
except Exception as e:
# For Model Router, the model name (e.g., "azure-model-router") may not be in the cost map
+8
View File
@@ -106,5 +106,13 @@
"base_url": "https://aihubmix.com/v1",
"api_key_env": "AIHUBMIX_API_KEY",
"api_base_env": "AIHUBMIX_API_BASE"
},
"crusoe": {
"base_url": "https://managed-inference-api-proxy.crusoecloud.com/v1",
"api_key_env": "CRUSOE_API_KEY",
"api_base_env": "CRUSOE_API_BASE",
"param_mappings": {
"max_completion_tokens": "max_tokens"
}
}
}
@@ -212,6 +212,22 @@ def _process_gemini_media(
return _apply_gemini_metadata(
part, model, media_resolution_enum, video_metadata
)
elif image_url.startswith(
"https://generativelanguage.googleapis.com/v1beta/files/"
):
# Gemini Files API URIs — the file is already uploaded to Google's
# servers; pass the URI through as file_data without fetching it.
# These URLs return 403 when accessed directly, so we must not try
# to resolve their MIME type via HTTP.
if format:
file_data = FileDataType(mime_type=format, file_uri=image_url)
else:
# Gemini Files API references can be passed through as URI-only.
file_data = cast(FileDataType, {"file_uri": image_url})
part = {"file_data": file_data}
return _apply_gemini_metadata(
part, model, media_resolution_enum, video_metadata
)
elif (
"https://" in image_url
and (image_type := format or _get_image_mime_type_from_url(image_url))
@@ -22061,6 +22061,98 @@
"tool_use_system_prompt_tokens": 346,
"supports_native_structured_output": true
},
"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_reasoning": true,
"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,
"supports_vision": 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_reasoning": true,
"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",
@@ -0,0 +1,35 @@
from typing import TYPE_CHECKING
from litellm.types.guardrails import SupportedGuardrailIntegrations
from .qohash import QostodianNexus
if TYPE_CHECKING:
from litellm.types.guardrails import Guardrail, LitellmParams
def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"):
import litellm
_instance = QostodianNexus(
api_base=litellm_params.api_base,
guardrail_name=guardrail.get("guardrail_name", ""),
event_hook=litellm_params.mode,
default_on=litellm_params.default_on,
additional_provider_specific_params=litellm_params.additional_provider_specific_params,
extra_headers=getattr(litellm_params, "extra_headers", None),
)
litellm.logging_callback_manager.add_litellm_callback(_instance)
return _instance
guardrail_initializer_registry = {
SupportedGuardrailIntegrations.QOSTODIAN_NEXUS.value: initialize_guardrail,
}
guardrail_class_registry = {
SupportedGuardrailIntegrations.QOSTODIAN_NEXUS.value: QostodianNexus,
}
@@ -0,0 +1,81 @@
"""
Qostodian Nexus (by Qohash) LiteLLM guardrail integration.
"""
import os
from typing import TYPE_CHECKING, Literal, Optional, Type
from litellm.integrations.custom_guardrail import log_guardrail_information
from litellm.proxy.guardrails.guardrail_hooks.generic_guardrail_api.generic_guardrail_api import (
GenericGuardrailAPI,
)
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
from litellm.types.utils import GenericGuardrailAPIInputs
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
GUARDRAIL_NAME = "qostodian_nexus"
class QostodianNexus(GenericGuardrailAPI):
def __init__(
self,
api_base: Optional[str] = None,
**kwargs,
):
api_base = api_base or os.environ.get(
"QOSTODIAN_NEXUS_API_BASE", "http://nexus:8800"
)
kwargs["guardrail_name"] = kwargs.get("guardrail_name", GUARDRAIL_NAME)
# Merge built-in Qostodian Nexus identifier headers with any caller-supplied extra_headers
nexus_headers = [
"x-qostodian-nexus-identifiers-trace",
"x-qostodian-nexus-identifiers-source",
"x-qostodian-nexus-identifiers-container",
"x-qostodian-nexus-identifiers-identity",
]
existing = kwargs.get("extra_headers") or []
kwargs["extra_headers"] = nexus_headers + [
h for h in existing if h not in nexus_headers
]
super().__init__(
api_base=api_base,
**kwargs,
)
@log_guardrail_information
async def apply_guardrail(
self,
inputs: GenericGuardrailAPIInputs,
request_data: dict,
input_type: Literal["request", "response"],
logging_obj: Optional["LiteLLMLoggingObj"] = None,
) -> GenericGuardrailAPIInputs:
"""
Apply Qostodian Nexus to the given inputs.
NOTE: This override is intentionally a pass-through. It must be present
directly in this class's __dict__ so that LiteLLM's unified guardrail
routing check (`"apply_guardrail" in type(callback).__dict__` in
litellm/proxy/utils.py) routes calls correctly. Do not remove.
"""
return await super().apply_guardrail(
inputs=inputs,
request_data=request_data,
input_type=input_type,
logging_obj=logging_obj,
)
@classmethod
def get_config_model(cls) -> Optional[Type[QostodianNexusConfigModel]]:
"""
Returns the config model for Qostodian Nexus.
"""
return QostodianNexusConfigModel
+3
View File
@@ -3188,6 +3188,8 @@ class PrismaClient:
t.organization_id as org_id,
p.project_alias AS project_alias,
tm.spend AS team_member_spend,
b_tm.tpm_limit AS team_member_tpm_limit,
b_tm.rpm_limit AS team_member_rpm_limit,
m.aliases AS team_model_aliases,
-- Added comma to separate b.* columns
b.max_budget AS litellm_budget_table_max_budget,
@@ -3203,6 +3205,7 @@ class PrismaClient:
FROM "LiteLLM_VerificationToken" AS v
LEFT JOIN "LiteLLM_TeamTable" AS t ON v.team_id = t.team_id
LEFT JOIN "LiteLLM_TeamMembership" AS tm ON v.team_id = tm.team_id AND tm.user_id = v.user_id
LEFT JOIN "LiteLLM_BudgetTable" AS b_tm ON tm.budget_id = b_tm.budget_id
LEFT JOIN "LiteLLM_ModelTable" m ON t.model_id = m.id
LEFT JOIN "LiteLLM_BudgetTable" AS b ON v.budget_id = b.budget_id
LEFT JOIN "LiteLLM_ProjectTable" AS p ON v.project_id = p.project_id
+5
View File
@@ -38,6 +38,9 @@ from litellm.types.proxy.guardrails.guardrail_hooks.tool_permission import (
from litellm.types.proxy.guardrails.guardrail_hooks.hiddenlayer import (
HiddenlayerGuardrailConfigModel,
)
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
"""
Pydantic object defining how to set guardrails on litellm proxy
@@ -96,6 +99,7 @@ class SupportedGuardrailIntegrations(Enum):
AKTO = "akto"
MCP_JWT_SIGNER = "mcp_jwt_signer"
LLM_AS_A_JUDGE = "llm_as_a_judge"
QOSTODIAN_NEXUS = "qostodian_nexus"
class Role(Enum):
@@ -773,6 +777,7 @@ class LitellmParams(
QualifireGuardrailConfigModel,
BlockCodeExecutionGuardrailConfigModel,
HiddenlayerGuardrailConfigModel,
QostodianNexusConfigModel,
):
guardrail: str = Field(description="The type of guardrail integration to use")
mode: Union[str, List[str], Mode] = Field(
@@ -0,0 +1,16 @@
from typing import Optional
from pydantic import Field
from .base import GuardrailConfigModel
class QostodianNexusConfigModel(GuardrailConfigModel):
api_base: Optional[str] = Field(
default=None,
description="The API base URL for Qostodian Nexus. If not provided, the `QOSTODIAN_NEXUS_API_BASE` environment variable is checked. Defaults to http://nexus:8800.",
)
@staticmethod
def ui_friendly_name() -> str:
return "Qostodian Nexus"
+92
View File
@@ -22109,6 +22109,98 @@
"tool_use_system_prompt_tokens": 346,
"supports_native_structured_output": true
},
"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_reasoning": true,
"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,
"supports_vision": 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_reasoning": true,
"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",
+18
View File
@@ -635,6 +635,24 @@
"interactions": true
}
},
"crusoe": {
"display_name": "Crusoe (`crusoe`)",
"url": "https://docs.litellm.ai/docs/providers/crusoe",
"endpoints": {
"chat_completions": true,
"messages": true,
"responses": true,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"moderations": false,
"batches": false,
"rerank": false,
"a2a": true,
"interactions": true
}
},
"custom": {
"display_name": "Custom (`custom`)",
"url": "https://docs.litellm.ai/docs/providers/custom_llm_server",
@@ -0,0 +1,94 @@
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.."))
)
from litellm.llms.azure.azure import AzureChatCompletion
from litellm.types.utils import EmbeddingResponse, Usage
def _make_embedding_response() -> EmbeddingResponse:
return EmbeddingResponse(
model="text-embedding-3-large",
usage=Usage(prompt_tokens=3, completion_tokens=0, total_tokens=3),
data=[{"embedding": [0.1, 0.2, 0.3], "index": 0, "object": "embedding"}],
)
def _make_logging_obj() -> MagicMock:
return MagicMock()
class TestAzureV1AsyncEmbedding:
def test_aembedding_receives_api_version(self):
"""Regression: api_version must be forwarded to aembedding() when aembedding=True.
Without the fix, it was silently dropped, causing AsyncAzureOpenAI to be used
instead of AsyncOpenAI for Azure AI Foundry (v1) endpoints. Fixes #24848."""
handler = AzureChatCompletion()
with patch.object(handler, "aembedding") as mock_aembedding:
handler.embedding(
model="text-embedding-3-large",
input=["hello world"],
api_base="https://my-endpoint.openai.azure.com",
api_version="v1",
timeout=60.0,
logging_obj=_make_logging_obj(),
model_response=_make_embedding_response(),
optional_params={},
api_key="fake-key",
aembedding=True,
litellm_params={},
)
mock_aembedding.assert_called_once()
_, kwargs = mock_aembedding.call_args
assert kwargs.get("api_version") == "v1"
def test_get_azure_openai_client_returns_async_openai_for_v1(self):
from openai import AsyncAzureOpenAI, AsyncOpenAI
handler = AzureChatCompletion()
client = handler.get_azure_openai_client(
api_key="fake-key",
api_base="https://my-endpoint.openai.azure.com",
api_version="v1",
_is_async=True,
litellm_params={},
)
assert isinstance(client, AsyncOpenAI)
assert not isinstance(client, AsyncAzureOpenAI)
def test_get_azure_openai_client_uses_v1_base_url(self):
handler = AzureChatCompletion()
client = handler.get_azure_openai_client(
api_key="fake-key",
api_base="https://my-endpoint.openai.azure.com",
api_version="v1",
_is_async=True,
litellm_params={},
)
assert client is not None
assert "/openai/v1/" in str(client.base_url)
@pytest.mark.parametrize("api_version", ["v1", "latest", "preview"])
def test_all_v1_variants_use_openai_client(self, api_version: str):
from openai import AsyncOpenAI
handler = AzureChatCompletion()
client = handler.get_azure_openai_client(
api_key="fake-key",
api_base="https://my-endpoint.openai.azure.com",
api_version=api_version,
_is_async=True,
litellm_params={},
)
assert isinstance(client, AsyncOpenAI)
+108
View File
@@ -0,0 +1,108 @@
"""
Tests for Crusoe provider integration
"""
import os
from unittest import mock
import litellm
CRUSOE_API_BASE = "https://managed-inference-api-proxy.crusoecloud.com/v1"
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"""
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):
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"
def test_crusoe_models_configuration():
"""Test that Crusoe models are configured correctly"""
from litellm import get_model_info
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/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"
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
@@ -0,0 +1,75 @@
"""
Test Azure OpenAI cost calculator service_tier pricing.
"""
import pytest
import litellm
from litellm.llms.azure.cost_calculation import cost_per_token
from litellm.types.utils import Usage
# Register a test model with tier-specific pricing
TEST_MODEL = "test-azure-gpt-4.1"
TEST_MODEL_COST = {
TEST_MODEL: {
"input_cost_per_token": 0.001,
"output_cost_per_token": 0.002,
"input_cost_per_token_priority": 0.01,
"output_cost_per_token_priority": 0.02,
"input_cost_per_token_flex": 0.0005,
"output_cost_per_token_flex": 0.001,
"litellm_provider": "azure",
"max_tokens": 8192,
}
}
class TestAzureServiceTierCostCalculation:
"""Test that service_tier is passed through Azure cost calculation."""
@pytest.fixture(autouse=True)
def register_test_model(self):
litellm.register_model(model_cost=TEST_MODEL_COST)
def test_service_tier_priority_higher_cost(self):
"""Priority tier should cost more than standard."""
usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
standard_prompt, standard_completion = cost_per_token(
model=TEST_MODEL, usage=usage
)
priority_prompt, priority_completion = cost_per_token(
model=TEST_MODEL, usage=usage, service_tier="priority"
)
assert priority_prompt > standard_prompt
assert priority_completion > standard_completion
def test_service_tier_flex_lower_cost(self):
"""Flex tier should cost less than standard."""
usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
standard_prompt, standard_completion = cost_per_token(
model=TEST_MODEL, usage=usage
)
flex_prompt, flex_completion = cost_per_token(
model=TEST_MODEL, usage=usage, service_tier="flex"
)
assert flex_prompt < standard_prompt
assert flex_completion < standard_completion
def test_service_tier_none_returns_standard(self):
"""service_tier=None should return standard pricing."""
usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
none_prompt, none_completion = cost_per_token(
model=TEST_MODEL, usage=usage, service_tier=None
)
standard_prompt, standard_completion = cost_per_token(
model=TEST_MODEL, usage=usage, service_tier="standard"
)
assert abs(none_prompt - standard_prompt) < 1e-10
assert abs(none_completion - standard_completion) < 1e-10
@@ -451,3 +451,51 @@ class TestAzureModelRouterCostBreakdown:
assert logging_obj.cost_breakdown["additional_costs"][
"Azure Model Router Flat Cost"
] == pytest.approx(expected_flat_cost, rel=1e-9)
class TestAzureAIServiceTierCostCalculation:
"""Test that service_tier is passed through Azure AI cost calculation."""
@pytest.fixture(autouse=True)
def register_test_model(self):
import litellm
litellm.register_model(model_cost={
"test-azure-ai-model": {
"input_cost_per_token": 0.001,
"output_cost_per_token": 0.002,
"input_cost_per_token_priority": 0.01,
"output_cost_per_token_priority": 0.02,
"input_cost_per_token_flex": 0.0005,
"output_cost_per_token_flex": 0.001,
"litellm_provider": "azure_ai",
"max_tokens": 8192,
}
})
def test_service_tier_priority_higher_cost(self):
"""Priority tier should cost more than standard for azure_ai."""
usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
standard_prompt, standard_completion = cost_per_token(
model="test-azure-ai-model", usage=usage
)
priority_prompt, priority_completion = cost_per_token(
model="test-azure-ai-model", usage=usage, service_tier="priority"
)
assert priority_prompt > standard_prompt
assert priority_completion > standard_completion
def test_service_tier_flex_lower_cost(self):
"""Flex tier should cost less than standard for azure_ai."""
usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
standard_prompt, standard_completion = cost_per_token(
model="test-azure-ai-model", usage=usage
)
flex_prompt, flex_completion = cost_per_token(
model="test-azure-ai-model", usage=usage, service_tier="flex"
)
assert flex_prompt < standard_prompt
assert flex_completion < standard_completion
@@ -0,0 +1,135 @@
import os
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_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
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
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"
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
@@ -1361,6 +1361,53 @@ def test_file_data_field_order_gcs_urls():
), "mime_type must come before file_uri in the file_data dict"
def test_gemini_files_api_uri_without_format():
"""
Test that Gemini Files API URIs work WITHOUT an explicit format/mime_type.
When a user uploads a file via the Gemini Files API and then references it
by URI (https://generativelanguage.googleapis.com/v1beta/files/...),
the file is already on Google's servers. These URLs return 403 when
fetched directly, so _process_gemini_media must NOT try to resolve the
MIME type via HTTP. Instead it should pass the URI through as file_data
and let the Gemini API resolve the type from its stored metadata.
Related issue: https://github.com/BerriAI/litellm/issues/24907
"""
from litellm.llms.vertex_ai.gemini.transformation import _process_gemini_media
file_url = "https://generativelanguage.googleapis.com/v1beta/files/37eh7rsw1vfe"
# Should NOT raise — previously this hit the generic https:// handler
# which called _get_image_mime_type_from_url() and got a 403.
result = _process_gemini_media(image_url=file_url)
assert "file_data" in result
file_data = result["file_data"]
assert file_data["file_uri"] == file_url
# When no format is provided, mime_type should be absent so the
# Gemini API infers it from the stored file metadata.
assert "mime_type" not in file_data
def test_gemini_files_api_uri_with_format():
"""
Test that Gemini Files API URIs correctly forward an explicit format.
Related issue: https://github.com/BerriAI/litellm/issues/24907
"""
from litellm.llms.vertex_ai.gemini.transformation import _process_gemini_media
file_url = "https://generativelanguage.googleapis.com/v1beta/files/n1vhxa28lyaw"
result = _process_gemini_media(image_url=file_url, format="text/plain")
assert "file_data" in result
file_data = result["file_data"]
assert file_data["file_uri"] == file_url
assert file_data["mime_type"] == "text/plain"
def test_extract_file_data_with_path_object():
"""
Test that filename is correctly extracted from Path objects for MIME type detection.
@@ -0,0 +1,252 @@
"""
Unit tests for Qostodian Nexus (by Qohash) integration.
Tests verify:
1. QostodianNexus can be instantiated with default and custom values
2. Qostodian Nexus is registered in SupportedGuardrailIntegrations
3. Guardrail initializer and class registries contain Qostodian Nexus
4. Configuration parameters are properly passed through
5. QostodianNexusConfigModel works correctly
"""
import os
import pytest
from unittest.mock import MagicMock
def test_qostodian_nexus_initialization_with_defaults():
"""Test QostodianNexus initializes with default values."""
import os
from unittest.mock import patch
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
# Unset env var so the hardcoded default is used
env = {k: v for k, v in os.environ.items() if k != "QOSTODIAN_NEXUS_API_BASE"}
with patch.dict(os.environ, env, clear=True):
guardrail = QostodianNexus()
# Should use default api_base
assert guardrail.api_base is not None
assert "nexus:8800" in guardrail.api_base
def test_qostodian_nexus_initialization_with_custom_api_base():
"""Test QostodianNexus initializes with custom api_base."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
custom_api_base = "http://custom-nexus:9000"
guardrail = QostodianNexus(api_base=custom_api_base)
assert custom_api_base in guardrail.api_base
def test_qostodian_nexus_in_supported_guardrail_integrations():
"""Test that Qostodian Nexus is registered in SupportedGuardrailIntegrations enum."""
from litellm.types.guardrails import SupportedGuardrailIntegrations
# Check enum contains QOSTODIAN_NEXUS
assert hasattr(SupportedGuardrailIntegrations, "QOSTODIAN_NEXUS")
assert SupportedGuardrailIntegrations.QOSTODIAN_NEXUS.value == "qostodian_nexus"
# Check it's in the list of all values
all_values = [e.value for e in SupportedGuardrailIntegrations]
assert "qostodian_nexus" in all_values
def test_qostodian_nexus_in_guardrail_initializer_registry():
"""Test that Qostodian Nexus is registered in guardrail_initializer_registry."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import (
guardrail_initializer_registry,
)
assert "qostodian_nexus" in guardrail_initializer_registry
assert callable(guardrail_initializer_registry["qostodian_nexus"])
def test_qostodian_nexus_in_guardrail_class_registry():
"""Test that Qostodian Nexus is registered in guardrail_class_registry."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import (
guardrail_class_registry,
QostodianNexus,
)
assert "qostodian_nexus" in guardrail_class_registry
assert guardrail_class_registry["qostodian_nexus"] == QostodianNexus
def test_qostodian_nexus_config_model_initialization():
"""Test QostodianNexusConfigModel can be instantiated."""
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
config = QostodianNexusConfigModel(
api_base="http://test:8800",
)
assert config.api_base == "http://test:8800"
def test_qostodian_nexus_config_model_defaults():
"""Test QostodianNexusConfigModel uses correct defaults."""
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
config = QostodianNexusConfigModel()
assert config.api_base is None
def test_qostodian_nexus_config_model_ui_friendly_name():
"""Test QostodianNexusConfigModel returns correct UI friendly name."""
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
ui_name = QostodianNexusConfigModel.ui_friendly_name()
assert ui_name == "Qostodian Nexus"
def test_qostodian_nexus_initializer_function():
"""Test the initialize_guardrail function."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import initialize_guardrail
from litellm.types.guardrails import LitellmParams, Guardrail
from unittest.mock import patch
# Mock litellm.logging_callback_manager
with patch("litellm.logging_callback_manager") as mock_manager:
mock_manager.add_litellm_callback = MagicMock()
# Create test params
litellm_params = LitellmParams(
guardrail="qostodian_nexus",
mode="pre_call",
api_base="http://test:8800",
default_on=True,
)
guardrail_config: Guardrail = {"guardrail_name": "test-qostodian-nexus"}
# Call initializer
result = initialize_guardrail(litellm_params, guardrail_config)
# Verify callback was added
mock_manager.add_litellm_callback.assert_called_once()
# Verify returned instance has correct properties
assert result is not None
assert "test:8800" in result.api_base
def test_qostodian_nexus_inherits_from_generic_guardrail_api():
"""Test that QostodianNexus inherits from GenericGuardrailAPI."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
from litellm.proxy.guardrails.guardrail_hooks.generic_guardrail_api.generic_guardrail_api import (
GenericGuardrailAPI,
)
assert issubclass(QostodianNexus, GenericGuardrailAPI)
def test_qostodian_nexus_guardrail_name_constant():
"""Test that GUARDRAIL_NAME constant is defined correctly."""
from litellm.proxy.guardrails.guardrail_hooks.qohash.qohash import GUARDRAIL_NAME
assert GUARDRAIL_NAME == "qostodian_nexus"
def test_qostodian_nexus_get_config_model():
"""Test that QostodianNexus returns the correct config model."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
config_model = QostodianNexus.get_config_model()
assert config_model is not None
assert config_model == QostodianNexusConfigModel
def test_qostodian_nexus_env_vars():
"""Test that QOSTODIAN_NEXUS_API_BASE env var is picked up correctly."""
import os
from unittest.mock import patch
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
with patch.dict(os.environ, {"QOSTODIAN_NEXUS_API_BASE": "http://new-api:8800"}):
guardrail = QostodianNexus()
assert "new-api:8800" in guardrail.api_base
def test_qostodian_nexus_config_model_field_descriptions():
"""Test that QostodianNexusConfigModel has correct field descriptions."""
from litellm.types.proxy.guardrails.guardrail_hooks.qohash import (
QostodianNexusConfigModel,
)
# Check that field descriptions mention the correct env vars
api_base_field = QostodianNexusConfigModel.model_fields["api_base"]
assert "QOSTODIAN_NEXUS_API_BASE" in api_base_field.description
def test_qostodian_nexus_unified_detection():
"""
Test that QostodianNexus is properly detected by LiteLLM's unified guardrail system.
This verifies the fix for the detection bug where QostodianNexus wasn't being
recognized because apply_guardrail was only inherited, not in the class's own __dict__.
"""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
# Create an instance (this is how LiteLLM uses it)
instance = QostodianNexus(api_base="http://test:8800")
# Test the exact detection logic used in litellm/proxy/utils.py:868
# use_unified = "apply_guardrail" in type(callback).__dict__
use_unified = "apply_guardrail" in type(instance).__dict__
# Should be detected as using unified guardrail system
assert use_unified is True, (
"QostodianNexus should be detected by unified guardrail system. "
"The apply_guardrail method must be present in QostodianNexus.__dict__"
)
# Also verify the method is callable
assert hasattr(instance, "apply_guardrail")
assert callable(instance.apply_guardrail)
def test_qostodian_nexus_builtin_extra_headers():
"""Test that QostodianNexus includes built-in x-qostodian-nexus-identifiers-* headers."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
instance = QostodianNexus()
expected_headers = [
"x-qostodian-nexus-identifiers-trace",
"x-qostodian-nexus-identifiers-source",
"x-qostodian-nexus-identifiers-container",
"x-qostodian-nexus-identifiers-identity",
]
for header in expected_headers:
assert header in instance.extra_headers, (
f"Expected built-in header '{header}' to be in extra_headers"
)
def test_qostodian_nexus_extra_headers_merged():
"""Test that caller-supplied extra_headers are merged with built-in headers."""
from litellm.proxy.guardrails.guardrail_hooks.qohash import QostodianNexus
custom_header = "x-custom-correlation-id"
instance = QostodianNexus(extra_headers=[custom_header])
# Built-in headers should be present
assert "x-qostodian-nexus-identifiers-trace" in instance.extra_headers
# Custom header should also be present
assert custom_header in instance.extra_headers
# No duplicates
assert len(instance.extra_headers) == len(set(instance.extra_headers))
@@ -1029,6 +1029,83 @@ async def test_team_member_rate_limits_v3():
), "Team member TPM limit should be set"
@pytest.mark.asyncio
async def test_team_member_rate_limits_v3_raises_429_when_over_limit():
"""
When should_rate_limit reports OVER_LIMIT for the team_member descriptor, the
pre-call hook raises HTTP 429 with rate_limit headers same contract as
test_rpm_api_key_rate_limits_v3 / test_tpm_api_key_rate_limits_v3.
"""
_api_key = hash_token("sk-12345")
_team_id = "team_123"
_user_id = "user_456"
user_api_key_dict = UserAPIKeyAuth(
api_key=_api_key,
team_id=_team_id,
user_id=_user_id,
team_member_rpm_limit=10,
team_member_tpm_limit=1000,
)
local_cache = DualCache()
parallel_request_handler = _PROXY_MaxParallelRequestsHandler(
internal_usage_cache=InternalUsageCache(local_cache)
)
captured_descriptors = None
async def mock_should_rate_limit(descriptors, **kwargs):
nonlocal captured_descriptors
captured_descriptors = descriptors
return {
"overall_code": "OVER_LIMIT",
"statuses": [
{
"code": "OVER_LIMIT",
"current_limit": 10,
"limit_remaining": -1,
"rate_limit_type": "requests",
"descriptor_key": "team_member",
},
{
"code": "OK",
"current_limit": 1000,
"limit_remaining": 500,
"rate_limit_type": "tokens",
"descriptor_key": "team_member",
},
],
}
parallel_request_handler.should_rate_limit = mock_should_rate_limit
error = None
try:
await parallel_request_handler.async_pre_call_hook(
user_api_key_dict=user_api_key_dict,
cache=local_cache,
data={"model": "gpt-3.5-turbo"},
call_type="",
)
except HTTPException as e:
error = e
assert e.status_code == 429
assert "rate_limit_type" in e.headers
assert e.headers.get("rate_limit_type") == "requests"
assert "retry-after" in e.headers
assert error is not None, "An Exception must be thrown"
assert captured_descriptors is not None, "Rate limit descriptors should be captured"
team_member_descriptor = None
for descriptor in captured_descriptors:
if descriptor["key"] == "team_member":
team_member_descriptor = descriptor
break
assert team_member_descriptor is not None
assert team_member_descriptor["value"] == f"{_team_id}:{_user_id}"
@pytest.mark.asyncio
async def test_dynamic_rate_limiting_v3():
"""
+13 -13
View File
@@ -9,11 +9,11 @@ from litellm._logging import (
JsonFormatter,
_redact_string,
_secret_filter,
_setup_json_exception_handlers,
verbose_logger,
verbose_proxy_logger,
verbose_router_logger,
)
from litellm.litellm_core_utils.secret_redaction import redact_string
SECRET = "sk-proj-abc123def456ghi789jklmnopqrst"
@@ -57,12 +57,12 @@ def test_redact_string_catches_secret_patterns():
SECRET,
]
for secret in cases:
result = _redact_string("msg: " + secret)
result = redact_string("msg: " + secret)
assert secret not in result, f"{secret!r} was not redacted"
assert "REDACTED" in result
normal = "Loaded model gpt-4 with 3 replicas on us-east-1"
assert _redact_string(normal) == normal
assert redact_string(normal) == normal
def test_filter_redacts_secrets_in_logger_output():
@@ -155,7 +155,7 @@ def test_x_api_key_regex_does_not_consume_json_delimiters():
"""x-api-key pattern must stop before closing quotes/braces so JSON stays valid."""
# Simulates a JSON log line containing an x-api-key header value
json_line = '{"headers": {"x-api-key": "secret123"}, "status": 200}'
result = _redact_string(json_line)
result = redact_string(json_line)
# The secret value should be redacted
assert "secret123" not in result
assert "REDACTED" in result
@@ -234,12 +234,12 @@ def test_key_name_redaction_catches_secrets_in_dict_repr():
"'slack_webhook_url': 'https://hooks.slack.com/services/T00/B00/xxx'",
]
for secret_line in cases:
result = _redact_string(secret_line)
result = redact_string(secret_line)
assert "REDACTED" in result, f"Key-name redaction missed: {secret_line!r}"
# Non-sensitive keys should NOT be redacted
safe = "'enable_jwt_auth': True, 'store_model_in_db': True"
assert _redact_string(safe) == safe
assert redact_string(safe) == safe
def test_key_name_redaction_in_general_settings_dict():
@@ -277,7 +277,7 @@ _SAMPLE_SA_JSON = (
def test_pem_private_key_redacted_in_json():
result = _redact_string(_SAMPLE_SA_JSON)
result = redact_string(_SAMPLE_SA_JSON)
assert "MIIEvQIBADA" not in result
assert "-----BEGIN" not in result
@@ -286,12 +286,12 @@ def test_pem_private_key_redacted_in_dict_repr():
import json
sa = json.loads(_SAMPLE_SA_JSON)
result = _redact_string(str(sa))
result = redact_string(str(sa))
assert "MIIEvQIBADA" not in result
def test_service_account_blob_fully_redacted():
result = _redact_string(f"Got={_SAMPLE_SA_JSON}")
result = redact_string(f"Got={_SAMPLE_SA_JSON}")
assert "my-proj-123" not in result
assert "sa@my-proj.iam.gserviceaccount.com" not in result
assert "abc123def" not in result
@@ -320,22 +320,22 @@ def test_vertex_traceback_redacts_pem():
"Unable to load vertex credentials from environment. "
f"Got={_SAMPLE_SA_JSON}"
)
result = _redact_string(traceback_text)
result = redact_string(traceback_text)
assert "MIIEvQIBADA" not in result
assert "-----BEGIN" not in result
def test_gcp_oauth_token_redacted():
result = _redact_string("access token ya29.c.c0ASRK0GZvXlongtokenhere")
result = redact_string("access token ya29.c.c0ASRK0GZvXlongtokenhere")
assert "ya29." not in result
assert "REDACTED" in result
def test_non_pem_private_key_value_redacted():
result = _redact_string("'private_key': 'some-non-pem-secret-value'")
result = redact_string("'private_key': 'some-non-pem-secret-value'")
assert "some-non-pem-secret" not in result
def test_normal_vertex_log_not_redacted():
msg = "Vertex: Loading vertex credentials, is_file_path=True, current dir /app"
assert _redact_string(msg) == msg
assert redact_string(msg) == msg
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -52,6 +52,7 @@ export const guardrail_provider_map: Record<string, string> = {
Promptguard: "promptguard",
LlmAsAJudge: "llm_as_a_judge",
Xecguard: "xecguard",
QostodianNexus: "qostodian_nexus",
};
// Function to populate provider map from API response - updates the original map
@@ -138,6 +139,7 @@ export const guardrailLogoMap: Record<string, string> = {
"LiteLLM Content Filter": `${asset_logos_folder}litellm_logo.jpg`,
"LiteLLM LLM as a Judge": `${asset_logos_folder}litellm_logo.jpg`,
"Akto": `${asset_logos_folder}akto.svg`,
"Qostodian Nexus": `${asset_logos_folder}qohash.jpg`,
};
export const getGuardrailLogoAndName = (guardrailValue: string): { logo: string; displayName: string } => {