![SDKRouter](https://raw.githubusercontent.com/markolofsen/assets/main/libs/sdkrouter.webp)

# SDKRouter

Unified Python SDK for AI services. Access 300+ LLM models, vision, audio, image generation, search, knowledge bases, and more through a single interface.

## Installation

```bash
pip install sdkrouter
```

## Quick Start

```python
from sdkrouter import SDKRouter, Model

client = SDKRouter(api_key="your-api-key")

response = client.chat.completions.create(
    model=Model.cheap(),
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
```

## Features

| Feature | Description | Docs |
|---------|-------------|------|
| **Chat** | OpenAI-compatible completions, streaming | [@docs/01-chat.md](@docs/01-chat.md) |
| **Structured Output** | Pydantic models, JSON extraction | [@docs/02-structured-output.md](@docs/02-structured-output.md) |
| **Audio** | TTS, STT, Deepgram streaming | [@docs/03-audio.md](@docs/03-audio.md) |
| **Vision** | Image analysis, OCR | [@docs/04-vision.md](@docs/04-vision.md) |
| **Image Gen** | AI image generation | [@docs/05-image-gen.md](@docs/05-image-gen.md) |
| **Search** | Web search with modes | [@docs/06-search.md](@docs/06-search.md) |
| **CDN** | File storage | [@docs/07-cdn.md](@docs/07-cdn.md) |
| **Translator** | JSON/text translation | [@docs/08-translator.md](@docs/08-translator.md) |
| **Knowledge Base** | Vector search, GitHub crawling, MCP | [@docs/13-knowbase.md](@docs/13-knowbase.md) |
| **Banner Generator** | README/social banners with LLM prompt enhancement | [@docs/14-banner.md](@docs/14-banner.md) |
| **Payments** | Crypto payments | [@docs/09-payments.md](@docs/09-payments.md) |
| **Proxies** | Proxy management | [@docs/10-proxies.md](@docs/10-proxies.md) |
| **Embeddings** | Text embeddings | [@docs/11-embeddings.md](@docs/11-embeddings.md) |
| **Other** | Shortlinks, cleaner, models API | [@docs/12-other.md](@docs/12-other.md) |

## Model Routing

Smart model selection with IDE autocomplete:

```python
from sdkrouter import Model

Model.cheap()                    # Lowest cost
Model.smart()                    # Highest quality
Model.balanced()                 # Best value
Model.fast()                     # Fastest

# With capabilities
Model.cheap(vision=True)         # + vision
Model.smart(tools=True)          # + function calling
Model.balanced(json=True)        # + JSON mode

# Categories
Model.smart(code=True)           # Coding
Model.cheap(reasoning=True)      # Problem solving
```

## Async Support

```python
from sdkrouter import AsyncSDKRouter, Model
import asyncio

async def main():
    client = AsyncSDKRouter(api_key="your-api-key")

    response = await client.chat.completions.create(
        model=Model.cheap(),
        messages=[{"role": "user", "content": "Hello!"}]
    )

    # Parallel requests
    results = await asyncio.gather(
        client.vision.analyze(image_url="..."),
        client.audio.speech(input="Hello!"),
    )

asyncio.run(main())
```

## Audio Example

```python
from sdkrouter import SDKRouter, AudioModel

client = SDKRouter()

# Text-to-Speech
response = client.audio.speech(
    input="Hello!",
    model=AudioModel.cheap(),
    voice="nova",
)
Path("output.mp3").write_bytes(response.audio_bytes)

# Speech-to-Text
result = client.audio.transcribe(file=audio_bytes)
print(result.text)
```

### Deepgram Streaming

```python
from sdkrouter import AsyncSDKRouter
from sdkrouter.tools.audio.stt import DeepgramConfig

sdk = AsyncSDKRouter()

config = DeepgramConfig(
    model="nova-3",
    endpointing=300,   # VAD: silence threshold (ms)
    vad_events=True,   # Enable VAD events
)

async with sdk.audio.stt.stream_deepgram(config) as session:
    await session.send(audio_chunk)
    async for segment in session.transcripts():
        print(segment.text)
```

## Banner Generator

Generate hero images and banners for README.md and other project files.
The LLM reads your project context and writes an optimized image prompt automatically.

```python
client = SDKRouter(api_key="sk_live_xxx")

# From README file — LLM reads it and generates the perfect prompt
result = client.banner.generate(
    source="README.md",
    output="assets/banner.png",   # saved to disk automatically
)
print(result.image_url)    # CDN URL
print(result.saved_to)     # local file path
print(result.cost_usd)     # total cost (LLM + image gen)

# From title + description
result = client.banner.generate(
    title="My Python Library",
    description="Semantic search for developers",
    preset="github",              # 1792x1024 (default)
    output="banner.webp",
)

# From a GitHub repo — fetches README.md automatically
result = client.banner.generate(
    github_url="https://github.com/org/repo",
    preset="social",              # 1200x630 for Twitter/LinkedIn
    output="social.png",
)

# Skip LLM — use your own prompt directly
result = client.banner.generate(
    prompt="Pixar-style 3D robot coding at a futuristic terminal, vivid neon colors",
    model="@smart",
    output="custom.png",
)

# Dry run — generate the prompt without calling image gen API
result = client.banner.generate(
    source="README.md",
    dry_run=True,
)
print(result.prompt)   # see what prompt LLM generated
```

### Size Presets

| Preset | Size | Use case |
|--------|------|----------|
| `github` | 1792×1024 | README.md hero image (default) |
| `social` | 1200×630 | Twitter / LinkedIn card |
| `og` | 1200×630 | OpenGraph meta image |
| `wide` | 1792×512 | Wide website banner |
| `square` | 1024×1024 | npm / PyPI icon |

### Parameters

```python
client.banner.generate(
    source="README.md",       # local file or GitHub URL
    title="My Library",       # project title (optional)
    description="...",        # short description (optional)
    github_url="https://...", # GitHub repo URL (optional)
    prompt="...",             # skip LLM, use directly
    output="banner.png",      # save to disk (optional)
    preset="github",          # size preset
    model="@balanced",        # image gen model
    llm_model="@smart",       # LLM model for prompt enhancement
    quality="hd",             # "standard" or "hd"
    style="vivid",            # "natural" or "vivid"
    enhance_prompt=True,      # use LLM to improve prompt
    dry_run=False,            # True = return prompt only
)
```

## Knowledge Base

Manage per-user knowledge base projects with vector search and GitHub crawling.

```python
client = SDKRouter(api_key="sk_live_xxx")

# Create a project
project = client.knowbase.projects.create(
    name="My Docs",
    slug="my-docs",
    is_public=True,
)

# Add a GitHub repository as a data source
source = client.knowbase.sources("my-docs").add(
    url="https://github.com/org/repo",
    branch="main",
    path_filter="docs/",
)

# Trigger an immediate crawl
client.knowbase.sources("my-docs").crawl(source.id)

# Upload a document manually
doc = client.knowbase.documents("my-docs").upload(
    title="API Reference",
    content="# API Reference\n\nAuthenticate using Bearer tokens...",
)

# Upload a local Markdown file
doc = client.knowbase.documents("my-docs").upload_file(Path("./README.md"))

# Semantic vector search (multilingual)
results = client.knowbase.search("my-docs", "how to authenticate")
for r in results.results:
    print(f"{r.similarity:.2f}  {r.document_title}")
    print(r.content[:200])
```

### MCP Integration

Connect Claude Desktop, Cursor, or any MCP-compatible LLM client directly to your knowledge base:

```json
{
  "mcpServers": {
    "my-docs": {
      "url": "https://mcp.sdkrouter.com/mcp",
      "headers": {
        "Authorization": "Bearer sk_live_xxx"
      }
    }
  }
}
```

MCP tools exposed: `search_knowledge_base`, `list_projects`, `get_project_info`.

## Provider Selection

By default the server auto-detects the provider from the model name. You can override this explicitly:

```python
# Client-level — all requests go through this provider
client = SDKRouter(api_key="your-key", provider="openrouter")

# Per-request override via extra_body
response = client.chat.completions.create(
    model="qwen3-max",
    messages=[{"role": "user", "content": "Hi"}],
    extra_body={"provider": "alibaba"},  # overrides client-level
)
```

Available providers: `openrouter`, `openai`, `anthropic` (more coming).

## Direct Provider Routing

By default (`use_self_hosted=True`) all LLM and embedding requests go through `llm.sdkrouter.com`.
To route directly to a provider using your own API key:

```python
# OpenAI directly
client = SDKRouter(
    api_key="sk-...",
    llm_url="https://api.openai.com/v1",
    use_self_hosted=False,
)

# OpenRouter directly
client = SDKRouter(
    api_key="sk-or-...",
    llm_url="https://openrouter.ai/api/v1",
    use_self_hosted=False,
)
```

In both cases `api_url` (CDN, vision, search, knowledge base) stays on `api.sdkrouter.com` —
only LLM/embeddings traffic is redirected to `llm_url`.

## Embeddings

```python
# Via sdkrouter proxy (default, single key)
client = SDKRouter(api_key="your-sdkrouter-key")
result = client.embeddings.create(
    ["Hello world", "Semantic search"],
    model="openai/text-embedding-3-small",
)
vectors = [item.embedding for item in result.data]

# Directly via OpenAI key
client = SDKRouter(
    api_key="sk-...",
    llm_url="https://api.openai.com/v1",
    use_self_hosted=False,
)
result = client.embeddings.create("Hello world", model="text-embedding-3-small")
```

| Model | Dimensions |
|-------|-----------|
| `openai/text-embedding-3-small` | 1536 (default) |
| `openai/text-embedding-3-large` | 3072 |
| `openai/text-embedding-ada-002` | 1536 (legacy) |

## Configuration

```python
# Environment variables (auto-loaded)
# SDKROUTER_API_KEY
# SDKROUTER_LLM_URL
# SDKROUTER_API_URL
# SDKROUTER_AUDIO_URL
# SDKROUTER_MCP_URL

client = SDKRouter(
    api_key="your-key",
    timeout=60.0,
    max_retries=3,
)
```

## Prompt Caching & Metrics

For Anthropic Claude models, SDKRouter automatically applies `cache_control` breakpoints.
Cache metrics are returned in `response.usage.prompt_tokens_details`:

```python
response = client.chat.completions.create(
    model="anthropic/claude-haiku-4-5",
    messages=[...],  # long conversation
)

details = response.usage.prompt_tokens_details
if details:
    print("Cache read tokens: ", details.cached_tokens)      # billed at 10%
    print("Cache write tokens:", details.cache_write_tokens) # billed at 125%
```

No client-side changes needed — caching is transparent and automatic.

## Supported Providers

- **OpenAI**: GPT-4.5, GPT-4o, o3, o1
- **Anthropic**: Claude Opus 4.6, Claude Sonnet 4.6, Claude Haiku 4.5
- **Google**: Gemini 2.5 Pro, Gemini 2.0 Flash
- **Alibaba DashScope**: Qwen3-Max, Qwen3.5-Plus, Qwen-Plus, QwQ-32B, Qwen3-VL
- **Meta**: Llama 4, Llama 3.3
- **Mistral**: Mistral Large, Codestral
- **DeepSeek**: DeepSeek V3, R1
- And 300+ more via OpenRouter

## License

MIT
