The world of embedding API clients is broken. (details)
- Everyone defaults to OpenAI's client for embeddings, even though it wasn't designed for that purpose
- Provider-specific libraries (VoyageAI, Cohere, etc.) are inconsistent, poorly maintained, or outright broken
- Universal clients like LiteLLM and any-llm-sdk don't focus on embeddings at allβthey rely on native client libraries, inheriting all their problems
- Every provider has different capabilitiesβsome support dimension changes, others don'tβwith no standardized way to discover what's available
- Most clients lack basic features like retry logic, proper error handling, and usage tracking
- There's no single source of truth for model metadata, pricing, or capabilities
Catsu fixes this. It's a lightweight, unified client built specifically for embeddings with:
π― A clean, consistent API across all providers
π Built-in retry logic with exponential backoff
π° Automatic usage and cost tracking
π Rich model metadata and capability discovery
β‘ First-class support for both sync and async
Install with uv (recommended):
uv pip install catsuOr with pip:
pip install catsuGet started in seconds! Just import catsu, create a client, and start embedding:
import catsu
# Initialize the client
client = catsu.Client()
# Generate embeddings (auto-detects provider from model name)
response = client.embed(
model="voyage-3",
input="Hello, embeddings!"
)
# Access your results
print(f"Dimensions: {response.dimensions}")
print(f"Tokens used: {response.usage.tokens}")
print(f"Cost: ${response.usage.cost:.6f}")
print(f"Embedding: {response.embeddings[0][:5]}...") # First 5 dimsThat's it! No configuration neededβcatsu picks up your API keys from environment variables automatically (VOYAGE_API_KEY, OPENAI_API_KEY, etc.).
Want more control? Specify the provider explicitly:
# Method 1: Separate parameters
response = client.embed(provider="voyageai", model="voyage-3", input="Hello!")
# Method 2: Provider prefix
response = client.embed(model="voyageai:voyage-3", input="Hello!")Need async? Just use aembed:
response = await client.aembed(model="voyage-3", input="Hello, async world!")π Want to learn more? Check out the complete documentation for detailed guides on all providers, parameters, and best practices.
Can't find your favorite model or provider? Open an issue and we will promptly try to add it! We're constantly expanding support for new embedding providers and models.
For guidelines on contributing, please see CONTRIBUTING.md.
If you found this helpful, consider giving it a β!
made with β€οΈ by chonkie, inc.