---
title: Select embedding models and dimensions correctly to ensure index compatibility and quality
impact: HIGH
impactDescription: "Wrong embedding model or dimension mismatch causes poor retrieval quality or index incompatibility"
tags: embeddings, models, openai, voyage, ollama, dimensions
---

## Embedding Models & API

Choose the right embedding model and use the API correctly.

**Quick Start:**
```python
from openai import OpenAI
client = OpenAI()

# Single text embedding
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Your text here"
)
vector = response.data[0].embedding  # 1536 dimensions

# Batch embedding (efficient)
texts = ["text1", "text2", "text3"]
response = client.embeddings.create(
    model="text-embedding-3-small",
    input=texts
)
vectors = [item.embedding for item in response.data]
```

**Model Selection (2026-Q2):**

| Model | Dims | Cost | Use Case |
|-------|------|------|----------|
| `text-embedding-3-small` | 1536 | $0.02/1M | General purpose (OpenAI) |
| `text-embedding-3-large` | 3072 | $0.13/1M | High accuracy (OpenAI) — Matryoshka-truncatable |
| `voyage-3` | 1024 | $0.06/1M | Production (OrchestKit default) |
| `voyage-3-lite` | 512 | $0.02/1M | Cost-tier, shorter queries, Matryoshka base |
| `voyage-3-large` | 2048 | $0.18/1M | Highest recall, multilingual + code |
| `text-embedding-004` (Gemini) | 768 | $0.025/1M | Google stack / multilingual (MRL-truncatable) |
| `nomic-embed-text` (Ollama) | 768 | Free | Local/CI |

Guidance:
- Default to `voyage-3` unless you have a reason not to. The `-lite` variant is the cost tier for short queries; `-large` wins on recall and multilingual content.
- `text-embedding-3-large` is still the strongest OpenAI option when you need Matryoshka dim reduction (3072 → 1536) without retraining.
- `text-embedding-004` is the Gemini option; use when you're already on Google's stack or need multilingual beyond Voyage's coverage.

**Similarity Calculation:**
```python
import numpy as np

def cosine_similarity(a: list[float], b: list[float]) -> float:
    a, b = np.array(a), np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 1.0 = identical, 0.0 = orthogonal
```

**Incorrect — mixing different embedding models:**
```python
# Index with one model
docs_embeddings = client.embeddings.create(
    model="text-embedding-3-large",  # 3072 dims
    input=documents
)

# Query with different model
query_embedding = client.embeddings.create(
    model="text-embedding-3-small",  # 1536 dims - MISMATCH!
    input=query
)
# Results will be nonsensical due to dimension mismatch
```

**Correct — consistent model for queries and documents:**
```python
MODEL = "text-embedding-3-small"  # Use same model everywhere

# Index
docs_embeddings = client.embeddings.create(model=MODEL, input=documents)

# Query
query_embedding = client.embeddings.create(model=MODEL, input=query)

# Now cosine similarity is meaningful
similarity = cosine_similarity(query_embedding, docs_embeddings[0])
```

**Key rules:**
- Embed queries and documents with the SAME model — never mix
- Dimension reduction: Can truncate `text-embedding-3-large` to 1536 dims (Matryoshka)
- Batch size: 100-500 texts per API call for efficiency
- Cache embeddings — never re-embed unchanged content
- Most models return normalized vectors (cosine = dot product)
