diff --git a/litellm/constants.py b/litellm/constants.py index 6a503463a8c..0715b826574 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1059,6 +1059,7 @@ openai_compatible_endpoints: Final[list[str]] = [ "https://api.reka.ai/v1", "https://gigachat.devices.sberbank.ru/api/v1", "https://api.openinfer.ai/v1", + "https://api.engy.ai/v1", ] @@ -1137,6 +1138,7 @@ openai_compatible_providers: Final[list[str]] = [ "prism", "sail", "reka", + "engy", ] OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS: Final = frozenset({"openai"} | frozenset(openai_compatible_providers)) @@ -1165,6 +1167,7 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s "lambda_ai", "hyperbolic", "wandb", + "engy", ] _openai_like_providers: Final[list[str]] = [ "predibase", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 36337fddbdc..9ecc1e49532 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -249,5 +249,15 @@ "/v1/responses", "/v1/messages" ] + }, + "engy": { + "base_url": "https://api.engy.ai/v1", + "api_key_env": "ENGY_API_KEY", + "api_base_env": "ENGY_API_BASE", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ] } } diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 3d89af9d691..ab6205c483e 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -77018,5 +77018,181 @@ "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": false + }, + "engy/deepseek-v4.1-flash": { + "cache_read_input_token_cost": 8e-09, + "input_cost_per_token": 4e-08, + "litellm_provider": "engy", + "max_input_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 8e-08, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/deepseek-v4-flash-0731": { + "cache_read_input_token_cost": 9e-09, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 9e-08, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.2": { + "cache_read_input_token_cost": 1.8e-07, + "input_cost_per_token": 6.8e-07, + "litellm_provider": "engy", + "max_input_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 1.5e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.3": { + "cache_read_input_token_cost": 1.8e-07, + "input_cost_per_token": 9.8e-07, + "litellm_provider": "engy", + "max_input_tokens": 327680, + "max_tokens": 327680, + "mode": "chat", + "output_cost_per_token": 3.08e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.3-flash": { + "cache_read_input_token_cost": 2.7e-08, + "input_cost_per_token": 1.35e-07, + "litellm_provider": "engy", + "max_input_tokens": 327680, + "max_tokens": 327680, + "mode": "chat", + "output_cost_per_token": 4.5e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/kimi-k3": { + "cache_read_input_token_cost": 1.95e-07, + "input_cost_per_token": 1.95e-06, + "litellm_provider": "engy", + "max_input_tokens": 1113088, + "max_tokens": 1113088, + "mode": "chat", + "output_cost_per_token": 9.75e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/qwen3.6-35b-a3b": { + "cache_read_input_token_cost": 1.5e-08, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 208192, + "max_tokens": 208192, + "mode": "chat", + "output_cost_per_token": 3e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/qwen3.8-27b": { + "cache_read_input_token_cost": 1.5e-08, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 1001536, + "max_tokens": 1001536, + "mode": "chat", + "output_cost_per_token": 3.2e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true } } diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index f95b553c2b4..18626f63679 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -2748,6 +2748,24 @@ "rerank": false, "a2a": false } + }, + "engy": { + "display_name": "Engy (`engy`)", + "url": "https://docs.litellm.ai/docs/providers/engy", + "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": false, + "interactions": false + } } }, "endpoints": { diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 4bec42a7868..188505f76b4 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -4070,5 +4070,33 @@ } ], "default_model_placeholder": "cursor/claude-4-sonnet" + }, + { + "provider": "ENGY", + "provider_display_name": "Engy", + "litellm_provider": "engy", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://api.engy.ai/v1", + "tooltip": null, + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": true, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "engy/kimi-k3" } ] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 5939a350970..e0923d3a1c9 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4557,6 +4557,7 @@ class LlmProviders(str, Enum): CURSOR = "cursor" BEDROCK_MANTLE = "bedrock_mantle" GDC = "gdc" + ENGY = "engy" # Create a set of all provider values for quick lookup diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 3d89af9d691..ab6205c483e 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -77018,5 +77018,181 @@ "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": false + }, + "engy/deepseek-v4.1-flash": { + "cache_read_input_token_cost": 8e-09, + "input_cost_per_token": 4e-08, + "litellm_provider": "engy", + "max_input_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 8e-08, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/deepseek-v4-flash-0731": { + "cache_read_input_token_cost": 9e-09, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 9e-08, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.2": { + "cache_read_input_token_cost": 1.8e-07, + "input_cost_per_token": 6.8e-07, + "litellm_provider": "engy", + "max_input_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 1.5e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.3": { + "cache_read_input_token_cost": 1.8e-07, + "input_cost_per_token": 9.8e-07, + "litellm_provider": "engy", + "max_input_tokens": 327680, + "max_tokens": 327680, + "mode": "chat", + "output_cost_per_token": 3.08e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/glm-5.3-flash": { + "cache_read_input_token_cost": 2.7e-08, + "input_cost_per_token": 1.35e-07, + "litellm_provider": "engy", + "max_input_tokens": 327680, + "max_tokens": 327680, + "mode": "chat", + "output_cost_per_token": 4.5e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/kimi-k3": { + "cache_read_input_token_cost": 1.95e-07, + "input_cost_per_token": 1.95e-06, + "litellm_provider": "engy", + "max_input_tokens": 1113088, + "max_tokens": 1113088, + "mode": "chat", + "output_cost_per_token": 9.75e-06, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "engy/qwen3.6-35b-a3b": { + "cache_read_input_token_cost": 1.5e-08, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 208192, + "max_tokens": 208192, + "mode": "chat", + "output_cost_per_token": 3e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "engy/qwen3.8-27b": { + "cache_read_input_token_cost": 1.5e-08, + "input_cost_per_token": 4.5e-08, + "litellm_provider": "engy", + "max_input_tokens": 1001536, + "max_tokens": 1001536, + "mode": "chat", + "output_cost_per_token": 3.2e-07, + "source": "https://api.engy.ai/v1/models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/responses", + "/v1/messages" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true } } diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index d65da3f41fd..4c8dc797e4b 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -3272,6 +3272,24 @@ "rerank": false, "a2a": false } + }, + "engy": { + "display_name": "Engy (`engy`)", + "url": "https://docs.litellm.ai/docs/providers/engy", + "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": false, + "interactions": false + } } }, "endpoints": { diff --git a/tests/unit/llms/openai_like/test_engy_provider.py b/tests/unit/llms/openai_like/test_engy_provider.py new file mode 100644 index 00000000000..6fb59187c78 --- /dev/null +++ b/tests/unit/llms/openai_like/test_engy_provider.py @@ -0,0 +1,295 @@ +import json +from pathlib import Path +from typing import Final + +import httpx +import pytest +import respx + +import litellm +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + +ENGY_API_BASE: Final = "https://api.engy.ai/v1" +ENGY_MODEL: Final = "engy/kimi-k3" +ENGY_MODEL_ID: Final = "kimi-k3" + + +def test_engy_provider_resolution(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("ENGY_API_KEY", "engy-env-key") + + model, provider, api_key, api_base = get_llm_provider( + model=ENGY_MODEL, + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert model == ENGY_MODEL_ID + assert provider == "engy" + assert api_key == "engy-env-key" + assert api_base == ENGY_API_BASE + + +def test_engy_provider_keeps_explicit_credentials(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("ENGY_API_KEY", "engy-env-key") + + _, provider, api_key, api_base = get_llm_provider( + model=ENGY_MODEL, + custom_llm_provider=None, + api_base="https://engy.internal.example/v1", + api_key="engy-explicit-key", + ) + + assert provider == "engy" + assert api_key == "engy-explicit-key" + assert api_base == "https://engy.internal.example/v1" + + +def test_engy_provider_reads_base_from_env(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("ENGY_API_KEY", "engy-env-key") + monkeypatch.setenv("ENGY_API_BASE", "https://engy.internal.example/v1") + + _, provider, _, api_base = get_llm_provider( + model=ENGY_MODEL, + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert provider == "engy" + assert api_base == "https://engy.internal.example/v1" + + +def test_engy_provider_resolves_from_api_base(): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + _, provider, _, api_base = get_llm_provider( + model=ENGY_MODEL_ID, + custom_llm_provider=None, + api_base=ENGY_API_BASE, + api_key="engy-explicit-key", + ) + + assert provider == "engy" + assert api_base == ENGY_API_BASE + + +def test_engy_is_available_in_add_model_form(): + fields_path: Final = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" + providers: Final = json.loads(fields_path.read_text()) + engy: Final = next(provider for provider in providers if provider["litellm_provider"] == "engy") + + assert engy["provider"] == "ENGY" + assert engy["provider_display_name"] == "Engy" + assert engy["default_model_placeholder"].startswith("engy/") + assert {field["key"]: field["required"] for field in engy["credential_fields"]} == { + "api_base": False, + "api_key": True, + } + + +def test_engy_endpoint_support_matches_providers_json(): + matrix_path: Final = Path(litellm.__file__).parent / "provider_endpoints_support_backup.json" + endpoints: Final = json.loads(matrix_path.read_text())["providers"]["engy"]["endpoints"] + providers_json: Final = json.loads( + (Path(litellm.__file__).parent / "llms" / "openai_like" / "providers.json").read_text() + ) + declared: Final = set(providers_json["engy"]["supported_endpoints"]) + + supported_from_matrix: Final = { + "/v1/chat/completions": endpoints["chat_completions"], + "/v1/responses": endpoints["responses"], + "/v1/messages": endpoints["messages"], + } + assert {endpoint for endpoint, supported in supported_from_matrix.items() if supported} == declared + + +def test_engy_chat_completion_request(): + with respx.mock() as upstream: + route: Final = upstream.post(f"{ENGY_API_BASE}/chat/completions").respond( + 200, + json={ + "id": "chatcmpl_engy", + "object": "chat.completion", + "created": 1_789_550_000, + "model": ENGY_MODEL_ID, + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello from Engy"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7}, + }, + ) + response: Final = litellm.completion( + model=ENGY_MODEL, + messages=[{"role": "user", "content": "Say hello"}], + api_key="engy-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert str(request.url) == f"{ENGY_API_BASE}/chat/completions" + assert request.headers["authorization"] == "Bearer engy-test-key" + assert body["model"] == ENGY_MODEL_ID + assert body["messages"] == [{"role": "user", "content": "Say hello"}] + assert response.choices[0].message.content == "Hello from Engy" + + +def test_engy_responses_request(): + with respx.mock() as upstream: + route: Final = upstream.post(f"{ENGY_API_BASE}/responses").respond( + 200, + json={ + "id": "resp_engy", + "object": "response", + "created_at": 1_789_550_000, + "model": ENGY_MODEL_ID, + "status": "completed", + "output": [ + { + "id": "msg_engy", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Hello from Engy", "annotations": []}], + } + ], + "usage": {"input_tokens": 4, "output_tokens": 3, "total_tokens": 7}, + }, + ) + response: Final = litellm.responses( + model=ENGY_MODEL, + input="Say hello", + api_key="engy-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert str(request.url) == f"{ENGY_API_BASE}/responses" + assert request.headers["authorization"] == "Bearer engy-test-key" + assert body["model"] == ENGY_MODEL_ID + assert body["input"] == "Say hello" + assert response.output[0].content[0].text == "Hello from Engy" + + +@pytest.mark.asyncio +async def test_engy_anthropic_messages_request(): + with respx.mock() as upstream: + route: Final = upstream.post(f"{ENGY_API_BASE}/messages").respond( + 200, + json={ + "id": "msg_engy", + "type": "message", + "role": "assistant", + "model": ENGY_MODEL_ID, + "content": [{"type": "text", "text": "Hello from Engy"}], + "stop_reason": "end_turn", + "stop_sequence": None, + "usage": {"input_tokens": 4, "output_tokens": 3}, + }, + ) + response: Final = await litellm.anthropic.messages.acreate( + model=ENGY_MODEL, + messages=[{"role": "user", "content": "Say hello"}], + max_tokens=32, + api_key="engy-test-key", + client=AsyncHTTPHandler(transport=httpx.AsyncHTTPTransport()), + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert str(request.url) == f"{ENGY_API_BASE}/messages" + assert request.headers["authorization"] == "Bearer engy-test-key" + assert body["model"] == ENGY_MODEL_ID + assert body["messages"] == [{"role": "user", "content": "Say hello"}] + assert response["content"][0]["text"] == "Hello from Engy" + + +def test_engy_chat_completion_cost_bills_cached_tokens_at_cache_read_rate(): + row: Final = litellm.model_cost[ENGY_MODEL] + with respx.mock() as upstream: + upstream.post(f"{ENGY_API_BASE}/chat/completions").respond( + 200, + json={ + "id": "chatcmpl_engy", + "object": "chat.completion", + "created": 1_789_550_000, + "model": ENGY_MODEL_ID, + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "ok"}, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1000, + "completion_tokens": 20, + "total_tokens": 1020, + "prompt_tokens_details": {"cached_tokens": 800}, + }, + }, + ) + response: Final = litellm.completion( + model=ENGY_MODEL, + messages=[{"role": "user", "content": "Say hello"}], + api_key="engy-test-key", + ) + + assert row["cache_read_input_token_cost"] < row["input_cost_per_token"] + assert litellm.completion_cost(completion_response=response, model=ENGY_MODEL) == pytest.approx( + 200 * row["input_cost_per_token"] + 800 * row["cache_read_input_token_cost"] + 20 * row["output_cost_per_token"] + ) + + +def test_engy_models_are_registered_in_the_cost_map(): + catalog: Final = {name for name, info in litellm.model_cost.items() if info.get("litellm_provider") == "engy"} + assert len(catalog) == 8 + assert all(name.startswith("engy/") for name in catalog) + + info: Final = litellm.get_model_info(model=ENGY_MODEL) + assert info["litellm_provider"] == "engy" + assert info["mode"] == "chat" + + +def test_engy_cost_map_backup_mirrors_main(): + main_map: Final = json.loads( + (Path(litellm.__file__).parent.parent / "model_prices_and_context_window.json").read_text() + ) + backup_map: Final = json.loads( + (Path(litellm.__file__).parent / "model_prices_and_context_window_backup.json").read_text() + ) + main_keys: Final = {key for key in main_map if key.startswith("engy/")} + backup_keys: Final = {key for key in backup_map if key.startswith("engy/")} + assert main_keys and main_keys == backup_keys + for key in main_keys: + assert backup_map[key] == main_map[key] + + +def test_engy_cost_map_rows_declare_same_endpoints_as_providers_json(): + providers_json: Final = json.loads( + (Path(litellm.__file__).parent / "llms" / "openai_like" / "providers.json").read_text() + ) + declared: Final = providers_json["engy"]["supported_endpoints"] + cost_map_paths: Final = ( + Path(litellm.__file__).parent.parent / "model_prices_and_context_window.json", + Path(litellm.__file__).parent / "model_prices_and_context_window_backup.json", + ) + for cost_map_path in cost_map_paths: + cost_map: Final = json.loads(cost_map_path.read_text()) + engy_rows: Final = {key: row for key, row in cost_map.items() if key.startswith("engy/")} + assert engy_rows + for row in engy_rows.values(): + assert row["supported_endpoints"] == declared