Skip to content

Commit ca0ef07

Browse files
authored
feat: add Cascadia provider for distributed on-prem Intel AI PC inference (#1147)
## Description Adds `cascadia` as a provider (closes #1146). Cascadia (https://cascadia.to) is a distributed inference runtime that shards open-weights models (INT4 OpenVINO) across fleets of Intel AI PCs behind an OpenAI-compatible coordinator, keeping inference on-premises. **How** - `BaseOpenAIProvider` subclass following the vLLM keyless pattern, extended to honor `CASCADIA_API_KEY` from the environment when a fleet is keyed (an explicit `api_key` wins; otherwise it falls back to `no-key-required` so trusted-LAN coordinators work keyless). Env-configurable base via `CASCADIA_API_BASE` (default `http://localhost:9090/v1`); no new dependencies (`cascadia = []` extra, spliced into `all`). - Capability flags scoped to v1: completion, streaming, reasoning, and list-models on; responses, embeddings, moderation, image, PDF, and batch off. - Registered in `LLMProvider` (`src/any_llm/constants.py`), the `tests/conftest.py` fixture maps, and `LOCAL_PROVIDERS` + `CI_EXCLUDED_PROVIDERS` in `tests/constants.py` (self-hosted backend; CI cannot reach a fleet, mirroring vllm). Files: `src/any_llm/providers/cascadia/{cascadia,__init__}.py`, `src/any_llm/constants.py`, `pyproject.toml`, `tests/constants.py`, `tests/conftest.py`, and `tests/unit/providers/test_cascadia_provider.py`. **Testing** - `tests/unit/providers/test_cascadia_provider.py`: provider metadata, keyless auth, explicit-key passthrough, `CASCADIA_API_KEY` env resolution, env/default base resolution, capability flags, and enum/loader registration. 8 passed, 100% line+branch coverage of `cascadia.py`. - Full local unit suite green (1419 passed, 64 skipped), no regressions. Developed against current `main` (latest release `any-llm-sdk` 1.18.0); rebased on the default branch, where no `cascadia` provider previously existed. - `pre-commit run` on the change set: ruff, ruff-format, mypy (strict), and codespell all pass. - Manual smoke via `acompletion("cascadia:<model>", ...)` against a Cascadia coordinator implementing the OpenAI-compatible contract: non-streaming and streaming both return content and a populated `usage` object. Production Cascadia runs the same contract across a physical Intel AI PC fleet. <details><summary>Smoke transcript (real coordinator)</summary> ``` $ export CASCADIA_API_BASE=https://<coordinator>/api/v1 $ export CASCADIA_API_KEY=<bearer token> # keyed fleet; trusted-LAN fleets run keyless $ python smoke_cascadia.py models: ['llama-8b-2stage', 'qwen3-8b'] using: cascadia:qwen3-8b # non-streaming: content + usage object returned non-stream: '<think> [reasoning trimmed] </think>\n\nCASCADIA-OK' usage: CompletionUsage(completion_tokens=1, prompt_tokens=0, total_tokens=1) # streaming: reassembled deltas stream: '<think> [reasoning trimmed] </think>\n\nOne, two, three, four, five.' ``` qwen3-8b is a reasoning model, so it emits a `<think>` block before the answer; that text is trimmed here for readability. Token counts on this hosted demo coordinator are approximate; the provider faithfully surfaces whatever the coordinator reports in `usage`. </details> ## PR Type - 🆕 New Feature ## Relevant issues Closes #1146 ## Checklist - [x] I understand the code I am submitting. - [x] I have added unit tests that prove my fix/feature works - [x] I have run this code locally and verified it fixes the issue. - [x] New and existing tests pass locally - [x] Documentation was updated where necessary - [x] I have read and followed the contribution guidelines - [x] **AI Usage:** - [ ] No AI was used. - [x] AI was used for drafting/refactoring. - [ ] This is fully AI-generated. ## AI Usage Information - AI Model used: Claude Opus 4.8 - AI Developer Tool used: Claude Code - Any other info you'd like to share: The provider, tests, and docs were drafted with AI assistance under my direction and review. I verified the change locally (full unit suite, ruff/mypy-strict/codespell, and a live smoke against a real Cascadia coordinator) and will respond to review comments myself. - [ ] I am an AI Agent filling out this form (check box if true) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Cascadia as a new supported language model provider with an OpenAI-compatible coordinator endpoint. * Enabled completion with streaming and reasoning-style capabilities, plus model listing. * Added flexible API key handling using `CASCADIA_API_KEY` (with safe non-failing fallback when unset). * **Optional Dependencies** * Introduced a new `cascadia` extras option (and included it in `all`). * **Tests** * Added unit test coverage for Cascadia provider registration, configuration, capability flags, and API-base/API-key precedence. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Tate Berenbaum <t8@users.noreply.github.com>
1 parent f25c461 commit ca0ef07

7 files changed

Lines changed: 128 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies = [
2121
[project.optional-dependencies]
2222

2323
all = [
24-
"any-llm-sdk[mistral,anthropic,huggingface,gemini,vertexai,vertexaianthropic,cohere,cerebras,fireworks,groq,bedrock,azure,azureanthropic,azureopenai,watsonx,together,sambanova,ollama,moonshot,neosantara,nebius,xai,databricks,deepseek,inception,openai,otari,openrouter,portkey,qiniu,requesty,lmstudio,llama,voyage,perplexity,platform,llamafile,llamacpp,sagemaker,github,zai,minimax,mzai,vllm,dashscope,deepinfra]"
24+
"any-llm-sdk[mistral,anthropic,huggingface,gemini,vertexai,vertexaianthropic,cohere,cerebras,fireworks,groq,bedrock,azure,azureanthropic,azureopenai,cascadia,watsonx,together,sambanova,ollama,moonshot,neosantara,nebius,xai,databricks,deepseek,inception,openai,otari,openrouter,portkey,qiniu,requesty,lmstudio,llama,voyage,perplexity,platform,llamafile,llamacpp,sagemaker,github,zai,minimax,mzai,vllm,dashscope,deepinfra]"
2525
]
2626

2727
platform = [
@@ -110,6 +110,7 @@ lmstudio = [
110110

111111
# These providers don't require any additional dependencies, but are included for completeness.
112112
azureopenai = []
113+
cascadia = []
113114
databricks = []
114115
deepseek = []
115116
fireworks = []

src/any_llm/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class LLMProvider(StrEnum):
2121
AZURE = "azure"
2222
AZUREANTHROPIC = "azureanthropic"
2323
AZUREOPENAI = "azureopenai"
24+
CASCADIA = "cascadia"
2425
CEREBRAS = "cerebras"
2526
COHERE = "cohere"
2627
DATABRICKS = "databricks"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from any_llm.providers.cascadia.cascadia import CascadiaProvider
2+
3+
__all__ = ["CascadiaProvider"]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
3+
from typing_extensions import override
4+
5+
from any_llm.providers.openai.base import BaseOpenAIProvider
6+
7+
8+
class CascadiaProvider(BaseOpenAIProvider):
9+
"""Provider for Cascadia distributed on-prem inference clusters.
10+
11+
Cascadia (https://cascadia.to) is a distributed LLM inference runtime that
12+
splits open-weights transformer models into INT4 OpenVINO shards and runs
13+
them across fleets of Intel AI PCs, keeping inference fully on-premises.
14+
A Cascadia coordinator exposes an OpenAI-compatible API; this provider
15+
targets that endpoint.
16+
"""
17+
18+
API_BASE = "http://localhost:9090/v1"
19+
ENV_API_KEY_NAME = "CASCADIA_API_KEY"
20+
ENV_API_BASE_NAME = "CASCADIA_API_BASE"
21+
PROVIDER_NAME = "cascadia"
22+
PROVIDER_DOCUMENTATION_URL = "https://cascadia.to"
23+
24+
SUPPORTS_COMPLETION = True
25+
SUPPORTS_COMPLETION_STREAMING = True
26+
SUPPORTS_COMPLETION_REASONING = True
27+
SUPPORTS_COMPLETION_IMAGE = False
28+
SUPPORTS_COMPLETION_PDF = False
29+
SUPPORTS_EMBEDDING = False
30+
SUPPORTS_MODERATION = False
31+
SUPPORTS_LIST_MODELS = True
32+
SUPPORTS_BATCH = False
33+
SUPPORTS_IMAGE_GENERATION = False
34+
SUPPORTS_RERANK = False
35+
SUPPORTS_RESPONSES = False
36+
37+
@override
38+
def _verify_and_set_api_key(self, api_key: str | None = None) -> str:
39+
# Honor an explicit key or CASCADIA_API_KEY from the environment. Cascadia
40+
# coordinators on trusted LANs may run keyless, so fall back to a placeholder
41+
# instead of hard-failing when no key is set (vLLM convention).
42+
return api_key or os.getenv(self.ENV_API_KEY_NAME) or "no-key-required"

tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def provider_reasoning_model_map() -> dict[LLMProvider, str]:
5252
LLMProvider.LLAMAFILE: "N/A",
5353
LLMProvider.LLAMACPP: "N/A",
5454
LLMProvider.VLLM: "N/A",
55+
LLMProvider.CASCADIA: "N/A",
5556
LLMProvider.LMSTUDIO: "qwen3-0.6b",
5657
LLMProvider.AZUREOPENAI: "gpt-4.1-nano",
5758
LLMProvider.CEREBRAS: "gpt-oss-120b",
@@ -97,6 +98,7 @@ def provider_model_map() -> dict[LLMProvider, str]:
9798
LLMProvider.LLAMAFILE: "N/A",
9899
LLMProvider.LMSTUDIO: "qwen/qwen3-1.7b", # small model keeps the LM Studio cache under the 10GB Actions limit; you must have LM Studio running and the server enabled
99100
LLMProvider.VLLM: "Qwen/Qwen2.5-0.5B-Instruct",
101+
LLMProvider.CASCADIA: "Qwen/Qwen2.5-0.5B-Instruct",
100102
LLMProvider.COHERE: "command-a-03-2025",
101103
LLMProvider.CEREBRAS: "gpt-oss-120b",
102104
LLMProvider.HUGGINGFACE: "Qwen/Qwen2.5-72B-Instruct",
@@ -180,6 +182,7 @@ def provider_client_config() -> dict[LLMProvider, dict[str, Any]]:
180182
LLMProvider.GROQ: {"timeout": 10},
181183
LLMProvider.LLAMACPP: {"api_base": "http://127.0.0.1:8090/v1"},
182184
LLMProvider.VLLM: {"api_base": "http://127.0.0.1:8080/v1"},
185+
LLMProvider.CASCADIA: {"api_base": "http://localhost:9090/v1"},
183186
LLMProvider.MISTRAL: {"timeout_ms": 100000},
184187
LLMProvider.NEOSANTARA: {"timeout": 10},
185188
LLMProvider.NEBIUS: {"api_base": "https://api.studio.nebius.com/v1/"},

tests/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
LLMProvider.OLLAMA,
88
LLMProvider.LMSTUDIO,
99
LLMProvider.LLAMAFILE,
10+
LLMProvider.CASCADIA,
1011
]
1112

1213
# Providers that should never run in CI (only for local development)
1314
CI_EXCLUDED_PROVIDERS = [
1415
LLMProvider.AZUREANTHROPIC,
1516
LLMProvider.VERTEXAIANTHROPIC,
1617
LLMProvider.VLLM,
18+
LLMProvider.CASCADIA,
1719
]
1820

1921
# Strip whitespace and drop empties so values like "anthropic, otari" or an unset env var
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import pytest
2+
3+
from any_llm.any_llm import AnyLLM
4+
from any_llm.constants import LLMProvider
5+
from any_llm.providers.cascadia.cascadia import CascadiaProvider
6+
7+
8+
def test_provider_metadata() -> None:
9+
provider = CascadiaProvider()
10+
assert provider.PROVIDER_NAME == "cascadia"
11+
assert provider.API_BASE == "http://localhost:9090/v1"
12+
assert provider.ENV_API_KEY_NAME == "CASCADIA_API_KEY"
13+
assert provider.ENV_API_BASE_NAME == "CASCADIA_API_BASE"
14+
assert provider.PROVIDER_DOCUMENTATION_URL == "https://cascadia.to"
15+
16+
17+
def test_provider_without_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
18+
# Keyless coordinators on trusted networks must work (vLLM convention).
19+
monkeypatch.delenv("CASCADIA_API_KEY", raising=False)
20+
provider = CascadiaProvider()
21+
assert provider._verify_and_set_api_key(None) == "no-key-required"
22+
23+
24+
def test_provider_with_api_key() -> None:
25+
provider = CascadiaProvider(api_key="test-api-key")
26+
assert provider._verify_and_set_api_key("test-api-key") == "test-api-key"
27+
28+
29+
def test_api_key_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
30+
# A keyed fleet's token set via CASCADIA_API_KEY must be honored.
31+
monkeypatch.setenv("CASCADIA_API_KEY", "env-key")
32+
provider = CascadiaProvider()
33+
assert provider._verify_and_set_api_key(None) == "env-key"
34+
35+
36+
def test_explicit_api_key_takes_precedence_over_env(monkeypatch: pytest.MonkeyPatch) -> None:
37+
# An explicit key must win over CASCADIA_API_KEY in the environment.
38+
monkeypatch.setenv("CASCADIA_API_KEY", "env-key")
39+
provider = CascadiaProvider(api_key="explicit-key")
40+
assert provider._verify_and_set_api_key("explicit-key") == "explicit-key"
41+
42+
43+
def test_api_base_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
44+
monkeypatch.setenv("CASCADIA_API_BASE", "http://fleet.internal:9090/v1")
45+
provider = CascadiaProvider()
46+
assert provider._resolve_api_base(None) == "http://fleet.internal:9090/v1"
47+
48+
49+
def test_api_base_default_when_unset(monkeypatch: pytest.MonkeyPatch) -> None:
50+
monkeypatch.delenv("CASCADIA_API_BASE", raising=False)
51+
provider = CascadiaProvider()
52+
# No env var, no explicit base: falls through to the class default.
53+
assert provider._resolve_api_base(None) is None
54+
assert str(provider.client.base_url).rstrip("/") == "http://localhost:9090/v1"
55+
56+
57+
def test_capability_flags() -> None:
58+
assert CascadiaProvider.SUPPORTS_COMPLETION
59+
assert CascadiaProvider.SUPPORTS_COMPLETION_STREAMING
60+
assert CascadiaProvider.SUPPORTS_COMPLETION_REASONING
61+
assert CascadiaProvider.SUPPORTS_LIST_MODELS
62+
assert not CascadiaProvider.SUPPORTS_EMBEDDING
63+
assert not CascadiaProvider.SUPPORTS_MODERATION
64+
assert not CascadiaProvider.SUPPORTS_COMPLETION_IMAGE
65+
assert not CascadiaProvider.SUPPORTS_COMPLETION_PDF
66+
assert not CascadiaProvider.SUPPORTS_BATCH
67+
assert not CascadiaProvider.SUPPORTS_IMAGE_GENERATION
68+
assert not CascadiaProvider.SUPPORTS_RERANK
69+
assert not CascadiaProvider.SUPPORTS_RESPONSES
70+
71+
72+
def test_registered_in_enum_and_loader() -> None:
73+
assert LLMProvider.from_string("cascadia") is LLMProvider.CASCADIA
74+
assert AnyLLM.get_provider_class("cascadia") is CascadiaProvider
75+
assert "cascadia" in AnyLLM.get_supported_providers()

0 commit comments

Comments
 (0)