| name | catsu |
| description | Use Catsu for unified, high-performance embedding API calls across 11 providers (OpenAI, VoyageAI, Cohere, Jina, Mistral, Gemini, Together AI, Mixedbread, Nomic, DeepInfra, Cloudflare) through a single consistent interface. Covers model selection and discovery, automatic retry with exponential backoff, cost and token tracking, Matryoshka dimension reduction, input type hints (query vs document), async/await support, and per-request API key overrides. Use when: generating embeddings, comparing embedding providers, building search or RAG systems, or integrating embeddings into Python or Rust applications. |
| license | MIT |
| metadata | {"author":"chonkie-inc","version":"0.1.0","category":"embeddings","repository":"https://github.com/chonkie-inc/catsu"} |
| compatibility | Python 3.10+ or Rust 2021 edition. Python package uses Rust core via PyO3 bindings. Requires API keys for chosen providers via environment variables. |
Catsu — Unified Embedding API Client
Catsu provides a single, consistent interface for generating embeddings across 11 providers and 35+ models. Built-in retry logic, cost tracking, and model discovery eliminate the need for provider-specific SDKs.
When to Use This Skill
Use this skill when users want to:
- Generate embeddings from any provider through a unified API
- Compare embedding models across providers (cost, quality, dimensions)
- Add embeddings to a RAG pipeline, search system, or recommendation engine
- Switch between providers without changing application code
- Track token usage and costs across embedding calls
- Use Matryoshka embeddings (reduced dimensions) for storage optimization
Installation
pip install catsu
Setup — API Keys
Set environment variables for the providers you use:
export OPENAI_API_KEY=<your-openai-key>
export VOYAGE_API_KEY=<your-voyage-key>
export COHERE_API_KEY=<your-cohere-key>
export JINA_API_KEY=<your-jina-key>
export MISTRAL_API_KEY=<your-mistral-key>
export GOOGLE_API_KEY=<your-google-key>
export TOGETHER_API_KEY=<your-together-key>
export MIXEDBREAD_API_KEY=<your-mixedbread-key>
export NOMIC_API_KEY=<your-nomic-key>
export DEEPINFRA_API_KEY=<your-deepinfra-key>
export CLOUDFLARE_API_TOKEN=<your-cloudflare-token>
Only the keys for providers you actually call are required.
Basic Usage
Python
from catsu import Client
client = Client()
response = client.embed(
model="openai:text-embedding-3-small",
input=["Hello, world!", "How are you?"]
)
print(response.embeddings)
print(response.dimensions)
print(response.usage.tokens)
print(response.usage.cost)
print(response.latency_ms)
Rust
use catsu::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new()?;
let response = client.embed(
"openai:text-embedding-3-small",
vec!["Hello, world!".to_string()],
).await?;
println!("Dimensions: {}", response.dimensions);
println!("Cost: ${}", response.usage.cost.unwrap_or(0.0));
Ok(())
}
Model Specification
Three ways to specify which model to use:
client = Client()
response = client.embed(model="voyageai:voyage-3", input="hello")
response = client.embed(provider="voyageai", model="voyage-3", input="hello")
response = client.embed(model="voyage-3", input="hello")
Input Types — Query vs Document
Many providers optimize embeddings differently for search queries vs stored documents:
response = client.embed(
model="voyageai:voyage-3",
input=["what is attention?"],
input_type="query",
)
response = client.embed(
model="voyageai:voyage-3",
input=["The attention mechanism allows models to..."],
input_type="document",
)
Providers that support input types: VoyageAI, Cohere (required for v3+), Jina, Mistral, Gemini, Nomic.
Custom Dimensions (Matryoshka Embeddings)
Reduce embedding dimensions for storage/speed at slight quality cost:
response = client.embed(
model="openai:text-embedding-3-small",
input=["hello"],
dimensions=256,
)
print(response.dimensions)
Providers supporting custom dimensions: OpenAI, VoyageAI, Cohere, Jina, Mistral, Gemini, Nomic.
Supported Providers & Models
| Provider | Models | Dimensions | Max Tokens | Input Type | Custom Dims |
|---|
| OpenAI | text-embedding-3-small, 3-large, ada-002 | 1536, 3072, 1536 | 8191 | No | Yes |
| VoyageAI | voyage-3, voyage-code-3, voyage-finance-2, voyage-law-2, voyage-multilingual-2, voyage-multimodal-3 | 1024 | 32000 | Yes | Yes |
| Cohere | embed-v4.0, embed-english-v3.0, embed-multilingual-v3.0 | 1024 | 128000 | Required (v3+) | Yes |
| Jina | jina-embeddings-v4, v3, jina-code-v2 | 1024 | 32768 | Yes | Yes |
| Mistral | mistral-embed, codestral-embed-2505 | 1024 | 32768 | Yes | Yes |
| Gemini | gemini-embedding-001 | 768 | 2048 | Yes | Yes (128-3072) |
| Together AI | BAAI/bge models | 1024 | 8192 | No | No |
| Mixedbread | mxbai-embed models | 1024 | 512 | No | No |
| Nomic | nomic-embed-text-v1.5 | 768 | 8192 | Yes | Yes |
| DeepInfra | BAAI/bge models | 1024 | 8192 | No | No |
| Cloudflare | BGE, Qwen models | 768-1024 | 512-8192 | No | No |
Model Discovery
from catsu import Client
all_models = Client.list_models()
openai_models = Client.list_models("openai")
for m in openai_models:
print(f"{m.name}: {m.dimensions}d, ${m.cost_per_million_tokens}/M tokens")
model = Client.get_model("openai", "text-embedding-3-small")
print(f"Max tokens: {model.max_tokens}")
print(f"Supports dimensions: {model.supports_dimensions}")
model = Client.find_model_by_name("voyage-3")
print(f"Provider: {model.provider}")
Model Selection Guide
| Use Case | Recommended Model | Why |
|---|
| General purpose, low cost | openai:text-embedding-3-small | Best price/performance ratio |
| Highest quality retrieval | voyageai:voyage-3 | Top MTEB scores |
| Code search | voyageai:voyage-code-3 or jina:jina-code-v2 | Code-optimized training |
| Legal / finance domain | voyageai:voyage-law-2 / voyage-finance-2 | Domain-specific |
| Multilingual content | cohere:embed-multilingual-v3.0 or voyageai:voyage-multilingual-2 | 100+ languages |
| Long documents (128K) | cohere:embed-v4.0 | 128K token context |
| Free / self-hosted | together:BAAI/bge-large-en-v1.5 | Open model, low cost |
| Multimodal (text + images) | voyageai:voyage-multimodal-3 or jina:jina-embeddings-v4 | Mixed content |
See references/model_comparison.md for detailed benchmarks and cost analysis.
Retry & Error Handling
Catsu handles transient failures automatically:
client = Client(
max_retries=5,
timeout=60,
)
Explicit error handling
from catsu.utils.errors import (
RateLimitError,
AuthenticationError,
ModelNotFoundError,
MissingApiKeyError,
)
try:
response = client.embed(model="voyage-3", input="hello")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after}s")
except AuthenticationError:
print("Invalid API key")
except ModelNotFoundError as e:
print(f"Unknown model: {e}")
except MissingApiKeyError as e:
print(f"Set {e.provider} API key")
Advanced Configuration
Per-request API key override
import os
response = client.embed(
model="openai:text-embedding-3-small",
input="hello",
api_key=os.environ["ALTERNATE_OPENAI_KEY"],
)
HTTP proxy and custom CA
client = Client(proxy="http://proxy:8080")
let config = HttpConfig {
proxy: Some("http://proxy:8080".to_string()),
ca_cert_pem: Some(cert_pem_string),
..Default::default()
};
let client = Client::with_config(config)?;
Context managers for cleanup
with Client() as client:
response = client.embed("voyage-3", "hello")
async with Client() as client:
response = await client.aembed("voyage-3", "hello")
NumPy conversion
response = client.embed("voyage-3", ["text1", "text2"])
arr = response.to_numpy()
Async Support
import asyncio
from catsu import Client
async def embed_batch():
async with Client() as client:
response = await client.aembed(
model="openai:text-embedding-3-small",
input=["text1", "text2", "text3"],
)
return response.embeddings
embeddings = asyncio.run(embed_batch())
Integration with Chonkie
Catsu works as an embedding provider for Chonkie's RAG pipelines:
pip install "chonkie[catsu]"
from chonkie import Pipeline
docs = (Pipeline()
.chunk_with("recursive", chunk_size=512)
.refine_with("embeddings", embedding_model="catsu:voyage-3")
.store_in("qdrant", url="http://localhost:6333", collection="docs")
.run(texts=documents)
)
Or use Catsu's embeddings directly with Chonkie's AutoEmbeddings:
from chonkie import AutoEmbeddings
embed = AutoEmbeddings.get_embeddings("catsu:openai:text-embedding-3-small")
vectors = embed.embed_batch(["text1", "text2"])