Getting a working voyage ai embeddings python integration takes about ten minutes if you know which client to use and how the response is shaped. This guide walks from an empty virtual environment to a verified embedding call, then to batching and basic similarity checks you can trust in production.
Step 1: Create a Voyage AI account and grab an API key
Sign up at the Voyage AI console and generate an API key. The key carries per-account rate limits and billing, so treat it like a secret. Do not hard-code it in source files that get committed.
If you already run LLM inference through a gateway, you can keep Voyage credentials separate—embedding providers are independent of chat completions.
Step 2: Install the Python client
The official voyageai package wraps the REST API and handles auth, retries, and response parsing. Install it in a clean environment:
python -m venv .venv
source .venv/bin/activate
pip install voyageai
If you prefer raw HTTP, requests is sufficient. But the client saves boilerplate and surfaces typed errors.
Step 3: Configure credentials securely
Export the key as an environment variable. In bash:
export VOYAGE_API_KEY="pa-xxxxxxxxxxxxxxxx"
In Python, read it back without fallback defaults that mask missing config:
import os
api_key = os.environ["VOYAGE_API_KEY"] # raises KeyError if unset
For local development, python-dotenv is fine. In CI or containers, inject the secret at runtime.
Step 4: Send your first embedding request
The voyage ai embeddings python client exposes a single embed method. Pass a list of strings and a model name. Even a single string must be wrapped in a list.
import voyageai
import os
client = voyageai.Client(api_key=os.environ["VOYAGE_API_KEY"])
texts = ["The quick brown fox jumps over the lazy dog"]
result = client.embed(texts, model="voyage-2")
print(type(result.embeddings)) # <class 'list'>
print(len(result.embeddings)) # 1
print(len(result.embeddings[0])) # 1024 for voyage-2
Response structure
The returned object is not a raw dict. It has two useful attributes:
embeddings: a list of vectors, one per input string.total_tokens: integer count of billed tokens across the batch.
A JSON view (if you call the HTTP endpoint directly) looks like:
{
"object": "list",
"data": [
{"object": "embedding", "embedding": [0.01, -0.02, ...], "index": 0}
],
"model": "voyage-2",
"usage": {"total_tokens": 8}
}
The client flattens data[*].embedding into result.embeddings.
Step 5: Embed multiple texts in one call
Batching reduces HTTP overhead and token accounting round-trips. Pass a list of any length under the model’s max batch size (typically 128 inputs per request; check current docs).
docs = [
"Postgres connection pool exhausted under load",
"Kubernetes liveness probe keeps restarting the pod",
"Vector search latency grows with index size",
]
batch = client.embed(docs, model="voyage-2")
assert len(batch.embeddings) == 3
assert all(len(v) == 1024 for v in batch.embeddings)
print(f"billed tokens: {batch.total_tokens}")
Keep individual inputs under the model’s token limit. voyage-2 and voyage-large-2 cap at 4000 tokens per input; voyage-code-2 allows 16000. Overflow is silently truncated, not rejected.
Step 6: Pick the right model for your data
Voyage ships several embedding models. The three you will actually use:
voyage-2: 1024 dimensions, general text, cheap.voyage-large-2: 1536 dimensions, higher quality on semantic search.voyage-code-2: 1536 dimensions, tuned for source code and technical docs.
Dimensionality and token limits
Dimensionality drives vector DB storage and cosine compute cost. If you are prototyping, start with voyage-2 to keep memory small. Switch to voyage-large-2 only after a blind A/B test on your own queries shows recall improvement.
# code retrieval example
code_snippets = ["def foo(): return 1", "class Bar: pass"]
emb = client.embed(code_snippets, model="voyage-code-2")
print(len(emb.embeddings[0])) # 1536
Step 7: Handle errors and rate limits
The client raises subclasses of voyageai.error.APIError. Rate limits return HTTP 429. Wrap calls in a retry with backoff:
import time
from voyageai.error import RateLimitError, APIError
def embed_with_retry(client, texts, model, tries=3):
for i in range(tries):
try:
return client.embed(texts, model=model)
except RateLimitError:
if i == tries - 1:
raise
time.sleep(2 ** i) # 1s, 2s, 4s
except APIError as e:
# non-retryable server error or bad input
raise
If you use requests directly, inspect resp.status_code and resp.json().get("error"). Do not assume every non-200 is retryable; 400 means malformed input.
Step 8: Verify the embeddings are correct
A vector is useless if you cannot confirm it encodes meaning. The fastest check is cosine similarity between known-related and unrelated sentences.
Cosine similarity sanity check
import numpy as np
def cosine(a, b):
a = np.array(a, dtype=np.float32)
b = np.array(b, dtype=np.float32)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
pair = client.embed(
["database timeout in production", "prod db connection timed out"],
model="voyage-2",
)
unrelated = client.embed(["the weather is sunny today"], model="voyage-2")
sim_near = cosine(pair.embeddings[0], pair.embeddings[1])
sim_far = cosine(pair.embeddings[0], unrelated.embeddings[0])
print(f"near: {sim_near:.3f} far: {sim_far:.3f}")
Expect near above 0.8 and far below 0.3 for voyage-2. If numbers are flipped, you likely swapped inputs or used a random vector by mistake. This check belongs in your integration test suite.
Step 9: Plug into a real workflow
Embeddings are only useful when persisted or compared. Below is a minimal in-memory nearest-neighbor lookup using numpy—replace with Pinecone, pgvector, or FAISS in production.
corpus = ["refund policy", "shipping times", "cancel subscription"]
corpus_emb = client.embed(corpus, model="voyage-2").embeddings
query = client.embed(["how do I get my money back"], model="voyage-2").embeddings[0]
sims = [cosine(query, c) for c in corpus_emb]
best_idx = int(np.argmax(sims))
print(f"best match: {corpus[best_idx]} ({sims[best_idx]:.3f})")
This pattern scales to thousands of vectors locally. Beyond that, use a vector index that supports metadata filtering.
Step 10: Clean up and next steps
You now have a repeatable voyage ai embeddings python path: key in env, client installed, single and batch calls working, model chosen by data type, errors retried, and output verified by cosine check.
Next, move the embedding call behind a small function in your service layer so you can swap models without touching call sites. Log total_tokens per request to track cost. If you later add LLM completions alongside retrieval, keep the embedding client isolated—its failure modes (truncation, dimension mismatch) are different from chat inference.
The voyage ai embeddings python client is stable enough for production, but pin the version in your requirements file to avoid silent response-shape changes.