Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
| [Clarifai (`clarifai`)](https://docs.litellm.ai/docs/providers/clarifai) | ✅ | ✅ | ✅ | | | | | | | |
| [Cloudflare AI Workers (`cloudflare`)](https://docs.litellm.ai/docs/providers/cloudflare_workers) | ✅ | ✅ | ✅ | | | | | | | |
| [Codestral (`codestral`)](https://docs.litellm.ai/docs/providers/codestral) | ✅ | ✅ | ✅ | | | | | | | |
| [Cognition (`cognition`)](https://docs.litellm.ai/docs/providers/cognition) | ✅ | ✅ | ✅ | | | | | | | |
| [Cohere (`cohere`)](https://docs.litellm.ai/docs/providers/cohere) | ✅ | ✅ | ✅ | ✅ | | | | | | ✅ |
| [Cohere Chat (`cohere_chat`)](https://docs.litellm.ai/docs/providers/cohere) | ✅ | ✅ | ✅ | | | | | | | |
| [CometAPI (`cometapi`)](https://docs.litellm.ai/docs/providers/cometapi) | ✅ | ✅ | ✅ | ✅ | | | | | | |
Expand Down
2 changes: 2 additions & 0 deletions litellm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@
"https://api.libertai.io/v1",
"https://pinstripes.io/v1",
"https://api.meta.ai/v1",
"https://api.cognition.ai/v1",
]


Expand Down Expand Up @@ -830,6 +831,7 @@
"pinstripes", # Pinstripes - JSON-configured provider
"darkbloom",
"meta", # Meta Model API (Muse Spark) - JSON-configured provider
"cognition",
]
openai_text_completion_compatible_providers: Final[list] = [ # providers that support `/v1/completions`
"together_ai",
Expand Down
3 changes: 3 additions & 0 deletions litellm/litellm_core_utils/get_llm_provider_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ def get_llm_provider(
elif endpoint == "https://api.meta.ai/v1":
custom_llm_provider = "meta"
dynamic_api_key = get_secret_str("META_API_KEY")
elif (json_provider := JSONProviderRegistry.get_by_base_url(endpoint)) is not None:
custom_llm_provider = json_provider.slug
dynamic_api_key = api_key if api_key is not None else get_secret_str(json_provider.api_key_env)

if api_base is not None and not isinstance(api_base, str):
raise Exception(f"api base needs to be a string. api_base={api_base}")
Expand Down
5 changes: 5 additions & 0 deletions litellm/llms/openai_like/json_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ def exists(cls, slug: str) -> bool:
"""Check if a provider is defined via JSON"""
return slug in cls._providers

@classmethod
def get_by_base_url(cls, base_url: str) -> SimpleProviderConfig | None:
"""Get a provider configuration by its default base url"""
return next((provider for provider in cls._providers.values() if provider.base_url == base_url), None)

@classmethod
def supports_responses_api(cls, slug: str) -> bool:
"""Check if a JSON provider supports the Responses API"""
Expand Down
5 changes: 5 additions & 0 deletions litellm/llms/openai_like/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@
"base_class": "openai_gpt",
"supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"]
},
"cognition": {
"base_url": "https://api.cognition.ai/v1",
"api_key_env": "COGNITION_API_KEY",
"api_base_env": "COGNITION_API_BASE"
},
"pinstripes": {
"base_url": "https://pinstripes.io/v1",
"api_key_env": "PINSTRIPES_API_KEY",
Expand Down
20 changes: 20 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -48475,6 +48475,26 @@
"supports_reasoning": true,
"supports_vision": false
},
"cognition/swe-1.6": {
"input_cost_per_token": 5e-07,
"output_cost_per_token": 2.5e-06,
"cache_read_input_token_cost": 2e-07,
"litellm_provider": "cognition",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"source": "https://docs.devin.ai/windsurf/plugins/cascade/models"
},
"cognition/swe-1.7": {
"input_cost_per_token": 2.5e-06,
"output_cost_per_token": 1.25e-05,
"cache_read_input_token_cost": 1e-06,
"litellm_provider": "cognition",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"source": "https://docs.devin.ai/windsurf/plugins/cascade/models"
},
"pinstripes/ps/glm-4.5-air": {
"max_tokens": 128000,
"max_input_tokens": 128000,
Expand Down
17 changes: 17 additions & 0 deletions litellm/provider_endpoints_support_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,23 @@
"interactions": true
}
},
"cognition": {
"display_name": "Cognition (`cognition`)",
"url": "https://docs.litellm.ai/docs/providers/cognition",
"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
}
},
"cohere": {
"display_name": "Cohere (`cohere`)",
"url": "https://docs.litellm.ai/docs/providers/cohere",
Expand Down
28 changes: 28 additions & 0 deletions litellm/proxy/public_endpoints/provider_create_fields.json
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,34 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "Cognition",
"provider_display_name": "Cognition",
"litellm_provider": "cognition",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.cognition.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": "cognition/swe-1.7"
},
{
"provider": "Cohere",
"provider_display_name": "Cohere",
Expand Down
1 change: 1 addition & 0 deletions litellm/types/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3782,6 +3782,7 @@ class LlmProviders(str, Enum):
TENSORMESH = "tensormesh"
LIBERTAI = "libertai"
PINSTRIPES = "pinstripes"
COGNITION = "cognition"
DARKBLOOM = "darkbloom"
META = "meta"
LITELLM_AGENT = "litellm_agent"
Expand Down
20 changes: 20 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -48475,6 +48475,26 @@
"supports_reasoning": true,
"supports_vision": false
},
"cognition/swe-1.6": {
"input_cost_per_token": 5e-07,
"output_cost_per_token": 2.5e-06,
"cache_read_input_token_cost": 2e-07,
"litellm_provider": "cognition",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"source": "https://docs.devin.ai/windsurf/plugins/cascade/models"
},
"cognition/swe-1.7": {
"input_cost_per_token": 2.5e-06,
"output_cost_per_token": 1.25e-05,
"cache_read_input_token_cost": 1e-06,
"litellm_provider": "cognition",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"source": "https://docs.devin.ai/windsurf/plugins/cascade/models"
},
"pinstripes/ps/glm-4.5-air": {
"max_tokens": 128000,
"max_input_tokens": 128000,
Expand Down
17 changes: 17 additions & 0 deletions provider_endpoints_support.json
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,23 @@
"interactions": true
}
},
"cognition": {
"display_name": "Cognition (`cognition`)",
"url": "https://docs.litellm.ai/docs/providers/cognition",
"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
}
},
"cohere": {
"display_name": "Cohere (`cohere`)",
"url": "https://docs.litellm.ai/docs/providers/cohere",
Expand Down
175 changes: 175 additions & 0 deletions tests/test_litellm/llms/openai_like/test_cognition_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""
Tests for the Cognition provider identity.

Cognition serves an OpenAI-compatible /v1/chat/completions surface, but it must resolve to its
own `cognition` provider so OpenAI-specific pricing and provider-level reporting never apply to
its traffic.
"""

import json
from pathlib import Path

import pytest

import litellm


class TestCognitionProviderIdentity:
def test_cognition_is_a_registered_provider(self):
from litellm import LlmProviders

assert LlmProviders.COGNITION.value == "cognition"
assert "cognition" in litellm.provider_list

def test_cognition_json_config(self):
from litellm.llms.openai_like.json_loader import JSONProviderRegistry

cognition = JSONProviderRegistry.get("cognition")
assert cognition is not None
assert cognition.base_url == "https://api.cognition.ai/v1"
assert cognition.api_key_env == "COGNITION_API_KEY"
assert cognition.api_base_env == "COGNITION_API_BASE"

def test_cognition_in_openai_compatible_providers(self):
from litellm.constants import openai_compatible_providers

assert "cognition" in openai_compatible_providers

def test_prefixed_model_resolves_to_cognition_not_openai(self):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

model, provider, _, api_base = get_llm_provider(
model="cognition/swe-1.7",
custom_llm_provider=None,
api_base=None,
api_key=None,
)

assert model == "swe-1.7"
assert provider == "cognition"
assert api_base == "https://api.cognition.ai/v1"

def test_explicit_api_base_and_key_win(self):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

_, provider, api_key, api_base = get_llm_provider(
model="cognition/swe-1.7",
custom_llm_provider=None,
api_base="https://cognition.internal.example/v1",
api_key="sk-test",
)

assert provider == "cognition"
assert api_base == "https://cognition.internal.example/v1"
assert api_key == "sk-test"

def test_api_base_autodetects_cognition(self, monkeypatch: pytest.MonkeyPatch):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

monkeypatch.setenv("COGNITION_API_KEY", "sk-cognition-env")

_, provider, api_key, api_base = get_llm_provider(
model="swe-1.7",
custom_llm_provider=None,
api_base="https://api.cognition.ai/v1",
api_key=None,
)

assert provider == "cognition"
assert api_base == "https://api.cognition.ai/v1"
assert api_key == "sk-cognition-env"

def test_autodetected_api_base_keeps_the_caller_api_key(self, monkeypatch: pytest.MonkeyPatch):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

monkeypatch.setenv("COGNITION_API_KEY", "sk-cognition-env")

_, provider, api_key, _ = get_llm_provider(
model="swe-1.7",
custom_llm_provider=None,
api_base="https://api.cognition.ai/v1",
api_key="sk-cognition-caller",
)

assert provider == "cognition"
assert api_key == "sk-cognition-caller"

def test_env_api_key_is_read_from_cognition_variable(self, monkeypatch: pytest.MonkeyPatch):
from litellm.llms.openai_like.dynamic_config import create_config_class
from litellm.llms.openai_like.json_loader import JSONProviderRegistry

monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setenv("COGNITION_API_KEY", "sk-cognition-env")

provider = JSONProviderRegistry.get("cognition")
assert provider is not None

api_base, api_key = create_config_class(provider)()._get_openai_compatible_provider_info(None, None)
assert api_base == "https://api.cognition.ai/v1"
assert api_key == "sk-cognition-env"


class TestCognitionCostTracking:
@pytest.mark.parametrize(
"model, input_cost, output_cost",
[
("cognition/swe-1.6", 5e-07, 2.5e-06),
("cognition/swe-1.7", 2.5e-06, 1.25e-05),
],
)
def test_cost_map_entries(self, model: str, input_cost: float, output_cost: float):
info = litellm.get_model_info(model=model)

assert info["litellm_provider"] == "cognition"
assert info["mode"] == "chat"
assert info["input_cost_per_token"] == input_cost
assert info["output_cost_per_token"] == output_cost

def test_cost_differs_from_openai_pricing(self):
"""A cognition-prefixed model must never be priced off an OpenAI cost entry."""
from litellm.cost_calculator import cost_per_token

prompt_cost, completion_cost = cost_per_token(
model="cognition/swe-1.7",
prompt_tokens=1_000_000,
completion_tokens=1_000_000,
custom_llm_provider="cognition",
)

assert prompt_cost == pytest.approx(2.5)
assert completion_cost == pytest.approx(12.5)

def test_supported_endpoints_matrix(self):
matrix = json.loads((Path(litellm.__file__).parent / "provider_endpoints_support_backup.json").read_text())

endpoints = matrix["providers"]["cognition"]["endpoints"]
assert endpoints["chat_completions"] is True
assert endpoints["messages"] is True
assert endpoints["responses"] is True
assert endpoints["embeddings"] is False


class TestCognitionRouting:
@pytest.mark.asyncio
async def test_router_spend_is_attributed_to_cognition_pricing(self):
"""Routed traffic is costed off the cognition entry, not an OpenAI one."""
from litellm import Router

router = Router(
model_list=[
{
"model_name": "swe",
"litellm_params": {"model": "cognition/swe-1.7", "api_key": "sk-test"},
}
]
)

response = await router.acompletion(
model="swe",
messages=[{"role": "user", "content": "hi"}],
mock_response="hello from swe",
)

usage = response.usage
expected = usage.prompt_tokens * 2.5e-06 + usage.completion_tokens * 1.25e-05
assert response._hidden_params["response_cost"] == pytest.approx(expected)
Loading
Loading