From eff553631949b2194b24ba5d197f7ec21bd6f7b6 Mon Sep 17 00:00:00 2001 From: Sebastien FAUVART Date: Thu, 8 Oct 2026 21:53:45 +0200 Subject: [PATCH 1/3] feat(providers): add Corti as an OpenAI-compatible provider Corti Models (corti-s1, corti-s1-instant, corti-s1-mini, corti-s1-mini-instant) is configured in providers.json with the EU endpoint as default base URL, CORTI_API_KEY and CORTI_API_BASE. max_completion_tokens is sent as max_tokens and temperature is capped at 1, as in Corti's chat completions schema. Responses go to Corti's native /v1/responses; Anthropic messages are bridged to chat completions. Adds the price map rows, the Add Model form entry, the endpoint support matrix entry, and the provider name and logo in the dashboard. --- litellm/constants.py | 2 + litellm/llms/openai_like/providers.json | 12 + ...odel_prices_and_context_window_backup.json | 60 +++++ .../provider_endpoints_support_backup.json | 18 ++ .../provider_create_fields.json | 28 ++ litellm/types/utils.py | 1 + model_prices_and_context_window.json | 60 +++++ provider_endpoints_support.json | 18 ++ .../llms/openai_like/test_corti_provider.py | 253 ++++++++++++++++++ .../public/assets/logos/corti.svg | 14 + .../components/provider_info_helpers.test.tsx | 15 ++ .../src/components/provider_info_helpers.tsx | 5 + .../src/lib/logoTreatments.ts | 1 + 13 files changed, 487 insertions(+) create mode 100644 tests/unit/llms/openai_like/test_corti_provider.py create mode 100644 ui/litellm-dashboard/public/assets/logos/corti.svg diff --git a/litellm/constants.py b/litellm/constants.py index c4adc0de22f..0c80fe63228 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -971,6 +971,7 @@ openai_compatible_endpoints: Final[list] = [ "https://api.sailresearch.com/v1", "https://api.cognition.ai/v1", "https://api.cortecs.ai/v1", + "https://ai.eu.corti.app/v1", "https://api.scx.ai/v1", "https://api.prisminference.com/v1", "https://api.reka.ai/v1", @@ -1047,6 +1048,7 @@ openai_compatible_providers: Final[list] = [ "meta", # Meta Model API (Muse Spark) - JSON-configured provider "cognition", "cortecs", + "corti", "scx-ai", "prism", "sail", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index f3aa7768928..975324879d5 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -195,6 +195,18 @@ }, "supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/embeddings"] }, + "corti": { + "base_url": "https://ai.eu.corti.app/v1", + "api_key_env": "CORTI_API_KEY", + "api_base_env": "CORTI_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + }, + "constraints": { + "temperature_max": 1.0 + }, + "supported_endpoints": ["/v1/chat/completions", "/v1/responses"] + }, "scx-ai": { "base_url": "https://api.scx.ai/v1", "api_key_env": "SCX_API_KEY", diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 97760a4f0b9..01538ad07ff 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -46207,6 +46207,66 @@ "supports_tool_choice": true, "supports_vision": true }, + "corti/corti-s1": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 2e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "corti/corti-s1-instant": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 2e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": false, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "corti/corti-s1-mini": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 1e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 4e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "corti/corti-s1-mini-instant": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 1e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 4e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": false, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "snowflake/claude-3-5-sonnet": { "litellm_provider": "snowflake", "max_input_tokens": 200000, diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 0e325bb61fe..a414a53f498 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -636,6 +636,24 @@ "interactions": false } }, + "corti": { + "display_name": "Corti (`corti`)", + "url": "https://docs.litellm.ai/docs/providers/corti", + "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 + } + }, "custom": { "display_name": "Custom (`custom`)", "url": "https://docs.litellm.ai/docs/providers/custom_llm_server", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index ae07d5d6bd9..93286d3ef1f 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -1043,6 +1043,34 @@ ], "default_model_placeholder": "cortecs/gpt-6-sol" }, + { + "provider": "CORTI", + "provider_display_name": "Corti", + "litellm_provider": "corti", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://ai.eu.corti.app/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": "corti/corti-s1" + }, { "provider": "CUSTOM", "provider_display_name": "Custom", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index a99c22e9479..3f93f295f55 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4264,6 +4264,7 @@ class LlmProviders(str, Enum): PINSTRIPES = "pinstripes" COGNITION = "cognition" CORTECS = "cortecs" + CORTI = "corti" SCX_AI = "scx-ai" PRISM = "prism" DARKBLOOM = "darkbloom" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 97760a4f0b9..01538ad07ff 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -46207,6 +46207,66 @@ "supports_tool_choice": true, "supports_vision": true }, + "corti/corti-s1": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 2e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "corti/corti-s1-instant": { + "cache_read_input_token_cost": 2e-07, + "input_cost_per_token": 2e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 8e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": false, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "corti/corti-s1-mini": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 1e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 4e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "corti/corti-s1-mini-instant": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 1e-06, + "litellm_provider": "corti", + "mode": "chat", + "output_cost_per_token": 4e-06, + "source": "https://docs.corti.ai/models/models#pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": false, + "supports_response_schema": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "snowflake/claude-3-5-sonnet": { "litellm_provider": "snowflake", "max_input_tokens": 200000, diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 00904219b81..1a4be6b3715 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -690,6 +690,24 @@ "interactions": false } }, + "corti": { + "display_name": "Corti (`corti`)", + "url": "https://docs.litellm.ai/docs/providers/corti", + "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 + } + }, "crusoe": { "display_name": "Crusoe (`crusoe`)", "url": "https://docs.litellm.ai/docs/providers/crusoe", diff --git a/tests/unit/llms/openai_like/test_corti_provider.py b/tests/unit/llms/openai_like/test_corti_provider.py new file mode 100644 index 00000000000..a0f798fda38 --- /dev/null +++ b/tests/unit/llms/openai_like/test_corti_provider.py @@ -0,0 +1,253 @@ +import json +from pathlib import Path +from typing import Final + +import pytest +import respx + +import litellm +from litellm.caching.llm_caching_handler import LLMClientCache + +_CHAT_COMPLETION: Final = { + "id": "chatcmpl_corti", + "object": "chat.completion", + "created": 1_790_000_000, + "model": "corti-s1", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello from Corti"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7}, +} + + +def test_corti_is_a_registered_provider(): + assert litellm.LlmProviders.CORTI.value == "corti" + assert "corti" in litellm.provider_list + assert "corti" in litellm.constants.openai_compatible_providers + + +def test_corti_provider_resolution(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("CORTI_API_KEY", "corti-test-key") + monkeypatch.delenv("CORTI_API_BASE", raising=False) + + model, provider, api_key, api_base = get_llm_provider( + model="corti/corti-s1", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert model == "corti-s1" + assert provider == "corti" + assert api_key == "corti-test-key" + assert api_base == "https://ai.eu.corti.app/v1" + + +def test_corti_provider_keeps_explicit_credentials(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("CORTI_API_KEY", "corti-env-key") + monkeypatch.setenv("CORTI_API_BASE", "https://corti.env.example/v1") + + _, provider, api_key, api_base = get_llm_provider( + model="corti/corti-s1", + custom_llm_provider=None, + api_base="https://ai.us.corti.app/v1", + api_key="corti-explicit-key", + ) + + assert provider == "corti" + assert api_key == "corti-explicit-key" + assert api_base == "https://ai.us.corti.app/v1" + + +def test_corti_api_base_env_overrides_default(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("CORTI_API_KEY", "corti-env-key") + monkeypatch.setenv("CORTI_API_BASE", "https://ai.us.corti.app/v1") + + _, provider, _, api_base = get_llm_provider( + model="corti/corti-s1", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert provider == "corti" + assert api_base == "https://ai.us.corti.app/v1" + + +def test_corti_api_base_autodetects_provider(monkeypatch: pytest.MonkeyPatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("CORTI_API_KEY", "corti-env-key") + + model, provider, api_key, api_base = get_llm_provider( + model="corti-s1", + custom_llm_provider=None, + api_base="https://ai.eu.corti.app/v1", + api_key=None, + ) + + assert model == "corti-s1" + assert provider == "corti" + assert api_key == "corti-env-key" + assert api_base == "https://ai.eu.corti.app/v1" + + +def test_corti_is_available_in_add_model_form(): + fields_path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" + providers = json.loads(fields_path.read_text()) + corti = next(provider for provider in providers if provider["litellm_provider"] == "corti") + + assert corti["provider"] == "CORTI" + assert corti["provider_display_name"] == "Corti" + assert corti["default_model_placeholder"] == "corti/corti-s1" + assert {field["key"]: field["required"] for field in corti["credential_fields"]} == { + "api_base": False, + "api_key": True, + } + + +def test_corti_supported_endpoints(): + expected: Final = { + "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, + } + backup_path = Path(litellm.__file__).parent / "provider_endpoints_support_backup.json" + root_path = Path(litellm.__file__).parent.parent / "provider_endpoints_support.json" + + assert json.loads(backup_path.read_text())["providers"]["corti"]["endpoints"] == expected + assert json.loads(root_path.read_text())["providers"]["corti"]["endpoints"] == expected + + +def test_corti_chat_completion_request(): + with respx.mock() as upstream: + route: Final = upstream.post("https://ai.eu.corti.app/v1/chat/completions").respond(200, json=_CHAT_COMPLETION) + response: Final = litellm.completion( + model="corti/corti-s1", + messages=[{"role": "user", "content": "Say hello"}], + api_key="corti-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert str(request.url) == "https://ai.eu.corti.app/v1/chat/completions" + assert request.headers["authorization"] == "Bearer corti-test-key" + assert body["model"] == "corti-s1" + assert body["messages"] == [{"role": "user", "content": "Say hello"}] + assert response.choices[0].message.content == "Hello from Corti" + + +def test_corti_chat_completion_maps_unsupported_params(): + with respx.mock() as upstream: + route: Final = upstream.post("https://ai.eu.corti.app/v1/chat/completions").respond(200, json=_CHAT_COMPLETION) + litellm.completion( + model="corti/corti-s1", + messages=[{"role": "user", "content": "Say hello"}], + api_key="corti-test-key", + max_completion_tokens=64, + temperature=1.5, + ) + + body: Final = json.loads(route.calls.last.request.content) + assert body["max_tokens"] == 64 + assert "max_completion_tokens" not in body + assert body["temperature"] == 1.0 + + +@pytest.mark.parametrize("model", ["corti-s1", "corti-s1-instant", "corti-s1-mini", "corti-s1-mini-instant"]) +def test_corti_chat_completion_cost_comes_from_the_price_map(model: str): + with respx.mock() as upstream: + upstream.post("https://ai.eu.corti.app/v1/chat/completions").respond( + 200, json={**_CHAT_COMPLETION, "model": model} + ) + response: Final = litellm.completion( + model=f"corti/{model}", + messages=[{"role": "user", "content": "Say hello"}], + api_key="corti-test-key", + ) + + row: Final = litellm.model_cost[f"corti/{model}"] + expected: Final = 4 * row["input_cost_per_token"] + 3 * row["output_cost_per_token"] + assert litellm.completion_cost(completion_response=response) == pytest.approx(expected) + assert expected > 0 + + +def test_corti_responses_request(): + with respx.mock() as upstream: + route: Final = upstream.post("https://ai.eu.corti.app/v1/responses").respond( + 200, + json={ + "id": "resp_corti", + "object": "response", + "created_at": 1_790_000_000, + "model": "corti-s1", + "status": "completed", + "output": [ + { + "id": "msg_corti", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": "Hello from Corti", "annotations": []}], + } + ], + "usage": {"input_tokens": 4, "output_tokens": 3, "total_tokens": 7}, + }, + ) + response: Final = litellm.responses( + model="corti/corti-s1", + input="Say hello", + api_key="corti-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert str(request.url) == "https://ai.eu.corti.app/v1/responses" + assert request.headers["authorization"] == "Bearer corti-test-key" + assert body["model"] == "corti-s1" + assert body["input"] == "Say hello" + assert response.output[0].content[0].text == "Hello from Corti" + + +@pytest.mark.asyncio +async def test_corti_anthropic_messages_request_is_bridged_to_chat_completions(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) + monkeypatch.setattr(litellm, "in_memory_llm_clients_cache", LLMClientCache()) + with respx.mock() as upstream: + route: Final = upstream.post("https://ai.eu.corti.app/v1/chat/completions").respond(200, json=_CHAT_COMPLETION) + response: Final = await litellm.anthropic.messages.acreate( + model="corti/corti-s1", + messages=[{"role": "user", "content": "Say hello"}], + max_tokens=32, + api_key="corti-test-key", + ) + + request: Final = route.calls.last.request + body: Final = json.loads(request.content) + assert route.call_count == 1 + assert request.headers["authorization"] == "Bearer corti-test-key" + assert body["model"] == "corti-s1" + assert body["messages"] == [{"role": "user", "content": "Say hello"}] + assert body["max_tokens"] == 32 + assert response["content"][0]["text"] == "Hello from Corti" diff --git a/ui/litellm-dashboard/public/assets/logos/corti.svg b/ui/litellm-dashboard/public/assets/logos/corti.svg new file mode 100644 index 00000000000..fffe16cdcda --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/corti.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index cecd6ce01bf..de9e65af544 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -73,6 +73,17 @@ describe("provider_info_helpers", () => { expect(fromEnumKey.logo).toBe(providerLogoMap[Providers.SCX_AI]); }); + it("should map corti slug and CORTI enum key to the Corti display name and logo", () => { + const fromSlug = getProviderLogoAndName("corti"); + expect(fromSlug.displayName).toBe(Providers.CORTI); + expect(fromSlug.logo).toBe(providerLogoMap[Providers.CORTI]); + expect(fromSlug.logo).toBeTruthy(); + + const fromEnumKey = getProviderLogoAndName("CORTI"); + expect(fromEnumKey.displayName).toBe(Providers.CORTI); + expect(fromEnumKey.logo).toBe(providerLogoMap[Providers.CORTI]); + }); + it("should map bedrock_mantle slug to Bedrock Mantle display name and logo", () => { const result = getProviderLogoAndName("bedrock_mantle"); expect(result.displayName).toBe(Providers.BedrockMantle); @@ -249,6 +260,10 @@ describe("provider_info_helpers", () => { expect(getPlaceholder(Providers.SCX_AI)).toBe("scx-ai/GLM-5.2"); }); + it("should return a corti model placeholder for CORTI provider", () => { + expect(getPlaceholder(Providers.CORTI)).toBe("corti/corti-s1"); + }); + it("should return an edenai model placeholder for EDENAI provider", () => { expect(getPlaceholder(Providers.EDENAI)).toBe("edenai/openai/gpt-mini-latest"); }); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 5a57fc23dde..32180be5235 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -10,6 +10,7 @@ import cerebrasLogo from "../../public/assets/logos/cerebras.svg"; import cloudflareLogo from "../../public/assets/logos/cloudflare.svg"; import cohereLogo from "../../public/assets/logos/cohere.svg"; import cometapiLogo from "../../public/assets/logos/cometapi.svg"; +import cortiLogo from "../../public/assets/logos/corti.svg"; import cursorLogo from "../../public/assets/logos/cursor.svg"; import databricksLogo from "../../public/assets/logos/databricks.svg"; import deepgramLogo from "../../public/assets/logos/deepgram.png"; @@ -98,6 +99,7 @@ export enum Providers { COHERE_CHAT = "Cohere Chat", COMETAPI = "Cometapi", COMPACTIFAI = "Compactifai", + CORTI = "Corti", Cursor = "Cursor", Dashscope = "Dashscope", Databricks = "Databricks (Qwen API)", @@ -219,6 +221,7 @@ export const provider_map: Record = { COHERE_CHAT: "cohere_chat", COMETAPI: "cometapi", COMPACTIFAI: "compactifai", + CORTI: "corti", Cursor: "cursor", Dashscope: "dashscope", Databricks: "databricks", @@ -339,6 +342,7 @@ export const providerLogoMap: Partial> = { [Providers.Cohere]: cohereLogo.src, [Providers.COHERE_CHAT]: cohereLogo.src, [Providers.COMETAPI]: cometapiLogo.src, + [Providers.CORTI]: cortiLogo.src, [Providers.Cursor]: cursorLogo.src, [Providers.Databricks]: databricksLogo.src, [Providers.Dashscope]: qwenLogo.src, @@ -452,6 +456,7 @@ const providerPlaceholderMap: Partial> = { [Providers.Bedrock]: "claude-3-opus", [Providers.CHATGPT]: "chatgpt/gpt-5.4", [Providers.Cognition]: "cognition/swe-1.7", + [Providers.CORTI]: "corti/corti-s1", [Providers.Cursor]: "cursor/claude-4-sonnet", [Providers.DeepInfra]: "deepinfra/", [Providers.EDENAI]: "edenai/openai/gpt-mini-latest", diff --git a/ui/litellm-dashboard/src/lib/logoTreatments.ts b/ui/litellm-dashboard/src/lib/logoTreatments.ts index fc19c5c364d..00666ca792a 100644 --- a/ui/litellm-dashboard/src/lib/logoTreatments.ts +++ b/ui/litellm-dashboard/src/lib/logoTreatments.ts @@ -6,6 +6,7 @@ const BUNDLED_LOGO_PATH = /(?:\/assets\/logos\/|\/_next\/static\/media\/)/; const TREATMENT_BY_ASSET: Readonly> = { "baseten.svg": "invert", + "corti.svg": "invert", "cursor.svg": "invert", "edenai.svg": "invert", "enkrypt_ai.avif": "invert", From 0028f850169d9c9169a4774a0ee200aac47a5633 Mon Sep 17 00:00:00 2001 From: Sebastien FAUVART Date: Fri, 9 Oct 2026 13:27:19 +0200 Subject: [PATCH 2/3] test(corti): annotate test bindings with Final --- tests/unit/llms/openai_like/test_corti_provider.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/llms/openai_like/test_corti_provider.py b/tests/unit/llms/openai_like/test_corti_provider.py index a0f798fda38..d383145a6a1 100644 --- a/tests/unit/llms/openai_like/test_corti_provider.py +++ b/tests/unit/llms/openai_like/test_corti_provider.py @@ -103,9 +103,9 @@ def test_corti_api_base_autodetects_provider(monkeypatch: pytest.MonkeyPatch): def test_corti_is_available_in_add_model_form(): - fields_path = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" - providers = json.loads(fields_path.read_text()) - corti = next(provider for provider in providers if provider["litellm_provider"] == "corti") + fields_path: Final = Path(litellm.__file__).parent / "proxy" / "public_endpoints" / "provider_create_fields.json" + providers: Final = json.loads(fields_path.read_text()) + corti: Final = next(provider for provider in providers if provider["litellm_provider"] == "corti") assert corti["provider"] == "CORTI" assert corti["provider_display_name"] == "Corti" @@ -131,8 +131,8 @@ def test_corti_supported_endpoints(): "a2a": False, "interactions": False, } - backup_path = Path(litellm.__file__).parent / "provider_endpoints_support_backup.json" - root_path = Path(litellm.__file__).parent.parent / "provider_endpoints_support.json" + backup_path: Final = Path(litellm.__file__).parent / "provider_endpoints_support_backup.json" + root_path: Final = Path(litellm.__file__).parent.parent / "provider_endpoints_support.json" assert json.loads(backup_path.read_text())["providers"]["corti"]["endpoints"] == expected assert json.loads(root_path.read_text())["providers"]["corti"]["endpoints"] == expected From 24f852a77b3778b8b9668168ddcc9448d58edd71 Mon Sep 17 00:00:00 2001 From: Sebastien FAUVART Date: Tue, 13 Oct 2026 14:14:26 +0200 Subject: [PATCH 3/3] test(corti): drop the structure-only registration test --- tests/unit/llms/openai_like/test_corti_provider.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/unit/llms/openai_like/test_corti_provider.py b/tests/unit/llms/openai_like/test_corti_provider.py index d383145a6a1..7d029b84f18 100644 --- a/tests/unit/llms/openai_like/test_corti_provider.py +++ b/tests/unit/llms/openai_like/test_corti_provider.py @@ -24,12 +24,6 @@ _CHAT_COMPLETION: Final = { } -def test_corti_is_a_registered_provider(): - assert litellm.LlmProviders.CORTI.value == "corti" - assert "corti" in litellm.provider_list - assert "corti" in litellm.constants.openai_compatible_providers - - def test_corti_provider_resolution(monkeypatch: pytest.MonkeyPatch): from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider