mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
Merge 3a9257d3c6 into 252c71c0b2
This commit is contained in:
commit
8b40af7c6e
12 changed files with 6650 additions and 0 deletions
|
|
@ -366,6 +366,7 @@ For MCP OAuth, an upstream may advertise dynamic client registration but refuse
|
|||
| [Text Completion Codestral (`text-completion-codestral`)](https://docs.litellm.ai/docs/providers/codestral) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Text Completion OpenAI (`text-completion-openai`)](https://docs.litellm.ai/docs/providers/text_completion_openai) | ✅ | ✅ | ✅ | | | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [Together AI (`together_ai`)](https://docs.litellm.ai/docs/providers/togetherai) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [TokenLab (`tokenlab`)](https://docs.tokenlab.sh/integrations/litellm) | ✅ | ✅ | ✅ | ✅ | | | | | | |
|
||||
| [Topaz (`topaz`)](https://docs.litellm.ai/docs/providers/topaz) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Triton (`triton`)](https://docs.litellm.ai/docs/providers/triton-inference-server) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [V0 (`v0`)](https://docs.litellm.ai/docs/providers/v0) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
|
|
@ -680,6 +680,7 @@ qwen_ai_platform_models: Set = set()
|
|||
moonshot_models: Set = set()
|
||||
publicai_models: Set = set()
|
||||
darkbloom_models: Set = set()
|
||||
tokenlab_models: Set = set() # mutable-ok: populated while loading the static model registry
|
||||
v0_models: Set = set()
|
||||
morph_models: Set = set()
|
||||
lambda_ai_models: Set = set()
|
||||
|
|
@ -939,6 +940,8 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None:
|
|||
publicai_models.add(key)
|
||||
elif value.get("litellm_provider") == "darkbloom":
|
||||
darkbloom_models.add(key)
|
||||
elif value.get("litellm_provider") == "tokenlab":
|
||||
tokenlab_models.add(key)
|
||||
elif value.get("litellm_provider") == "v0":
|
||||
v0_models.add(key)
|
||||
elif value.get("litellm_provider") == "morph":
|
||||
|
|
@ -1099,6 +1102,7 @@ model_list = list(
|
|||
| moonshot_models
|
||||
| publicai_models
|
||||
| darkbloom_models
|
||||
| tokenlab_models
|
||||
| v0_models
|
||||
| morph_models
|
||||
| lambda_ai_models
|
||||
|
|
@ -1208,6 +1212,7 @@ def _build_models_by_provider() -> dict:
|
|||
"moonshot": moonshot_models,
|
||||
"publicai": publicai_models,
|
||||
"darkbloom": darkbloom_models,
|
||||
"tokenlab": tokenlab_models,
|
||||
"v0": v0_models,
|
||||
"morph": morph_models,
|
||||
"lambda_ai": lambda_ai_models,
|
||||
|
|
|
|||
|
|
@ -893,6 +893,7 @@ openai_compatible_endpoints: Final[list] = [
|
|||
"https://api.inference.wandb.ai/v1",
|
||||
"https://api.clarifai.com/v2/ext/openai/v1",
|
||||
"https://api.libertai.io/v1",
|
||||
"https://api.tokenlab.sh/v1",
|
||||
"https://pinstripes.io/v1",
|
||||
"https://api.meta.ai/v1",
|
||||
"https://api.cognition.ai/v1",
|
||||
|
|
@ -965,6 +966,7 @@ openai_compatible_providers: Final[list] = [
|
|||
"docker_model_runner",
|
||||
"ragflow",
|
||||
"pinstripes", # Pinstripes - JSON-configured provider
|
||||
"tokenlab", # TokenLab - JSON-configured provider
|
||||
"darkbloom",
|
||||
"meta", # Meta Model API (Muse Spark) - JSON-configured provider
|
||||
"cognition",
|
||||
|
|
|
|||
|
|
@ -363,6 +363,10 @@ def get_llm_provider(
|
|||
elif endpoint == "https://api.inference.wandb.ai/v1":
|
||||
custom_llm_provider = "wandb"
|
||||
dynamic_api_key = get_secret_str("WANDB_API_KEY")
|
||||
elif endpoint == "https://api.tokenlab.sh/v1":
|
||||
custom_llm_provider = "tokenlab"
|
||||
tokenlab_config: Final = JSONProviderRegistry.get("tokenlab")
|
||||
dynamic_api_key = get_secret_str(tokenlab_config.api_key_env) if tokenlab_config else None
|
||||
elif endpoint == "https://pinstripes.io/v1":
|
||||
custom_llm_provider = "pinstripes"
|
||||
dynamic_api_key = get_secret_str("PINSTRIPES_API_KEY")
|
||||
|
|
|
|||
|
|
@ -78,6 +78,14 @@ class JSONProviderRegistry:
|
|||
return False
|
||||
return "/v1/responses" in provider.supported_endpoints
|
||||
|
||||
@classmethod
|
||||
def supports_anthropic_messages_api(cls, slug: str) -> bool:
|
||||
"""Check if a JSON provider supports the Anthropic Messages API"""
|
||||
provider = cls._providers.get(slug)
|
||||
if provider is None:
|
||||
return False
|
||||
return "/v1/messages" in provider.supported_endpoints
|
||||
|
||||
@classmethod
|
||||
def list_providers(cls) -> list:
|
||||
"""List all registered provider slugs"""
|
||||
|
|
|
|||
|
|
@ -180,6 +180,15 @@
|
|||
"api_key_env": "COGNITION_API_KEY",
|
||||
"api_base_env": "COGNITION_API_BASE"
|
||||
},
|
||||
"tokenlab": {
|
||||
"base_url": "https://api.tokenlab.sh/v1",
|
||||
"api_key_env": "TOKENLAB_API_KEY",
|
||||
"api_base_env": "TOKENLAB_API_BASE",
|
||||
"param_mappings": {
|
||||
"max_completion_tokens": "max_tokens"
|
||||
},
|
||||
"supported_endpoints": ["/v1/chat/completions", "/v1/messages", "/v1/responses", "/v1/embeddings"]
|
||||
},
|
||||
"pinstripes": {
|
||||
"base_url": "https://pinstripes.io/v1",
|
||||
"api_key_env": "PINSTRIPES_API_KEY",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1628,6 +1628,23 @@
|
|||
"a2a": false
|
||||
}
|
||||
},
|
||||
"tokenlab": {
|
||||
"display_name": "TokenLab (`tokenlab`)",
|
||||
"url": "https://docs.tokenlab.sh/integrations/litellm",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": false,
|
||||
"a2a": false
|
||||
}
|
||||
},
|
||||
"nvidia_nim": {
|
||||
"display_name": "Nvidia NIM (`nvidia_nim`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/nvidia_nim",
|
||||
|
|
|
|||
|
|
@ -4042,6 +4042,7 @@ class LlmProviders(str, Enum):
|
|||
PARASAIL = "parasail"
|
||||
XIAOMI_MIMO = "xiaomi_mimo"
|
||||
TENSORMESH = "tensormesh"
|
||||
TOKENLAB = "tokenlab"
|
||||
LIBERTAI = "libertai"
|
||||
PINSTRIPES = "pinstripes"
|
||||
COGNITION = "cognition"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2366,6 +2366,23 @@
|
|||
"a2a": false
|
||||
}
|
||||
},
|
||||
"tokenlab": {
|
||||
"display_name": "TokenLab (`tokenlab`)",
|
||||
"url": "https://docs.tokenlab.sh/integrations/litellm",
|
||||
"endpoints": {
|
||||
"chat_completions": true,
|
||||
"messages": true,
|
||||
"responses": true,
|
||||
"embeddings": true,
|
||||
"image_generations": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": false,
|
||||
"a2a": false
|
||||
}
|
||||
},
|
||||
"tensormesh": {
|
||||
"display_name": "Tensormesh (`tensormesh`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/tensormesh",
|
||||
|
|
|
|||
176
tests/test_litellm/llms/openai_like/test_tokenlab_provider.py
Normal file
176
tests/test_litellm/llms/openai_like/test_tokenlab_provider.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import pytest
|
||||
|
||||
import litellm
|
||||
|
||||
|
||||
TOKENLAB_API_BASE = "https://api.tokenlab.sh/v1"
|
||||
TOKENLAB_SAMPLE_MODELS = [
|
||||
"tokenlab/gpt-5.5",
|
||||
"tokenlab/claude-opus-4-8",
|
||||
"tokenlab/gemini-3.5-flash",
|
||||
"tokenlab/deepseek-v4-pro",
|
||||
"tokenlab/qwen3.7-max",
|
||||
"tokenlab/text-embedding-3-small",
|
||||
]
|
||||
|
||||
|
||||
def test_tokenlab_json_registry():
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
assert litellm.LlmProviders.TOKENLAB.value == "tokenlab"
|
||||
assert litellm.LlmProviders("tokenlab") == litellm.LlmProviders.TOKENLAB
|
||||
assert JSONProviderRegistry.exists("tokenlab")
|
||||
config = JSONProviderRegistry.get("tokenlab")
|
||||
assert config is not None
|
||||
assert config.base_url == TOKENLAB_API_BASE
|
||||
assert config.api_key_env == "TOKENLAB_API_KEY"
|
||||
assert config.api_base_env == "TOKENLAB_API_BASE"
|
||||
assert config.param_mappings["max_completion_tokens"] == "max_tokens"
|
||||
assert config.supported_endpoints == [
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages",
|
||||
"/v1/responses",
|
||||
"/v1/embeddings",
|
||||
]
|
||||
assert JSONProviderRegistry.supports_responses_api("tokenlab") is True
|
||||
assert JSONProviderRegistry.supports_anthropic_messages_api("tokenlab") is True
|
||||
|
||||
|
||||
def test_tokenlab_provider_detection_by_prefix():
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
model, provider, _, api_base = get_llm_provider("tokenlab/gpt-5.5")
|
||||
|
||||
assert model == "gpt-5.5"
|
||||
assert provider == "tokenlab"
|
||||
assert api_base == TOKENLAB_API_BASE
|
||||
|
||||
|
||||
def test_tokenlab_api_base_override():
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
_, provider, api_key, api_base = get_llm_provider(
|
||||
model="tokenlab/gpt-5.5",
|
||||
api_base="https://custom.tokenlab.example/v1",
|
||||
api_key="sk-test",
|
||||
)
|
||||
|
||||
assert provider == "tokenlab"
|
||||
assert api_base == "https://custom.tokenlab.example/v1"
|
||||
assert api_key == "sk-test"
|
||||
|
||||
|
||||
def test_tokenlab_url_autodetection():
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
|
||||
_, provider, _, api_base = get_llm_provider(
|
||||
model="gpt-5.5",
|
||||
api_base=TOKENLAB_API_BASE,
|
||||
)
|
||||
|
||||
assert provider == "tokenlab"
|
||||
assert api_base == TOKENLAB_API_BASE
|
||||
|
||||
|
||||
def test_tokenlab_chat_complete_url():
|
||||
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("tokenlab"))()
|
||||
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="gpt-5.5",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== "https://api.tokenlab.sh/v1/chat/completions"
|
||||
)
|
||||
|
||||
|
||||
def test_tokenlab_responses_api_config():
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
provider="tokenlab",
|
||||
model="tokenlab/gpt-5.5",
|
||||
)
|
||||
|
||||
assert isinstance(config, OpenAIResponsesAPIConfig)
|
||||
assert config.custom_llm_provider == "tokenlab"
|
||||
assert config.get_complete_url(api_base=None, litellm_params={}) == "https://api.tokenlab.sh/v1/responses"
|
||||
|
||||
|
||||
def test_tokenlab_anthropic_messages_config():
|
||||
from litellm.llms.openai_like.messages.transformation import (
|
||||
OpenAILikeAnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
provider=litellm.LlmProviders.TOKENLAB,
|
||||
model="claude-opus-4-8",
|
||||
)
|
||||
|
||||
assert isinstance(config, OpenAILikeAnthropicMessagesConfig)
|
||||
assert (
|
||||
config.get_complete_url(
|
||||
api_base=TOKENLAB_API_BASE,
|
||||
api_key="sk-test",
|
||||
model="claude-opus-4-8",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
== "https://api.tokenlab.sh/v1/messages"
|
||||
)
|
||||
|
||||
|
||||
class TestTokenLabCostMap:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _use_local_model_cost_map(self, monkeypatch):
|
||||
original_model_cost = litellm.model_cost
|
||||
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
litellm.get_model_info.cache_clear()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
litellm.model_cost = original_model_cost
|
||||
litellm.get_model_info.cache_clear()
|
||||
|
||||
def test_sample_models_registered(self):
|
||||
for model in TOKENLAB_SAMPLE_MODELS:
|
||||
info = litellm.get_model_info(model)
|
||||
assert info["litellm_provider"] == "tokenlab"
|
||||
assert info["mode"] in {"chat", "embedding"}
|
||||
|
||||
def test_claude_model_cost_map_keeps_schema_supported_endpoints(self):
|
||||
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
|
||||
|
||||
endpoints = litellm.model_cost["tokenlab/claude-opus-4-8"]["supported_endpoints"]
|
||||
assert endpoints == ["/v1/chat/completions", "/v1/responses"]
|
||||
|
||||
provider_config = JSONProviderRegistry.get("tokenlab")
|
||||
assert provider_config is not None
|
||||
assert "/v1/messages" in provider_config.supported_endpoints
|
||||
|
||||
def test_chat_cost_is_wired(self):
|
||||
prompt_cost, completion_cost = litellm.cost_per_token(
|
||||
model="tokenlab/gpt-5.5",
|
||||
prompt_tokens=1_000_000,
|
||||
completion_tokens=1_000_000,
|
||||
)
|
||||
assert prompt_cost == pytest.approx(1.5)
|
||||
assert completion_cost == pytest.approx(9.0)
|
||||
|
||||
def test_embedding_cost_is_wired(self):
|
||||
prompt_cost, completion_cost = litellm.cost_per_token(
|
||||
model="tokenlab/text-embedding-3-small",
|
||||
prompt_tokens=1_000_000,
|
||||
completion_tokens=0,
|
||||
)
|
||||
assert prompt_cost == pytest.approx(0.02)
|
||||
assert completion_cost == 0
|
||||
Loading…
Add table
Reference in a new issue