nodus-llm
Multi-credential LLM client with automatic failover and provider abstraction.
Rotates through ordered credential profiles when an LLM provider fails — handling rate limits, auth errors, billing limits, and context overflow — with exponential per-credential backoff (5m→10m→20m→40m→1h). No required external dependencies; provider SDKs are optional extras.
Status: v0.1.0 — prepared, not yet published.
Install
pip install nodus-llm
# With OpenAI support:
pip install "nodus-llm[openai]"
# With Anthropic support:
pip install "nodus-llm[anthropic]"
# Both:
pip install "nodus-llm[all]"
What it provides
| Component | Purpose |
|---|---|
CredentialProfile |
One API key + provider + model with cooldown state |
CredentialStore |
Ordered profile list with availability tracking |
FailoverClient |
Rotates profiles on failure with exponential backoff |
FailoverError |
Raised when all profiles are exhausted |
FailoverReason |
RATE_LIMIT | AUTH | BILLING | CONTEXT_OVERFLOW | EXHAUSTED |
context_window_for(model) |
Token limit for a known model |
would_overflow(messages, model) |
Context window check before sending |
CONTEXT_WINDOWS |
Dict of model name → token limit |
Quick start
from nodus_llm import CredentialProfile, CredentialStore, FailoverClient
from nodus_llm.providers.openai import OpenAIProvider
profiles = [
CredentialProfile(api_key="sk-primary", provider="openai", model="gpt-4o"),
CredentialProfile(api_key="sk-backup", provider="openai", model="gpt-4o-mini"),
]
store = CredentialStore(profiles=profiles)
client = FailoverClient(store, provider_fn=OpenAIProvider)
reply = client.chat([{"role": "user", "content": "Hello!"}])
CredentialProfile and CredentialStore
from nodus_llm import CredentialProfile, CredentialStore
profile = CredentialProfile(
api_key="sk-abc123",
provider="openai", # "openai" | "anthropic" | custom string
model="gpt-4o",
max_tokens=4096, # optional per-profile override
)
store = CredentialStore(profiles=[profile])
available = store.available_profiles() # profiles not in cooldown
store.mark_failed(profile) # starts exponential cooldown
store.mark_success(profile) # clears cooldown
Cooldown schedule: 5m → 10m → 20m → 40m → 1h (exponential, capped at 1h).
FailoverClient
from nodus_llm import FailoverClient, FailoverError, FailoverReason
client = FailoverClient(
store,
provider_fn=OpenAIProvider, # factory: (profile) → LLMClient
)
try:
reply = client.chat(
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=256,
)
except FailoverError as exc:
print(exc.reason) # FailoverReason.EXHAUSTED
FailoverClient tries profiles in order. On failure it:
- Classifies the error →
FailoverReason - Marks the profile failed (starts cooldown)
- Tries the next available profile
- Raises
FailoverErrorif all profiles are exhausted
Providers
OpenAI
from nodus_llm.providers.openai import OpenAIProvider
client = FailoverClient(store, provider_fn=OpenAIProvider)
Requires pip install "nodus-llm[openai]".
Anthropic
from nodus_llm.providers.anthropic import AnthropicProvider
client = FailoverClient(store, provider_fn=AnthropicProvider)
Requires pip install "nodus-llm[anthropic]".
OpenAI-compatible endpoints
from nodus_llm.providers.compat import OpenAICompatProvider
profile = CredentialProfile(
api_key="local-key",
provider="local",
model="llama-3.1-8b",
base_url="http://localhost:11434/v1",
)
client = FailoverClient(store, provider_fn=OpenAICompatProvider)
Context window utilities
from nodus_llm import context_window_for, would_overflow, CONTEXT_WINDOWS
limit = context_window_for("gpt-4o") # 128000 | None (unknown model)
messages = [{"role": "user", "content": "..." * 10000}]
if would_overflow(messages, "gpt-4o-mini"):
# truncate or summarise before sending
...
# All known limits
print(CONTEXT_WINDOWS) # {"gpt-4o": 128000, "claude-3-5-sonnet-20241022": 200000, ...}
Custom provider
Any callable (CredentialProfile) -> LLMClient works as provider_fn.
LLMClient is a structural protocol — any object with
chat(messages, **kwargs) -> str satisfies it.
class MyProvider:
def __init__(self, profile): self.profile = profile
def chat(self, messages, **kwargs): return "custom response"
client = FailoverClient(store, provider_fn=MyProvider)
Design
- No required dependencies. Core credential management and failover are pure stdlib. Provider SDKs are opt-in extras.
nodus-circuit-breakeris type-check only. TheLLMClientprotocol is referenced viaTYPE_CHECKING— no runtime import.- Exponential backoff per credential. Each profile has independent cooldown state; a failed profile is skipped until its cooldown expires.
Development
pip install -e ".[dev]"
pytest tests/ -q
License
MIT — see LICENSE.
Metadata
Release files for nodus-llm 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nodus_llm-0.1.0.tar.gz | 13.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nodus_llm-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.8 kB
Release files / nodus_llm-0.1.0.tar.gz
| Download URL | nodus_llm-0.1.0.tar.gz |
|---|---|
| Size | 13.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
39fe51899bcf17a7ae593115323c7f0e33a777114eb9de67d21cdb98c4d503e6
|
|
BLAKE2b-256 checksum How to use checksums |
c221600cea26078e3f8b88e2be5bdbef5f30615bd0cac4efdec24aae6d861524
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / nodus_llm-0.1.0-py3-none-any.whl
| Download URL | nodus_llm-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e281b49b087893e2d12b2fe96b64fa1b8ec2fd54a7351083d1b106c9cf525c83
|
|
BLAKE2b-256 checksum How to use checksums |
230594be79676618399635b9954009d27243c29a25cc621f4874e1189f874643
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|