Metadata-Version: 2.4
Name: sdkrouter
Version: 0.1.42
Summary: Unified SDK for AI services with OpenAI compatibility
Project-URL: Homepage, https://sdkrouter.com
Project-URL: Documentation, https://sdkrouter.com
Project-URL: Repository, https://sdkrouter.com
Author-email: markolofsen <dev@markolofsen.com>
License-Expression: MIT
Keywords: ai,api,cdn,llm,ocr,openai,sdk,vision
Classifier: Development Status :: 4 - Beta
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx<1.0.0,>=0.28.0
Requires-Dist: openai<3.0.0,>=2.0.0
Requires-Dist: pydantic-settings>=2.7.0
Requires-Dist: pydantic<3.0.0,>=2.10.0
Requires-Dist: rich>=14.0.0
Requires-Dist: sdkrouter-tools>=0.1.0
Requires-Dist: tenacity>=9.1.0
Requires-Dist: tiktoken>=0.8.0
Requires-Dist: websockets>=16.0
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == 'dev'
Requires-Dist: ipykernel>=6.0.0; extra == 'dev'
Requires-Dist: jupyter>=1.0.0; extra == 'dev'
Requires-Dist: mypy>=1.15.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Requires-Dist: questionary>=2.1.0; extra == 'dev'
Requires-Dist: ruff>=0.9.0; extra == 'dev'
Requires-Dist: toml>=0.10.0; extra == 'dev'
Requires-Dist: twine>=6.0.0; extra == 'dev'
Description-Content-Type: text/markdown

![SDKRouter](https://raw.githubusercontent.com/markolofsen/assets/main/libs/sdkrouter/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, crop_to, preset management | [@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 **always runs** — it reads your project context, follows the target
image model's prompt rules (DALL-E vs Gemini), and writes an optimised prompt automatically.

```python
from sdkrouter import SDKRouter
from sdkrouter.tools.banner import BannerStyle, BannerSize

client = SDKRouter(api_key="sk_live_xxx")

# From README — LLM picks style from context automatically
result = client.banner.generate(
    source="README.md",
    output="assets/banner.png",
)
print(result.image_url)    # CDN URL
print(result.saved_to)     # local file path
print(result.cost_usd)     # total cost (LLM + image gen)

# Steer LLM aesthetic with a style enum
result = client.banner.generate(
    source="README.md",
    preset=BannerStyle.GLASSMORPHISM,  # enum — directs LLM's aesthetic
    size=BannerSize.GITHUB,            # enum — 1792x1024
    model="@smart",
    output="banner.png",
)

# String values also accepted
result = client.banner.generate(
    title="My API Gateway",
    preset="cyberpunk",
    size="social",
    output="social.png",
)

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

# Dry run — see the LLM-generated prompt before spending on image gen
result = client.banner.generate(
    source="README.md",
    preset=BannerStyle.AI_NETWORK,
    dry_run=True,
)
print(result.prompt)
```

### Visual Style Presets (`BannerStyle` enum)

Passed as `preset=` — steers the LLM's aesthetic direction. The LLM always runs
and adapts the style to your project context.

| Enum | Value | Visual Style | Best For |
|------|-------|-------------|----------|
| `BannerStyle.GLASSMORPHISM` | `"glassmorphism"` | Dark navy, frosted glass, neon teal | API gateways, SDKs, LLM routers |
| `BannerStyle.ISOMETRIC` | `"isometric"` | Pastel 3D boxes, geometric | CLI tools, data pipelines |
| `BannerStyle.AI_NETWORK` | `"ai_network"` | Glowing nodes in deep space | AI/ML, vector search, LLMs |
| `BannerStyle.CYBERPUNK` | `"cyberpunk"` | Neon circuits, matrix rain | GPU tools, real-time systems |
| `BannerStyle.BLUEPRINT` | `"blueprint"` | White lines on navy, schematic | DevOps, infrastructure |
| `BannerStyle.MINIMAL` | `"minimal"` | Smooth gradient, SaaS feel | Web frameworks, open-source |
| `BannerStyle.SPEED` | `"speed"` | Light beams, motion blur | CDN, proxy, shortlinks |
| `BannerStyle.PIXAR` | `"pixar"` | Friendly 3D mascot | Beginner tools, educational |
| `BannerStyle.HOLOGRAPHIC` | `"holographic"` | Iridescent, rainbow refraction | AI assistants, next-gen APIs |
| `BannerStyle.GRADIENT_MESH` | `"gradient_mesh"` | Organic gradient mesh | SaaS products, productivity |

### Size Presets (`BannerSize` enum)

| Enum | Value | Size | Use case |
|------|-------|------|----------|
| `BannerSize.GITHUB` | `"github"` | 1792×1024 | README.md hero image (default) |
| `BannerSize.SOCIAL` | `"social"` | 1200×630 | Twitter / LinkedIn card |
| `BannerSize.OG` | `"og"` | 1200×630 | OpenGraph meta image |
| `BannerSize.WIDE` | `"wide"` | 1792×512 | Wide website banner |
| `BannerSize.SQUARE` | `"square"` | 1024×1024 | npm / PyPI icon |

### Model-Aware Prompt Generation

The LLM generates prompts optimised for the target image model's rules:

| Model | Family | Prompt rules |
|-------|--------|-------------|
| `@balanced` → `openai/gpt-5-image-mini` | DALL-E | Natural language, negatives embedded in text |
| `@cheap` / `@fast` / `@smart` → Gemini | Gemini | Natural language, strong style adherence, hex colors |
| `openai/gpt-image-1`, `dall-e-3` | DALL-E | Natural language |
| `google/gemini-2.5-flash-image` | Gemini | 100–250 words, explicit art style names |

### 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 your own final prompt
    preset=BannerStyle.AI_NETWORK,  # visual style enum (or string)
    size=BannerSize.GITHUB,         # size enum (or string)
    model="@balanced",              # image gen model (auto-detects family)
    llm_model="@smart",             # LLM for prompt generation
    quality="hd",                   # "standard" or "hd"
    style="vivid",                  # "natural" or "vivid"
    output="banner.png",            # save to disk (optional)
    dry_run=False,                  # True = return prompt only, no image gen
)
```

## 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])
```

### Bulk Archive Ingest

Upload a ZIP or a local directory — files are converted, LLM-summarised, vectorised, and indexed asynchronously via a background job:

```python
from pathlib import Path

# Upload a ZIP archive and wait for completion
queued = client.knowbase.documents("my-docs").upload_archive("docs.zip")
client.knowbase.documents("my-docs").wait_for_archive_done(queued.job_id)

# Or ingest a local directory directly
queued = client.knowbase.documents("my-docs").upload_dir(Path("./docs"))
client.knowbase.documents("my-docs").wait_for_archive_done(queued.job_id, timeout=300)

# Poll job status manually
status = client.knowbase.documents("my-docs").job_status(queued.job_id)
print(status.status, status.progress)

# Wait for embeddings to be ready after ingest
client.knowbase.documents("my-docs").wait_for_processing(doc.id)
```

Supported file types: `.md`, `.mdx`, `.py`, `.ts`, `.tsx`, `.js`, `.go`, `.json`, `.yaml`, `.rst`, `.txt` and more.

### 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`.

## Payments

Cryptocurrency payment processing via NowPayments.

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

# Check balance
balance = client.payments.get_balance()
print(balance.currency, balance.amount)

# Create a payment invoice
payment = client.payments.create(
    amount_usd="49.00",
    currency_code="USDTTRC20",
    description="Pro plan",
    callback_url="https://example.com/webhook",
)
print(payment.payment_url)   # redirect user here
print(payment.payment_id)

# Check status
status = client.payments.check_status(payment.payment_id)
print(status.status)  # "waiting", "confirming", "confirmed", "finished"

# List recent payments
page = client.payments.list(page=1, page_size=20)
for p in page.results:
    print(p.payment_id, p.status, p.amount_usd)

# Request a withdrawal
withdrawal = client.payments.create_withdrawal(
    amount_usd="100.00",
    currency_code="USDTTRC20",
    wallet_address="T...",
)
```

## Proxies

Manage residential and datacenter proxy pools with rotation and assignment.

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

# List all proxies
proxies = client.proxies.list()

# Get healthy proxies only
healthy = client.proxies.get_healthy()
korean = client.proxies.get_korean()
by_country = client.proxies.get_by_country("US")

# Create a rotation group
rotation = client.proxies.create_rotation(
    name="scraping-pool",
    proxy_ids=[p.id for p in healthy[:10]],
)

# Assign a proxy to a task
assignment = client.proxies.create_assignment(
    proxy_id=healthy[0].id,
    task_id="job-123",
)

# Performance stats
stats = client.proxies.get_performance_stats()
```

## 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
