Last updated 2026-05-17

Voyage AI Provider

The voyage provider implements Embedder only. Voyage AI specialises in retrieval-grade embeddings and is the recommended embedding partner for Anthropic Claude users — Anthropic does not offer first-party embeddings, and llmrouter pairs Claude with Voyage by default in its examples.

Import path

import (
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/voyage"
)

Default endpoint

With no WithBaseURL override, the provider targets https://api.voyageai.com/v1. Embedding requests go to {BaseURL}/embeddings.

Construction

p, err := voyage.New(
llmrouter.WithAPIKey(os.Getenv("VOYAGE_API_KEY")),
)
if err != nil {
log.Fatal(err)
}

Authentication is via Authorization: Bearer <key>.

Models

  • voyage-3 — current general-purpose model (1024 dims, supports 256/512/1024/2048 via Dimensions). The recommended default for most retrieval tasks.
  • voyage-3-large — higher quality, also 1024 dims with the same dimension options. Use this when retrieval accuracy matters more than cost.
  • voyage-3-lite — 512 dims, lowest cost. Strong baseline for small-scale RAG.
  • voyage-code-3 — tuned for source code, identifiers, and technical documentation.
  • voyage-finance-2 — tuned for financial documents, filings, and earnings transcripts.
  • voyage-law-2 — tuned for legal documents and caselaw.
  • voyage-multilingual-2 — multilingual support across 29 languages, 1024 dims. Use this for non-English corpora.

Input type

Voyage distinguishes query embeddings from document embeddings — using the wrong one significantly hurts retrieval recall. The library maps TaskType as follows:

Canonical TaskType Voyage input_type
RETRIEVAL_QUERY / QUESTION_ANSWERINGquery
RETRIEVAL_DOCUMENTdocument
SEMANTIC_SIMILARITYdocument
CLASSIFICATIONdocument
CLUSTERINGdocument

Empty TaskType sends no input_type, which Voyage treats as document.

Output dimension

Voyage 3-family models support output_dimension — the library forwards EmbedRequest.Dimensions as that field. Valid values depend on the model:

  • voyage-3, voyage-3-large: 256, 512, 1024 (default), 2048.
  • voyage-3-lite: 512 (fixed).
  • voyage-code-3: 256, 512, 1024 (default), 2048.
  • voyage-multilingual-2: 1024 (fixed).

RAG example: embed documents, embed query, retrieve

package main
import (
"context"
"fmt"
"log"
"math"
"os"
"sort"
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/voyage"
)
func main() {
p, err := voyage.New(llmrouter.WithAPIKey(os.Getenv("VOYAGE_API_KEY")))
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
docs := []string{
"The mitochondrion is the powerhouse of the cell.",
"ATP synthase produces adenosine triphosphate.",
"Photosynthesis converts light energy into chemical energy.",
}
docEmb, err := p.Embed(ctx, llmrouter.EmbedRequest{
Model: "voyage-3",
Inputs: docs,
Dimensions: 1024,
TaskType: "RETRIEVAL_DOCUMENT",
})
if err != nil {
log.Fatal(err)
}
queryEmb, err := p.Embed(ctx, llmrouter.EmbedRequest{
Model: "voyage-3",
Inputs: []string{"What makes ATP?"},
TaskType: "RETRIEVAL_QUERY",
})
if err != nil {
log.Fatal(err)
}
type hit struct {
idx int
sim float32
}
var hits []hit
for i, dv := range docEmb.Embeddings {
hits = append(hits, hit{i, cosine(queryEmb.Embeddings[0], dv)})
}
sort.Slice(hits, func(i, j int) bool { return hits[i].sim > hits[j].sim })
for _, h := range hits {
fmt.Printf("%.3f %s\n", h.sim, docs[h.idx])
}
}
func cosine(a, b []float32) float32 {
var dot, na, nb float64
for i := range a {
dot += float64(a[i]) * float64(b[i])
na += float64(a[i]) * float64(a[i])
nb += float64(b[i]) * float64(b[i])
}
return float32(dot / (math.Sqrt(na) * math.Sqrt(nb)))
}

Pairing with Anthropic Claude

Anthropic does not offer a first-party embedding endpoint and explicitly recommends Voyage. The canonical pairing:

import (
"github.com/elloloop/llmrouter/providers/anthropic"
"github.com/elloloop/llmrouter/providers/voyage"
)
chat, _ := anthropic.New(llmrouter.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")))
embed, _ := voyage.New(llmrouter.WithAPIKey(os.Getenv("VOYAGE_API_KEY")))
// embed.Embed(...) for retrieval; chat.CompletionStream(...) for generation.

Truncation

Voyage truncates inputs that exceed the model's context window by default. To error instead of truncating, pass truncation=false via Raw:

raw := []byte(`{
"input": ["..."],
"input_type": "document",
"truncation": false
}`)
resp, err := p.Embed(ctx, llmrouter.EmbedRequest{
Model: "voyage-3",
Raw: raw,
})

Error handling

Non-2xx responses surface as *llmrouter.ErrUpstream with Provider == "voyage". Common cases:

  • 401 — bad or missing API key.
  • 400 — invalid output_dimension for the chosen model, or input over context window with truncation=false.
  • 429 — rate-limit (Voyage's rate limits are per-minute token throughput).

Caveats

  • Embeddings only. No chat, no TTS, no STT. Compose with a chat provider for full pipelines.
  • Batch size limit. Voyage caps requests at 128 inputs per call (1000 for voyage-3-lite). The provider does not auto-batch; chunk client-side.
  • Asymmetric query/document. Always set TaskType correctly — the difference is large (5-10% recall on standard benchmarks).

See also