agentsclimarketplace

Catsu

Skill feyninc/skills/skills/catsu

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.From its SKILL.md

Install
npx -y skills add feyninc/skills --skill catsu

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • reads credentialsReads from 8 credential sources: `OPENAI_API_KEY` and 7 more.
  • 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 2 commands, including `pip install catsu` and 1 more.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

10.0 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

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

# Python
pip install catsu
# Requires Python 3.10+

# Rust
# Add to Cargo.toml:
# [dependencies]
# catsu = "0.1"
# tokio = { version = "1", features = ["full"] }

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>        # or GEMINI_API_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>  # + CLOUDFLARE_ACCOUNT_ID

Only the keys for providers you actually call are required.

Basic Usage

Python

from catsu import Client

client = Client()

# Embed text — provider:model format
response = client.embed(
    model="openai:text-embedding-3-small",
    input=["Hello, world!", "How are you?"]
)

print(response.embeddings)       # [[0.012, -0.034, ...], [...]]
print(response.dimensions)       # 1536
print(response.usage.tokens)     # 10
print(response.usage.cost)       # 0.000002
print(response.latency_ms)       # 142.5

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()

# 1. Provider prefix (recommended)
response = client.embed(model="voyageai:voyage-3", input="hello")

# 2. Explicit provider parameter
response = client.embed(provider="voyageai", model="voyage-3", input="hello")

# 3. Auto-detection (if model name is unique across providers)
response = client.embed(model="voyage-3", input="hello")

Input Types — Query vs Document

Many providers optimize embeddings differently for search queries vs stored documents:

# For search queries (short, question-like)
response = client.embed(
    model="voyageai:voyage-3",
    input=["what is attention?"],
    input_type="query",
)

# For documents being indexed (longer content)
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:

# OpenAI: 1536 → 256 dimensions
response = client.embed(
    model="openai:text-embedding-3-small",
    input=["hello"],
    dimensions=256,
)
print(response.dimensions)  # 256

Providers supporting custom dimensions: OpenAI, VoyageAI, Cohere, Jina, Mistral, Gemini, Nomic.

Supported Providers & Models

ProviderModelsDimensionsMax TokensInput TypeCustom Dims
OpenAItext-embedding-3-small, 3-large, ada-0021536, 3072, 15368191NoYes
VoyageAIvoyage-3, voyage-code-3, voyage-finance-2, voyage-law-2, voyage-multilingual-2, voyage-multimodal-3102432000YesYes
Cohereembed-v4.0, embed-english-v3.0, embed-multilingual-v3.01024128000Required (v3+)Yes
Jinajina-embeddings-v4, v3, jina-code-v2102432768YesYes
Mistralmistral-embed, codestral-embed-2505102432768YesYes
Geminigemini-embedding-0017682048YesYes (128-3072)
Together AIBAAI/bge models10248192NoNo
Mixedbreadmxbai-embed models1024512NoNo
Nomicnomic-embed-text-v1.57688192YesYes
DeepInfraBAAI/bge models10248192NoNo
CloudflareBGE, Qwen models768-1024512-8192NoNo

Model Discovery

from catsu import Client

# List all available models
all_models = Client.list_models()

# Filter by provider
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")

# Get specific model info
model = Client.get_model("openai", "text-embedding-3-small")
print(f"Max tokens: {model.max_tokens}")
print(f"Supports dimensions: {model.supports_dimensions}")

# Find model by name (auto-detect provider)
model = Client.find_model_by_name("voyage-3")
print(f"Provider: {model.provider}")

Model Selection Guide

Use CaseRecommended ModelWhy
General purpose, low costopenai:text-embedding-3-smallBest price/performance ratio
Highest quality retrievalvoyageai:voyage-3Top MTEB scores
Code searchvoyageai:voyage-code-3 or jina:jina-code-v2Code-optimized training
Legal / finance domainvoyageai:voyage-law-2 / voyage-finance-2Domain-specific
Multilingual contentcohere:embed-multilingual-v3.0 or voyageai:voyage-multilingual-2100+ languages
Long documents (128K)cohere:embed-v4.0128K token context
Free / self-hostedtogether:BAAI/bge-large-en-v1.5Open model, low cost
Multimodal (text + images)voyageai:voyage-multimodal-3 or jina:jina-embeddings-v4Mixed 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,              # Default: 3
    timeout=60,                 # Default: 30 seconds
)

# Retries automatically on: 429, 408, 409, 500, 502, 503, 504
# Uses exponential backoff with jitter
# Respects Retry-After headers

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

# Python
client = Client(proxy="http://proxy:8080")

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

# Sync
with Client() as client:
    response = client.embed("voyage-3", "hello")

# Async
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()   # shape: (2, 1024)

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

# Use catsu as the embedding backend
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"])

What ships with it: 1 file

2.5 KB alongside SKILL.md

references/

Gives 0 of the 12 instructions most rag retrieval skills give in ~2.5k tokens

Counted across 199 of the 213 authors here whose files we hold, read 2026-09-06

  • Enable caching for frequent queriesin 14 of 199, across 5 files
  • Enable MMR for diverse resultsin 12 of 199, across 5 files
  • Enable binary quantization to reduce memoryin 11 of 199, across 4 files
  • Initialize the database with dimensions matching the embedding modelin 11 of 199, across 4 files
  • Start the similarity threshold at 0.7in 11 of 199, across 4 files
  • Check database statistics when diagnosing slow searchin 11 of 199, across 4 files
  • Export and import vectors as JSONin 10 of 199, across 3 files
  • Match index dimension to the embedding modelin 10 of 199, across 9 files
  • Order filters cheap before expensivein 9 of 199, across 2 files
  • Generate a runnable scaffold in the user's stackin 9 of 199, across 2 files
  • Recommend multi-action scoring when frequent tuning is expectedin 9 of 199, across 2 files
  • Batch store documents for bulk insertsin 9 of 199, across 2 files

Said here and by no other author read

  • Install catsu with pip
  • Specify models using provider:model format
  • Pass input_type for query versus document inputs
  • Reduce dimensions via the dimensions parameter
  • Discover models with Client.list_models
  • Handle RateLimitError and AuthenticationError explicitly

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.