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
2 changes: 2 additions & 0 deletions litellm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,7 @@
"https://api.meta.ai/v1",
"https://api.sailresearch.com/v1",
"https://api.cognition.ai/v1",
"https://api.cortecs.ai/v1",
"https://api.scx.ai/v1",
"https://api.prisminference.com/v1",
"https://gigachat.devices.sberbank.ru/api/v1",
Expand Down Expand Up @@ -1022,6 +1023,7 @@
"darkbloom",
"meta", # Meta Model API (Muse Spark) - JSON-configured provider
"cognition",
"cortecs",
"scx-ai",
"prism",
"sail",
Expand Down
6 changes: 6 additions & 0 deletions litellm/llms/openai_like/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@
"api_key_env": "COGNITION_API_KEY",
"api_base_env": "COGNITION_API_BASE"
},
"cortecs": {
"base_url": "https://api.cortecs.ai/v1",
"api_key_env": "CORTECS_API_KEY",
"api_base_env": "CORTECS_API_BASE",
"supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"]
},
"pinstripes": {
Comment on lines +183 to 189

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cortecs usage records zero spend

If a Cortecs deployment has no custom pricing, a successful call to cortecs/gpt-6-sol has no matching provider-specific cost entry. Cost calculation returns no cost, and the proxy records $0 spend despite reported token usage. Key and team budgets based on accumulated spend therefore cannot cap this usage. Add pricing or require explicit pricing for billable Cortecs deployments.

"base_url": "https://pinstripes.io/v1",
"api_key_env": "PINSTRIPES_API_KEY",
Expand Down
18 changes: 18 additions & 0 deletions litellm/provider_endpoints_support_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,24 @@
"interactions": true
}
},
"cortecs": {
"display_name": "Cortecs (`cortecs`)",
"url": "https://docs.litellm.ai/docs/providers/cortecs",
"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",
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 @@ -1015,6 +1015,34 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "CORTECS",
"provider_display_name": "Cortecs",
"litellm_provider": "cortecs",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.cortecs.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": "cortecs/gpt-6-sol"
},
{
"provider": "CUSTOM",
"provider_display_name": "Custom",
Expand Down
1 change: 1 addition & 0 deletions litellm/types/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4153,6 +4153,7 @@ class LlmProviders(str, Enum):
LIBERTAI = "libertai"
PINSTRIPES = "pinstripes"
COGNITION = "cognition"
CORTECS = "cortecs"
SCX_AI = "scx-ai"
PRISM = "prism"
DARKBLOOM = "darkbloom"
Expand Down
18 changes: 18 additions & 0 deletions provider_endpoints_support.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,24 @@
"interactions": true
}
},
"cortecs": {
"display_name": "Cortecs (`cortecs`)",
"url": "https://docs.litellm.ai/docs/providers/cortecs",
"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",
Expand Down
187 changes: 187 additions & 0 deletions tests/unit/llms/openai_like/test_cortecs_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import json
from pathlib import Path
from typing import Final

import pytest
import respx

import litellm
from litellm.caching.llm_caching_handler import LLMClientCache


def test_cortecs_provider_resolution(monkeypatch: pytest.MonkeyPatch):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

monkeypatch.setenv("CORTECS_API_KEY", "cortecs-test-key")

model, provider, api_key, api_base = get_llm_provider(
model="cortecs/gpt-6-sol",
custom_llm_provider=None,
api_base=None,
api_key=None,
)

assert model == "gpt-6-sol"
assert provider == "cortecs"
assert api_key == "cortecs-test-key"
assert api_base == "https://api.cortecs.ai/v1"


def test_cortecs_provider_keeps_explicit_credentials(monkeypatch: pytest.MonkeyPatch):
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider

monkeypatch.setenv("CORTECS_API_KEY", "cortecs-env-key")

_, provider, api_key, api_base = get_llm_provider(
model="cortecs/gpt-6-sol",
custom_llm_provider=None,
api_base="https://cortecs.internal.example/v1",
api_key="cortecs-explicit-key",
)

assert provider == "cortecs"
assert api_key == "cortecs-explicit-key"
assert api_base == "https://cortecs.internal.example/v1"


def test_cortecs_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())
cortecs = next(provider for provider in providers if provider["litellm_provider"] == "cortecs")

assert cortecs["provider"] == "CORTECS"
assert cortecs["provider_display_name"] == "Cortecs"
assert cortecs["default_model_placeholder"] == "cortecs/gpt-6-sol"
assert {field["key"]: field["required"] for field in cortecs["credential_fields"]} == {
"api_base": False,
"api_key": True,
}


def test_cortecs_supported_endpoints():
matrix_path = Path(litellm.__file__).parent / "provider_endpoints_support_backup.json"
providers = json.loads(matrix_path.read_text())["providers"]

assert providers["cortecs"]["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,
}


def test_cortecs_chat_completion_request():
with respx.mock() as upstream:
route: Final = upstream.post("https://api.cortecs.ai/v1/chat/completions").respond(
200,
json={
"id": "chatcmpl_cortecs",
"object": "chat.completion",
"created": 1_789_550_000,
"model": "gpt-6-sol",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello from Cortecs"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7},
},
)
response: Final = litellm.completion(
model="cortecs/gpt-6-sol",
messages=[{"role": "user", "content": "Say hello"}],
api_key="cortecs-test-key",
)

request: Final = route.calls.last.request
body: Final = json.loads(request.content)
assert route.call_count == 1
assert str(request.url) == "https://api.cortecs.ai/v1/chat/completions"
assert request.headers["authorization"] == "Bearer cortecs-test-key"
assert body["model"] == "gpt-6-sol"
assert body["messages"] == [{"role": "user", "content": "Say hello"}]
assert response.choices[0].message.content == "Hello from Cortecs"


def test_cortecs_responses_request():
with respx.mock() as upstream:
route: Final = upstream.post("https://api.cortecs.ai/v1/responses").respond(
200,
json={
"id": "resp_cortecs",
"object": "response",
"created_at": 1_789_550_000,
"model": "gpt-6-sol",
"status": "completed",
"output": [
{
"id": "msg_cortecs",
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{"type": "output_text", "text": "Hello from Cortecs", "annotations": []}],
}
],
"usage": {"input_tokens": 4, "output_tokens": 3, "total_tokens": 7},
},
)
response: Final = litellm.responses(
model="cortecs/gpt-6-sol",
input="Say hello",
api_key="cortecs-test-key",
)

request: Final = route.calls.last.request
body: Final = json.loads(request.content)
assert route.call_count == 1
assert str(request.url) == "https://api.cortecs.ai/v1/responses"
assert request.headers["authorization"] == "Bearer cortecs-test-key"
assert body["model"] == "gpt-6-sol"
assert body["input"] == "Say hello"
assert response.output[0].content[0].text == "Hello from Cortecs"


@pytest.mark.asyncio
async def test_cortecs_anthropic_messages_request(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://api.cortecs.ai/v1/messages").respond(
200,
Comment on lines +156 to +160

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Streaming routes lack test coverage

The new request tests cover only non-streaming calls, although Cortecs is registered for chat completions, Responses, and Messages. A broken streaming URL, transport, or event parser on any of these routes would pass this suite. Please add mocked streaming tests for the advertised endpoints.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

json={
"id": "msg_cortecs",
"type": "message",
"role": "assistant",
"model": "gpt-6-sol",
"content": [{"type": "text", "text": "Hello from Cortecs"}],
"stop_reason": "end_turn",
"stop_sequence": None,
"usage": {"input_tokens": 4, "output_tokens": 3},
},
)
response: Final = await litellm.anthropic.messages.acreate(
model="cortecs/gpt-6-sol",
messages=[{"role": "user", "content": "Say hello"}],
max_tokens=32,
api_key="cortecs-test-key",
)

request: Final = route.calls.last.request
body: Final = json.loads(request.content)
assert route.call_count == 1
assert str(request.url) == "https://api.cortecs.ai/v1/messages"
assert request.headers["authorization"] == "Bearer cortecs-test-key"
assert request.headers["anthropic-version"] == "2023-06-01"
assert body["model"] == "gpt-6-sol"
assert body["messages"] == [{"role": "user", "content": "Say hello"}]
assert response["content"][0]["text"] == "Hello from Cortecs"
Loading