Skip to main content

llm-pycascade

Resilient cascading LLM inference with automatic failover, circuit breaking, and retry cooldowns.

A faithful Python port of the llm-cascade Rust crate.

Features

  • Cascade inference — define ordered fallback chains across multiple LLM providers
  • Automatic failover — if a provider fails, the next in the cascade is tried immediately
  • Circuit breaking / cooldowns — providers that fail repeatedly are put on exponential backoff
  • Retry-After support — respects HTTP 429 Retry-After headers
  • Failed-prompt persistence — conversations that exhaust all providers are saved as timestamped JSON
  • Multi-provider — OpenAI, Anthropic, Google Gemini, and Ollama out of the box
  • OpenAI-compatible — works with vLLM, LiteLLM, Together AI, and any OpenAI-compatible endpoint
  • Tool/function calling — first-class support across all providers
  • Async — built on httpx and aiosqlite for non-blocking I/O
  • Keyring integration — optional keyring package for secure API key storage

Architecture

┌──────────────┐     ┌──────────────────────────────────────────┐
│   User Code  │────▶│              run_cascade()               │
└──────────────┘     │                                          │
                     │  ┌─────────┐  ┌─────────┐  ┌─────────┐  │
                     │  │ Entry 1 │─▶│ Entry 2 │─▶│ Entry 3 │  │
                     │  │OpenAI   │  │Anthropic│  │ Gemini  │  │
                     │  │gpt-4o   │  │claude.. │  │gemini.. │  │
                     │  └─────────┘  └─────────┘  └─────────┘  │
                     │       ▼            ▼            ▼        │
                     │  ┌─────────────────────────────────────┐ │
                     │  │        Cooldown Tracker (SQLite)     │ │
                     │  │  ┌───────────────────────────────┐  │ │
                     │  │  │ is_on_cooldown() / set_cooldown│  │ │
                     │  │  └───────────────────────────────┘  │ │
                     │  └─────────────────────────────────────┘ │
                     │       ▼            ▼            ▼        │
                     │  ┌─────────────────────────────────────┐ │
                     │  │      Attempt Log (SQLite)            │ │
                     │  │  ┌───────────────────────────────┐  │ │
                     │  │  │ log_attempt(status, latency,  │  │ │
                     │  │  │             tokens)           │  │ │
                     │  │  └───────────────────────────────┘  │ │
                     │  └─────────────────────────────────────┘ │
                     │       ▼ (all failed)                     │
                     │  ┌─────────────────────────────────────┐ │
                     │  │  save_failed_conversation() → JSON   │ │
                     │  └─────────────────────────────────────┘ │
                     └──────────────────────────────────────────┘

Installation

pip

pip install llm-pycascade

uv

uv add llm-pycascade

With optional keyring support

pip install llm-pycascade[keyring]

Configuration

llm-pycascade can be configured from a TOML file (defaults to ~/.config/llm-pycascade/config.toml) or directly from a Python dict — useful on stateless machines where no config file can exist:

from llm_pycascade import config_from_dict

config = config_from_dict({
    "providers": {
        "openai": {"type": "openai", "api_key_env": "OPENAI_API_KEY"},
        "ollama": {"type": "ollama"},
    },
    "cascades": {
        "primary": {
            "entries": [
                {"provider": "openai", "model": "gpt-4o"},
                {"provider": "ollama", "model": "llama3.1"},
            ]
        }
    },
})

The dict uses the same schema as the TOML file (see Configuration for all options, including inline API keys via api_key_literal).

Equivalent TOML:

# config.example.toml — full example configuration

[providers.openai]
type = "openai"
api_key_env = "OPENAI_API_KEY"

[providers.anthropic]
type = "anthropic"
api_key_env = "ANTHROPIC_API_KEY"

[providers.gemini]
type = "gemini"
api_key_env = "GEMINI_API_KEY"

[providers.ollama]
type = "ollama"
# No API key needed for local Ollama

[cascades.primary]
entries = [
    { provider = "openai",    model = "gpt-4o" },
    { provider = "anthropic", model = "claude-sonnet-4-20250514" },
    { provider = "gemini",    model = "gemini-2.0-flash" },
    { provider = "ollama",    model = "llama3.1" },
]

[cascades.fast]
entries = [
    { provider = "anthropic", model = "claude-haiku-4-20250507" },
    { provider = "openai",    model = "gpt-4o-mini" },
    { provider = "ollama",    model = "mistral" },
]

[database]
path = "~/.local/share/llm-pycascade/db.sqlite"

[failure_persistence]
dir = "~/.local/share/llm-pycascade/failed_prompts"

Usage

Basic cascade

import asyncio
from llm_pycascade import (
    AppConfig,
    Conversation,
    init_db,
    load_config,
    run_cascade,
)

async def main():
    config = load_config()
    db_path = config.database.path.replace("~", "/home/user")
    conn = await init_db(db_path)

    conversation = Conversation.single_user_prompt("Explain quantum computing in one sentence.")

    try:
        response = await run_cascade("primary", conversation, config, conn)
        print(response.text_only())
    finally:
        await conn.close()

asyncio.run(main())

With tools

from llm_pycascade import ContentBlock, Message, ToolDefinition

tools = [
    ToolDefinition(
        name="get_weather",
        description="Get the current weather for a city",
        parameters={
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
            },
            "required": ["city"],
        },
    )
]

conversation = Conversation.with_tools(
    messages=[Message.user("What's the weather in Tokyo?")],
    tools=tools,
)

response = await run_cascade("primary", conversation, config, conn)

for block in response.content:
    if block.type == ContentBlockType.TEXT:
        print(f"Text: {block.text}")
    elif block.type == ContentBlockType.TOOL_CALL:
        print(f"Tool call: {block.name}({block.arguments})")

Environment variable override

export LLM_PYCASCADE_CONFIG=/path/to/custom/config.toml

Key Types

Type Module Description
run_cascade() cascade Main entry point — runs a named cascade
AppConfig config Top-level configuration (from TOML file or dict)
config_from_dict() config Build an AppConfig from a plain Python dict
load_config() config Load an AppConfig from a TOML file
ProviderConfig config Per-provider settings (type, API key, base URL)
CascadeConfig config Ordered list of provider/model entries
CascadeEntry config Single provider/model pair in a cascade
Conversation models Multi-turn conversation with optional tools
Message models Single message (role + content)
MessageRole models Enum: system, user, assistant, tool
LlmResponse models Provider response with content blocks and usage
ContentBlock models Discriminated union: text or tool_call
ToolDefinition models Tool/function schema definition
ProviderError error Provider-level failure (HTTP, parse, missing key)
CascadeError error All-providers-exhausted failure

Cooldown & Backoff

Failure # Cooldown Duration Capped At
1st 60s
2nd 120s
3rd 240s
4th 480s
5th 960s
6th+ 1920s+ 3600s (1h)

Special cases:

  • HTTP 429 with Retry-After: uses the header value directly instead of exponential backoff
  • Cooldown check: the entry is silently skipped if still on cooldown
  • Cooldown expiry: cooldowns are checked against real-time; expired entries are automatically available

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llm_pycascade-0.2.0.tar.gz (86.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llm_pycascade-0.2.0-py3-none-any.whl (30.0 kB view details)

Uploaded Python 3

File details

Details for the file llm_pycascade-0.2.0.tar.gz.

File metadata

  • Download URL: llm_pycascade-0.2.0.tar.gz
  • Upload date:
  • Size: 86.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for llm_pycascade-0.2.0.tar.gz
Algorithm Hash digest
SHA256 33c13c1a244405574f2326d42f36ee7c01815228146d3351ba5b5b300c806d76
MD5 a85c651460ca7b3a75c27069454f4ebf
BLAKE2b-256 97f5073f6605c9b10f8826ba13d50cf1ee4e4941a0a31eaac982019830ab07a3

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_pycascade-0.2.0.tar.gz:

Publisher: python-publish.yml on paluigi/llm-pycascade

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file llm_pycascade-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: llm_pycascade-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 30.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for llm_pycascade-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d44ddfeb45beeb4df9b54847d0793d1290219f0a2cf2e7663fdbb79187019f54
MD5 4e20d38b269b6c45dfb82c6f1079494a
BLAKE2b-256 75cd5c878e5f0be8ad4c49890a97dc53eedb815b27003a83e5aeff3312e404a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_pycascade-0.2.0-py3-none-any.whl:

Publisher: python-publish.yml on paluigi/llm-pycascade

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page