diff --git a/litellm/llms/zai/chat/transformation.py b/litellm/llms/zai/chat/transformation.py index e0e42d772f8..34c4fc58c6c 100644 --- a/litellm/llms/zai/chat/transformation.py +++ b/litellm/llms/zai/chat/transformation.py @@ -17,7 +17,12 @@ class ZAIChatConfig(OpenAIGPTConfig): self, api_base: str | None, api_key: str | None ) -> tuple[str | None, str | None]: api_base = api_base or get_secret_str("ZAI_API_BASE") or ZAI_API_BASE - dynamic_api_key: Final = api_key or get_secret_str("ZAI_API_KEY") + dynamic_api_key: Final = ( + api_key + or get_secret_str("ZAI_API_KEY") + or get_secret_str("BIGMODEL_API_KEY") + or get_secret_str("ZHIPUAI_API_KEY") + ) return api_base, dynamic_api_key def get_openai_compatible_provider_info( @@ -43,6 +48,8 @@ class ZAIChatConfig(OpenAIGPTConfig): def get_supported_openai_params(self, model: str) -> list: base_params: Final = [ "max_tokens", + "max_completion_tokens", + "response_format", "stream", "stream_options", "temperature", diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 9a7d88b8eda..736589dc734 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -54934,6 +54934,21 @@ "supports_reasoning": true, "supports_tool_choice": true }, + "zai/GLM-5.3": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "zai/glm-5.3-flash": { "cache_creation_input_token_cost": 0, "cache_read_input_token_cost": 3e-08, @@ -54950,6 +54965,46 @@ "source": "https://docs.z.ai/guides/overview/pricing", "supports_vision": true }, + "zai/GLM-5.3-Flash": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 3e-08, + "input_cost_per_token": 1.5e-07, + "output_cost_per_token": 5e-07, + "litellm_provider": "zai", + "max_input_tokens": 1048576, + "max_output_tokens": 128000, + "mode": "chat", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_vision": true + }, + "zai/glm-5v-turbo": { + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "zai/GLM-5V-Turbo": { + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "zai/glm-5.1": { "cache_creation_input_token_cost": 0, "cache_read_input_token_cost": 2.6e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 9a7d88b8eda..736589dc734 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -54934,6 +54934,21 @@ "supports_reasoning": true, "supports_tool_choice": true }, + "zai/GLM-5.3": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "zai/glm-5.3-flash": { "cache_creation_input_token_cost": 0, "cache_read_input_token_cost": 3e-08, @@ -54950,6 +54965,46 @@ "source": "https://docs.z.ai/guides/overview/pricing", "supports_vision": true }, + "zai/GLM-5.3-Flash": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 3e-08, + "input_cost_per_token": 1.5e-07, + "output_cost_per_token": 5e-07, + "litellm_provider": "zai", + "max_input_tokens": 1048576, + "max_output_tokens": 128000, + "mode": "chat", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_vision": true + }, + "zai/glm-5v-turbo": { + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "zai/GLM-5V-Turbo": { + "input_cost_per_token": 1.4e-06, + "litellm_provider": "zai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://docs.z.ai/guides/overview/pricing", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "zai/glm-5.1": { "cache_creation_input_token_cost": 0, "cache_read_input_token_cost": 2.6e-07, diff --git a/tests/unit/test_zai_glm_5_3_metadata.py b/tests/unit/test_zai_glm_5_3_metadata.py new file mode 100644 index 00000000000..8175d64fea6 --- /dev/null +++ b/tests/unit/test_zai_glm_5_3_metadata.py @@ -0,0 +1,99 @@ +import json +from collections.abc import Mapping +from pathlib import Path +from typing import Final + +import pytest + +import litellm +from litellm.llms.zai.chat.transformation import ZAIChatConfig + +REPO_ROOT: Final = Path(__file__).parents[2] +MAIN_PATH: Final = REPO_ROOT / "model_prices_and_context_window.json" +BACKUP_PATH: Final = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json" + +ZAI_API_KEY_ENV_VARS: Final = ("ZAI_API_KEY", "BIGMODEL_API_KEY", "ZHIPUAI_API_KEY") +LEGACY_API_KEY_ENV_VARS: Final = ("BIGMODEL_API_KEY", "ZHIPUAI_API_KEY") +NEW_ZAI_MODEL_KEYS: Final = ("zai/GLM-5.3", "zai/GLM-5.3-Flash", "zai/glm-5v-turbo", "zai/GLM-5V-Turbo") + + +def _load(path: Path) -> Mapping[str, object]: + with open(path) as f: + return json.load(f) + + +@pytest.fixture +def local_model_cost_map(monkeypatch: pytest.MonkeyPatch) -> None: + """Resolve get_model_info against the in-repo cost map instead of the remote one + fetched at import time.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + litellm.get_model_info.cache_clear() + yield + litellm.get_model_info.cache_clear() + + +@pytest.mark.parametrize("legacy_env_var", LEGACY_API_KEY_ENV_VARS) +def test_provider_info_resolves_legacy_api_key(monkeypatch: pytest.MonkeyPatch, legacy_env_var: str) -> None: + for env_var in ZAI_API_KEY_ENV_VARS: + monkeypatch.delenv(env_var, raising=False) + monkeypatch.setenv(legacy_env_var, "legacy-secret") + + api_base, api_key = ZAIChatConfig()._get_openai_compatible_provider_info(api_base=None, api_key=None) + + assert api_key == "legacy-secret" + assert api_base == "https://api.z.ai/api/paas/v4" + + +def test_provider_info_prefers_zai_api_key_over_legacy(monkeypatch: pytest.MonkeyPatch) -> None: + for env_var in ZAI_API_KEY_ENV_VARS: + monkeypatch.setenv(env_var, f"{env_var}-secret") + + _, api_key = ZAIChatConfig()._get_openai_compatible_provider_info(api_base=None, api_key=None) + + assert api_key == "ZAI_API_KEY-secret" + + +def test_supported_openai_params_include_new_params() -> None: + params: Final = ZAIChatConfig().get_supported_openai_params(model="zai/glm-5.3") + + assert "response_format" in params + assert "max_completion_tokens" in params + + +@pytest.mark.parametrize( + ("alias", "canonical"), + [ + ("zai/GLM-5.3", "zai/glm-5.3"), + ("zai/GLM-5.3-Flash", "zai/glm-5.3-flash"), + ("zai/GLM-5V-Turbo", "zai/glm-5v-turbo"), + ], +) +def test_uppercase_alias_model_info_matches_canonical(local_model_cost_map: None, alias: str, canonical: str) -> None: + alias_info: Final = litellm.get_model_info(alias) + canonical_info: Final = litellm.get_model_info(canonical) + + assert alias_info["key"] == alias + assert {k: v for k, v in alias_info.items() if k != "key"} == { + k: v for k, v in canonical_info.items() if k != "key" + } + + +def test_glm_5v_turbo_model_info(local_model_cost_map: None) -> None: + info: Final = litellm.get_model_info("zai/glm-5v-turbo") + + assert info["litellm_provider"] == "zai" + assert info["mode"] == "chat" + assert info["supports_vision"] is True + assert info["supports_function_calling"] is True + assert info["supports_tool_choice"] is True + + +def test_new_zai_entries_match_between_main_and_backup() -> None: + main_cost: Final = _load(MAIN_PATH) + backup_cost: Final = _load(BACKUP_PATH) + + for model in NEW_ZAI_MODEL_KEYS: + assert backup_cost.get(model) == main_cost.get(model), ( + f"{model} differs between main and backup model cost maps" + )