Metadata-Version: 2.4
Name: llm-rotate
Version: 0.2.0
Summary: Unified LLM client with key rotation, health-aware failover, and multi-provider orchestration.
Project-URL: Repository, https://github.com/Research-Commons/llm-rotate
Author: llm-rotate contributors
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: anthropic<1,>=0.85
Requires-Dist: google-auth<3,>=2.0
Requires-Dist: google-genai<2,>=1.60
Requires-Dist: openai<3,>=2.20
Requires-Dist: pydantic<3,>=2.0
Requires-Dist: typing-extensions>=4.12
Provides-Extra: dev
Requires-Dist: coverage[toml]>=7.0; extra == 'dev'
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
Provides-Extra: redis
Requires-Dist: redis[hiredis]<6,>=5.0; extra == 'redis'
Description-Content-Type: text/markdown

# llm-rotate

`llm-rotate` is a Python library for resilient LLM calls across providers.
It provides API key rotation, health-aware selection, retry/failover, and a unified chat interface.

## What it does

- Unified API for OpenAI, Anthropic, Google AI Studio, Vertex AI, and OpenRouter
- Automatic key rotation on rate-limit/auth/transient failures
- Provider fallback chains
- Streaming and sync/async chat support
- Structured per-call usage logging

## Quickstart

`llm-rotate` ships a **built-in provider catalog** covering all five supported
backends. Configure it programmatically with a plain Python dict before your
first `lm` call — no env var registry blob required.

```python
from llm_rotate import configure, lm

configure(
    registry={
        "keys": [
            {
                "key_id": "openai-1",
                "provider": "openai",
                "secret_ref": "env://OPENAI_API_KEY",
                "models": ["gpt-4o-mini"],
            },
            {
                "key_id": "gemini-1",
                "provider": "google_ai_studio",
                "secret_ref": "env://GOOGLE_API_KEY",
                "models": ["gemini-2.0-flash"],
            },
        ]
    },
    use_keys=["openai-1", "gemini-1"],
)

response = await lm.chat("gpt-4o-mini", [{"role": "user", "content": "Hello"}])
print(response.content)
```

`configure()` must be called **before** the first use of `lm`. Using `lm`
without prior `configure()` raises `ConfigurationError`.

### Required fields per key entry

| Field | Description |
|-------|-------------|
| `key_id` | Unique name for this credential (used in logs and `use_keys=[...]`) |
| `provider` | One of: `openai`, `anthropic`, `google_ai_studio`, `google_vertex`, `openrouter` |
| `secret_ref` | `env://VAR_NAME` — resolved at runtime, never stored |
| `models` | List of model IDs this key may serve (used for routing and inference) |

### Overriding built-in provider settings (optional)

If you need custom cooldown/quarantine values for a specific provider, add a
`providers` key containing only the entries you want to override — the rest keep
their built-in defaults:

```python
configure(
    registry={
        "providers": {
            "openai": {
                "provider_type": "direct",
                "display_name": "OpenAI",
                "default_cooldown_seconds": 10,
            }
        },
        "keys": [...],
    },
    use_keys=[...],
)
```

### Merge rules

1. Built-in providers (`openai`, `anthropic`, `google_ai_studio`, `google_vertex`,
   `openrouter`) are always the base layer.
2. Any provider entry in the registry dict's `"providers"` block **replaces** the
   corresponding built-in entry by name; providers not mentioned keep their
   built-in values.
3. `"keys"` always come from the caller-supplied dict — the package ships no keys,
   credentials, or `secret_ref` values.
