Skip to main content

Self-healing unified LLM routing SDK for Anthropic + 350+ OpenRouter models

Project description

plaudapi

Standard wrappers fail when providers flake. plaudapi heals.

PyPI version Python 3.11+ License: MIT Tests

A unified Python SDK for Anthropic + 350+ OpenRouter models with dynamic routing, automatic failover, and heuristic self-repair. One interface. Zero babysitting.


Install

pip install plaudapi
plaudapi setup          # saves API keys to ~/.plaudapi/.env

Quickstart

from plaudapi import PlaudClient

client = PlaudClient()   # FREE_FIRST by default — no API key required to start
resp = client.chat([{"role": "user", "content": "Explain transformers in 2 sentences."}])
print(resp.content)

That's it. plaudapi picks the best available free model from the live OpenRouter catalog, falls back to a hardcoded default if the catalog is unreachable, and retries transient failures with full-jitter backoff — all without any extra wiring.


Why plaudapi?

Raw SDK plaudapi
Multi-provider One package per provider Single unified interface
Free models Manual catalog lookup FREE_FIRST auto-selects from live catalog
Price optimization Hardcode a model and hope COST_OPTIMIZED sorts 350+ models by live pricing
Failover Write your own try/except loop Built-in jitter retry + strategy fallback
Malformed JSON json.JSONDecodeError crashes Heuristic repair closes unclosed braces/quotes
Context overrun Provider 400 error Pre-flight token estimate raises PlaudContextWindowError
Auth errors Confusing 401 response PlaudAuthError tells you exactly which env var is missing
Streaming Different API per provider client.stream() / client.astream() everywhere

Routing Strategies

Pass strategy= to PlaudClient() to change routing behaviour. All 19 strategies share the same chat() / stream() / achat() / astream() API.

Strategy Default model Notes
FREE_FIRST (default) nvidia/nemotron-3-super-120b-a12b:free Dynamically picks any free model
COST_OPTIMIZED dynamic Sorts live catalog by price ascending
QUALITY_FIRST anthropic/claude-opus-4.6-fast Highest context paid model
ANTHROPIC_DIRECT claude-sonnet-4-6 Native Anthropic API (no OpenRouter)
ANTHROPIC anthropic/claude-sonnet-4.6 via OpenRouter
OPENAI openai/gpt-4o via OpenRouter (62 models)
GOOGLE google/gemini-2.0-flash-001 via OpenRouter (32 models)
META meta-llama/llama-4-maverick via OpenRouter (14 models)
DEEPSEEK deepseek/deepseek-v3.2 via OpenRouter
MISTRAL mistralai/mistral-small-2603 via OpenRouter
XAI x-ai/grok-4.20 via OpenRouter
PERPLEXITY perplexity/sonar-pro Search-grounded
COHERE cohere/command-a via OpenRouter
NOUS nousresearch/hermes-4-405b Hermes family
QWEN qwen/qwen3.5-flash-02-23 Alibaba, via OpenRouter
MINIMAX minimax/minimax-m1 via OpenRouter
KIMI moonshotai/kimi-k2.5 Moonshot AI
ZAI z-ai/glm-5.1 GLM family
NVIDIA nvidia/nemotron-3-super-120b-a12b Nemotron family
from plaudapi import PlaudClient
from plaudapi.router import RoutingStrategy

# Pin to a specific provider family
client = PlaudClient(strategy=RoutingStrategy.DEEPSEEK)

# Or override the model directly while keeping strategy routing
resp = client.chat(
    messages=[{"role": "user", "content": "Hello"}],
    model="deepseek/deepseek-r1",
)

Streaming

Sync streaming

from plaudapi import PlaudClient

client = PlaudClient(strategy="quality_first")

for chunk in client.stream([{"role": "user", "content": "Write me a haiku."}]):
    print(chunk.delta, end="", flush=True)
    if chunk.done:
        print()  # usage stats available on final chunk

Async streaming

import asyncio
from plaudapi import PlaudClient

async def main():
    client = PlaudClient(strategy="openai")
    async for chunk in client.astream(
        [{"role": "user", "content": "Count to 5, one word per line."}]
    ):
        print(chunk.delta, end="", flush=True)

asyncio.run(main())

Browse the Model Catalog

The catalog is fetched from OpenRouter with a 1-hour TTL cache at ~/.plaudapi/models_cache.json. No key required for reads.

List all models

models = client.list_models()
print(f"{len(models)} models available")

# Async
models = await client.alist_models()

Filter with find_models()

# All free models with at least 128k context
free_large = client.find_models(free=True, min_context=128_000)

# Anthropic models sorted cheapest-first
cheap_anthropic = client.find_models(
    provider="anthropic",
    free=False,
    sort_by="price_asc",
)

# Vision-capable models
vision = client.find_models(modality="image", sort_by="context_desc")

for m in cheap_anthropic[:3]:
    print(m["id"], "-", m["pricing"]["prompt"], "$/token")

find_models() supports: free, provider, min_context, modality, sort_by (price_asc, price_desc, context_desc).


Self-Healing

plaudapi bakes six self-healing behaviours directly into the client. You don't configure them — they just work.

1. Auth pre-check

Before the first network call, plaudapi verifies that the required API key is present. Missing keys raise PlaudAuthError immediately with the exact env var name and the fix:

PlaudAuthError: OPENROUTER_API_KEY is not set or invalid.
Run 'plaudapi setup' or add it to ~/.plaudapi/.env

2. Routing fallback

If the live OpenRouter catalog is unreachable (network error, rate limit, timeout), the router silently falls back to a hardcoded default model for the chosen strategy. Your code never sees the exception.

3. JSON auto-repair

When a model returns malformed JSON — trailing commas, unclosed braces, unterminated strings — repair_json() applies heuristic patches before surfacing the result:

from plaudapi.repair import repair_json

parsed, was_repaired = repair_json('{"key": "value",}')
# parsed = {"key": "value"}, was_repaired = True

4. Retry with full-jitter backoff

Network errors and 429 rate limits are retried up to 3 times using full-jitter exponential backoff (random delay in [0, min(cap, base * 2^attempt)]). Works identically for sync and async code.

5. strict_free guardrail

Set strict_free=True to guarantee your app never accidentally uses a paid model. If no free models are found in the catalog, plaudapi raises PlaudFallbackExhaustedError rather than silently upgrading to a paid tier.

client = PlaudClient(strict_free=True)  # raises if zero free models available

6. Context window pre-check

PlaudRequest.estimated_tokens() counts your message length before sending. If it would exceed the model's context limit, PlaudContextWindowError is raised with the token count and the limit — so you can trim the message rather than burn the round-trip.

PlaudContextWindowError: Request has 142000 tokens but model limit is 128000.
Reduce your message length or choose a model with a larger context window.

Debug Mode

Set debug=True to print per-call telemetry to stdout:

client = PlaudClient(debug=True)
resp = client.chat([{"role": "user", "content": "Hi"}])
# [plaudapi] openai/gpt-4o · 12 in / 38 out · $0.00043 · 623ms · openrouter

Exception Reference

PlaudError                        # base
├── PlaudNetworkError             # timeouts, connection failures
│   └── PlaudRateLimitError       # HTTP 429, retries exhausted
├── PlaudAuthError                # missing/invalid API key
├── PlaudFallbackExhaustedError   # strict_free=True, no free models
└── PlaudLogicError               # bad request shape
    ├── PlaudContextWindowError   # exceeds model context limit
    ├── PlaudModelNotFoundError   # model ID not in catalog (with suggestions)
    └── PlaudContentPolicyError   # HTTP 403, provider safety filter

Catch at whatever level makes sense for your application:

from plaudapi.exceptions import PlaudError, PlaudRateLimitError

try:
    resp = client.chat(messages)
except PlaudRateLimitError:
    # all retries failed — back off at the application level
    ...
except PlaudError as e:
    # catch-all for any plaudapi error
    ...

License

MIT — see LICENSE.

Project details


Download files

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

Source Distribution

plaudapi-0.1.0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

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

plaudapi-0.1.0-py3-none-any.whl (20.1 kB view details)

Uploaded Python 3

File details

Details for the file plaudapi-0.1.0.tar.gz.

File metadata

  • Download URL: plaudapi-0.1.0.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for plaudapi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4517e18e684dea4d48cb9cd4584686ce089bed3085ae36feff67e01a7fb8ba72
MD5 0d0b2c4c84e6d3f0704afc9e78a477d5
BLAKE2b-256 7217a61cc953c961e914dc1e3fa935d9e4cdaa1f026b0ae27ebbfc836de28b01

See more details on using hashes here.

File details

Details for the file plaudapi-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: plaudapi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for plaudapi-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 48780bfa0b5907cca6c289072e10cee9af079c8f0ebc124731bda0c0b0538b71
MD5 984feb036a9ee530a52e9caa700ebe17
BLAKE2b-256 c3081d1ca32d3d42628ac0afafc3ae6e38cb836b51356500c2423e83d620e75b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page