Metadata-Version: 2.4
Name: styrr
Version: 0.1.0
Summary: Multi-model LLM router with automatic fallback — Python SDK
Project-URL: homepage, https://github.com/breakingthecloud/styrr-py
Project-URL: repository, https://github.com/breakingthecloud/styrr-py
Author-email: Carlos Cortez <carlos@finoptix.dev>
License: Apache-2.0
Keywords: bedrock,fallback,llm,openrouter,router
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: all
Requires-Dist: boto3>=1.34.0; extra == 'all'
Requires-Dist: huggingface-hub>=0.24.0; extra == 'all'
Provides-Extra: bedrock
Requires-Dist: boto3>=1.34.0; extra == 'bedrock'
Provides-Extra: huggingface
Requires-Dist: huggingface-hub>=0.24.0; extra == 'huggingface'
Description-Content-Type: text/markdown

# styrr

Multi-model LLM router with automatic fallback — Python SDK.

Never crash because a single model is rate-limited or down. Styrr chains models in order and falls through automatically.

## Install

```bash
pip install styrr
```

Optional extras:

```bash
pip install styrr[bedrock]      # AWS Bedrock support (boto3)
pip install styrr[huggingface]  # HuggingFace Inference Endpoints
pip install styrr[all]          # Everything
```

## Quick Start

```python
import os
import asyncio
from styrr import StyrRouter

async def main():
    router = StyrRouter(
        models=[
            {"id": "openai/gpt-4o-mini"},
            {"id": "nvidia/nemotron:free"},
        ],
        api_key=os.environ["OPENROUTER_API_KEY"],
    )

    result = await router.prompt("What is FinOps in 2 sentences?")
    print(result)

asyncio.run(main())
```

## Fallback Chain

Models are tried in order. If the first returns 429, 5xx, or times out, the next is tried automatically.

```python
router = StyrRouter(
    models=[
        {"id": "anthropic.claude-sonnet-4"},          # Bedrock primary
        {"id": "openai/gpt-4o-mini"},                  # OpenRouter fallback
        {"id": "nvidia/nemotron-3-super-120b:free"},   # Free fallback
    ],
    api_key=os.environ["OPENROUTER_API_KEY"],
    on_fallback=lambda f, e, n: print(f"Fell back: {f} -> {n}: {e}"),
)
```

## Tool Calling

```python
result = await router.call(
    [
        {"role": "system", "content": "You have access to tools."},
        {"role": "user", "content": "What's the weather in Paris?"},
    ],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string", "description": "City name"}
                },
                "required": ["location"],
            },
        },
    }],
)

if result.tool_calls:
    for tc in result.tool_calls:
        print(f"{tc['name']}({tc['arguments']})")
else:
    print(result.text)
```

## Providers

| Provider | Model prefix | Auth | Extra install |
|---|---|---|---|
| OpenRouter / OpenAI | `openai/*`, `nvidia/*`, `meta-llama/*`, etc. | `api_key` | — |
| AWS Bedrock | `anthropic.claude-*`, `amazon.*`, `bedrock/*` | AWS credentials | `styrr[bedrock]` |
| HuggingFace | `huggingface/*`, `hf_*` | HF token | `styrr[huggingface]` |

Provider detection is automatic based on model ID.

## Streaming

```python
async for event in router.stream([{"role": "user", "content": "Hi"}]):
    if event["type"] == "text_delta":
        print(event["delta"], end="", flush=True)
    elif event["type"] == "done":
        print(f'\nDone — model: {event["model_used"]}')
    elif event["type"] == "error":
        print(f'Error: {event["error"]}')
```

## API

### `StyrRouter`

| Method | Returns | Description |
|---|---|---|
| `call(messages, tools?, temperature?, max_tokens?)` | `StyrResponse` | Non-streaming call with fallback |
| `stream(messages, tools?, temperature?, max_tokens?)` | `AsyncGenerator[dict]` | Streaming with fallback |
| `prompt(user_message, system_prompt?, **kwargs)` | `str` | Simple prompt helper |

### `StyrResponse`

| Field | Type | Description |
|---|---|---|
| `text` | `str` | Response text |
| `model_used` | `str` | Which model responded |
| `latency_ms` | `int` | Round-trip latency |
| `fallbacks_tried` | `int` | How many models were tried before success |
| `tool_calls` | `list[dict] \| None` | Tool call requests (if any) |
| `usage` | `dict \| None` | Token usage if available |

## Architecture

```
Your App
  └─ StyrRouter
       ├─ OpenAICompatProvider  (OpenRouter, OpenAI, NVIDIA, etc.)
       ├─ BedrockProvider       (AWS Bedrock)
       └─ HuggingFaceProvider   (HF Inference Endpoints)
```

## Running Tests

```bash
pip install pytest pytest-asyncio httpx
pytest tests/
```

## License

Apache-2.0
