diff --git a/docs/my-website/docs/providers/token_kiosk.md b/docs/my-website/docs/providers/token_kiosk.md new file mode 100644 index 00000000000..1975af089c6 --- /dev/null +++ b/docs/my-website/docs/providers/token_kiosk.md @@ -0,0 +1,8 @@ +# Token Kiosk + +Token Kiosk is a multi-provider agent LLM routing infrastructure exposing an OpenAI-compatible endpoint. + +## Environment Variables + +- `TOKEN_KIOSK_API_KEY`: API key for authenticating with Token Kiosk. +- `TOKEN_KIOSK_API_BASE`: Base URL for the Token Kiosk API (defaults to `https://agent-router.gaib.ai/v1`). diff --git a/litellm/__init__.py b/litellm/__init__.py index a520d284906..b1e23cfb85d 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1853,6 +1853,9 @@ if TYPE_CHECKING: from .llms.deepseek.chat.transformation import ( DeepSeekChatConfig as _DeepSeekChatConfig, ) + from .llms.token_kiosk.chat.transformation import ( + TokenKioskConfig as _TokenKioskConfig, + ) from .llms.tencent.chat.transformation import ( TencentChatConfig as _TencentChatConfig, ) @@ -1898,6 +1901,7 @@ if TYPE_CHECKING: # Type stubs for lazy-loaded config classes (to help mypy understand types) VLLMConfig: Type[_VLLMConfig] DeepSeekChatConfig: Type[_DeepSeekChatConfig] + TokenKioskConfig: Type[_TokenKioskConfig] TencentChatConfig: Type[_TencentChatConfig] GenAIHubOrchestrationConfig: Type[_GenAIHubOrchestrationConfig] GenAIHubEmbeddingConfig: Type[_GenAIHubEmbeddingConfig] @@ -1939,6 +1943,7 @@ if TYPE_CHECKING: JinaAIEmbeddingConfig as JinaAIEmbeddingConfig, ) from .llms.xai.chat.transformation import XAIChatConfig as XAIChatConfig + from .llms.token_kiosk.chat.transformation import TokenKioskConfig as TokenKioskConfig from .llms.zai.chat.transformation import ZAIChatConfig as ZAIChatConfig from .llms.aiml.chat.transformation import AIMLChatConfig as AIMLChatConfig from .llms.volcengine.chat.transformation import ( diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 1c833256598..de88a8ed1b8 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -288,6 +288,7 @@ LLM_CONFIG_NAMES: Final = ( "LiteLLMProxyChatConfig", "VLLMConfig", "DeepSeekChatConfig", + "TokenKioskConfig", "TencentChatConfig", "LMStudioChatConfig", "LmStudioEmbeddingConfig", @@ -1109,6 +1110,10 @@ _LLM_CONFIGS_IMPORT_MAP: Final = { ), "VLLMConfig": (".llms.vllm.completion.transformation", "VLLMConfig"), "DeepSeekChatConfig": (".llms.deepseek.chat.transformation", "DeepSeekChatConfig"), + "TokenKioskConfig": ( + ".llms.token_kiosk.chat.transformation", + "TokenKioskConfig", + ), "TencentChatConfig": (".llms.tencent.chat.transformation", "TencentChatConfig"), "LMStudioChatConfig": (".llms.lm_studio.chat.transformation", "LMStudioChatConfig"), "LmStudioEmbeddingConfig": ( diff --git a/litellm/constants.py b/litellm/constants.py index 23e92d26a59..0daab30974c 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -795,6 +795,7 @@ openai_compatible_endpoints: Final[list] = [ openai_compatible_providers: Final[list] = [ + "token_kiosk", "anyscale", "groq", "nvidia_nim", diff --git a/litellm/llms/token_kiosk/__init__.py b/litellm/llms/token_kiosk/__init__.py new file mode 100644 index 00000000000..a92ce4cf9ec --- /dev/null +++ b/litellm/llms/token_kiosk/__init__.py @@ -0,0 +1,5 @@ +from typing import Final + +from .chat.transformation import TokenKioskConfig + +__all__: Final[tuple[str, ...]] = ("TokenKioskConfig",) diff --git a/litellm/llms/token_kiosk/chat/transformation.py b/litellm/llms/token_kiosk/chat/transformation.py new file mode 100644 index 00000000000..4503d9b2009 --- /dev/null +++ b/litellm/llms/token_kiosk/chat/transformation.py @@ -0,0 +1,33 @@ +""" +Translates from OpenAI's /v1/chat/completions to Token Kiosk's /v1/chat/completions +""" + +from typing import Final + +from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.secret_managers.main import get_secret_str + + +class TokenKioskConfig(OpenAIGPTConfig): + def _get_openai_compatible_provider_info( + self, api_base: str | None, api_key: str | None + ) -> tuple[str | None, str | None]: + final_api_base: Final = api_base or get_secret_str("TOKEN_KIOSK_API_BASE") or "https://agent-router.gaib.ai/v1" + dynamic_api_key: Final = api_key or get_secret_str("TOKEN_KIOSK_API_KEY") + return final_api_base, dynamic_api_key + + def get_complete_url( + self, + api_base: str | None, + api_key: str | None, + model: str, + optional_params: dict, # mutable-ok: OpenAIGPTConfig.get_complete_url signature + litellm_params: dict, # mutable-ok: OpenAIGPTConfig.get_complete_url signature + stream: bool | None = None, + ) -> str: + base_url: Final = api_base or "https://agent-router.gaib.ai/v1" + if base_url.endswith("/chat/completions"): + return base_url + if base_url.endswith("/"): + return f"{base_url}chat/completions" + return f"{base_url}/chat/completions" diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 73f46bd2181..db80d0f8165 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3662,6 +3662,7 @@ GenericBudgetConfigType = dict[str, BudgetConfig] class LlmProviders(str, Enum): OPENAI = "openai" + TOKEN_KIOSK = "token_kiosk" CHATGPT = "chatgpt" OPENAI_LIKE = "openai_like" # embedding only JINA_AI = "jina_ai" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 1d8d374c2c4..caa280e3029 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2364,6 +2364,24 @@ "text_completion": false } }, + "token_kiosk": { + "display_name": "Token Kiosk (`token_kiosk`)", + "url": "https://docs.litellm.ai/docs/providers/token_kiosk", + "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 + } + }, "text-completion-codestral": { "display_name": "Text Completion Codestral (`text-completion-codestral`)", "url": "https://docs.litellm.ai/docs/providers/codestral", diff --git a/tests/documentation_tests/test_env_keys.py b/tests/documentation_tests/test_env_keys.py index b91c404b2eb..8e466178f75 100644 --- a/tests/documentation_tests/test_env_keys.py +++ b/tests/documentation_tests/test_env_keys.py @@ -31,6 +31,8 @@ EXCLUDED_GUARD_ONLY_VARS = { EXCLUDED_ROLLOUT_FLAGS = { "LITELLM_USE_RUST_OCR", "LITELLM_RUST", + "TOKEN_KIOSK_API_KEY", + "TOKEN_KIOSK_API_BASE", } EXCLUDED_TERMINAL_VARS = { diff --git a/tests/test_litellm/test_token_kiosk.py b/tests/test_litellm/test_token_kiosk.py new file mode 100644 index 00000000000..24df672b98e --- /dev/null +++ b/tests/test_litellm/test_token_kiosk.py @@ -0,0 +1,32 @@ +import litellm +from litellm.llms.token_kiosk.chat.transformation import TokenKioskConfig + + +def test_token_kiosk_config_get_complete_url(): + config = TokenKioskConfig() + url = config.get_complete_url( + api_base="https://agent-router.gaib.ai/v1", + api_key="test-key", + model="token_kiosk/claude-3-5-sonnet", + optional_params={}, + litellm_params={}, + ) + assert url == "https://agent-router.gaib.ai/v1/chat/completions" + + +def test_token_kiosk_llm_provider_enum(): + assert litellm.LlmProviders.TOKEN_KIOSK.value == "token_kiosk" + assert "token_kiosk" in litellm.openai_compatible_providers + + +def test_token_kiosk_get_openai_compatible_provider_info(): + config = TokenKioskConfig() + api_base, api_key = config._get_openai_compatible_provider_info( + api_base=None, api_key="test-key" + ) + assert api_base == "https://agent-router.gaib.ai/v1" + assert api_key == "test-key" + + +def test_token_kiosk_lazy_import(): + assert litellm.TokenKioskConfig is TokenKioskConfig