embedder

package module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MPL-2.0 Imports: 1 Imported by: 0

README

go-embedder

A Go library for converting text into float32 embedding vectors using OpenAI-compatible APIs.

Features

  • Interface-based design — the embedder.Embedder interface lets you swap providers without changing consumer code
  • Automatic batching — large requests are split into sub-batches within API limits
  • Rate limiting — token-bucket limiter prevents overwhelming the API
  • Retry with backoff — exponential backoff with jitter on 429, 5xx, and transient transport errors
  • Per-text failure isolationEmbedBatchPartial returns per-text errors so partial failures don't discard successful results
  • Concurrency control — semaphore caps in-flight HTTP requests across all callers
  • Context cancellation — every blocking point respects context.Context

Installation

go get github.com/ieshan/go-embedder

Quick Start

Embed a single text string:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ieshan/go-embedder/openai"
)

func main() {
    client, err := openai.New(openai.Options{
        BaseURL: "https://api.openai.com/v1",
        APIKey:  "sk-...",
        Model:   "text-embedding-3-small",
    })
    if err != nil {
        log.Fatal(err)
    }

    vec, err := client.Embed(context.Background(), "Hello, world!")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("vector dimensions: %d\n", len(vec))
}

Batch Embedding

Embed multiple texts in a single call. The client automatically splits the input into sub-batches within MaxBatchSize and dispatches them concurrently:

texts := []string{
    "The quick brown fox",
    "jumps over the lazy dog",
    "Machine learning is fun",
}

vectors, err := client.EmbedBatch(context.Background(), texts)
if err != nil {
    log.Fatal(err)
}
for i, vec := range vectors {
    fmt.Printf("text[%d]: %d dimensions\n", i, len(vec))
}

EmbedBatch is all-or-nothing — if any sub-batch fails, the combined error is returned and no vectors are returned.

Partial Batch Embedding

When you want per-text failure isolation, use EmbedBatchPartial. It returns parallel vectors and errs slices where vectors[i] is nil iff errs[i] != nil. Successful texts are never discarded due to sibling failures:

vectors, errs := client.EmbedBatchPartial(context.Background(), texts)
for i := range texts {
    if errs[i] != nil {
        fmt.Printf("text[%d] failed: %v\n", i, errs[i])
        continue
    }
    fmt.Printf("text[%d]: %d dimensions\n", i, len(vectors[i]))
}
// Also check ctx.Err() to distinguish caller cancellation from per-sub-batch failure.
if ctx.Err() != nil {
    log.Printf("context was cancelled: %v", ctx.Err())
}

Using the Embedder Interface

The embedder.Embedder interface lets you write provider-agnostic code:

import "github.com/ieshan/go-embedder"

func ProcessTexts(ctx context.Context, e embedder.Embedder, texts []string) error {
    vectors, err := e.EmbedBatch(ctx, texts)
    if err != nil {
        return err
    }
    // use vectors...
    return nil
}

The openai.Client satisfies this interface:

var _ embedder.Embedder = (*openai.Client)(nil)

The interface also includes a Dim() int method for querying embedding dimensionality.

Fake Embedder

For testing, embedder.FakeEmbedder provides deterministic embeddings without external calls:

import "github.com/ieshan/go-embedder"

e := embedder.NewFakeEmbedder(1536)
vec, err := e.Embed(context.Background(), "hello")

It satisfies embedder.Embedder and is safe for concurrent use.

Configuration Reference

Field Type Default Description
BaseURL string (required) Base URL of the embedding API, e.g. https://api.openai.com/v1
APIKey string "" API key sent in the Authorization header
Model string (required) Embedding model name, e.g. text-embedding-3-small
Dimensions int 0 Embedding vector dimensions; 0 uses the model default
InputType string "" Provider-specific input type, e.g. document or query for Voyage AI
MaxBatchSize int 64 Maximum texts per API call
MaxRetries int 5 Maximum retry attempts on 429 and 5xx errors
RateLimit float64 0 Sustained requests per second; 0 disables rate limiting
RateBurst int 10 Maximum burst above the sustained rate (used only when RateLimit > 0)
RetryBaseDelay time.Duration 500ms Initial backoff delay; doubles each attempt, capped at 30s
HTTPClient *http.Client nil Custom HTTP client; nil creates a pooled client
HTTPTimeout time.Duration 30s HTTP timeout (applied only when HTTPClient is nil)
Concurrency int 8 Maximum concurrent sub-batch HTTP requests

Supported Providers

Any provider that follows the OpenAI /v1/embeddings API shape works out of the box:

Provider BaseURL Notes
OpenAI https://api.openai.com/v1 Default provider
Voyage AI https://api.voyageai.com/v1 Set InputType to document or query
Azure OpenAI https://<resource>.openai.azure.com/openai/deployments/<deployment> Append /embeddings?api-version=... to BaseURL
Ollama http://localhost:11434/v1 Local inference, no API key needed
LM Studio http://localhost:1234/v1 Local inference, no API key needed
Example: Voyage AI
client, err := openai.New(openai.Options{
    BaseURL:   "https://api.voyageai.com/v1",
    APIKey:    "va-...",
    Model:     "voyage-3",
    InputType: "document", // or "query"
})
Example: Ollama (local)
client, err := openai.New(openai.Options{
    BaseURL: "http://localhost:11434/v1",
    Model:   "nomic-embed-text",
    // APIKey omitted — not required for local providers
})

Error Handling

The client defines two sentinel errors for use with errors.Is:

  • openai.ErrRateLimited — API returned 429 and all retries were exhausted
  • openai.ErrAPIError — API returned a non-transient 4xx error (not 429)

For HTTP error details, use errors.As to extract *openai.APIError:

var apiErr *openai.APIError
if errors.As(err, &apiErr) {
    fmt.Printf("HTTP %d: %s\n", apiErr.StatusCode, apiErr.Body)
}
Retry Behavior

The client retries on:

  • HTTP 429 (Too Many Requests)
  • HTTP 5xx (Server Error)
  • Transient transport errors (EOF, connection reset, TLS bad_record_mac, http.Client.Timeout expiry)

It does not retry on:

  • HTTP 4xx (non-429) — returned immediately wrapped in ErrAPIError
  • Fatal transport errors (DNS resolution failure, TLS certificate verification failure)
  • Caller context cancellation or deadline

Rate Limiting

Set RateLimit to throttle requests to a sustained rate. RateBurst allows short bursts above that rate. A value of 0 disables rate limiting entirely:

client, err := openai.New(openai.Options{
    BaseURL:   "https://api.openai.com/v1",
    APIKey:    "sk-...",
    Model:     "text-embedding-3-small",
    RateLimit: 50,  // 50 requests/sec sustained
    RateBurst: 10,  // allow bursts of 10 above the sustained rate
})

Concurrency

Concurrency caps the number of in-flight HTTP requests across all EmbedBatch and EmbedBatchPartial calls on the same client. This prevents overwhelming the provider when multiple callers embed concurrently:

client, err := openai.New(openai.Options{
    BaseURL:      "https://api.openai.com/v1",
    APIKey:       "sk-...",
    Model:        "text-embedding-3-small",
    MaxBatchSize: 100, // 100 texts per API call
    Concurrency:  4,   // max 4 concurrent HTTP requests
})

Custom HTTP Client

Pass your own *http.Client for fine-grained control over transport, TLS, proxies, etc. When HTTPClient is set, HTTPTimeout is ignored:

client, err := openai.New(openai.Options{
    BaseURL:    "https://api.openai.com/v1",
    APIKey:     "sk-...",
    Model:      "text-embedding-3-small",
    HTTPClient: &http.Client{
        Timeout: 60 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns:        100,
            MaxIdleConnsPerHost: 10,
            TLSClientConfig:     &tls.Config{ /* ... */ },
        },
    },
})

Testing

make test
# or
go test ./...

Documentation

Overview

Package embedder defines the Embedder interface for converting text into float32 embedding vectors.

This package contains only the interface. Concrete implementations live in sub-packages (e.g. openai).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Embedder

type Embedder interface {
	Embed(ctx context.Context, text string) ([]float32, error)
	EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
	// EmbedBatchPartial embeds texts in sub-batches with per-text failure
	// isolation. Returned slices satisfy:
	//   - len(vectors) == len(errs) == len(texts)
	//   - vectors[i] is nil iff errs[i] != nil
	// Callers should also inspect ctx.Err() to distinguish caller
	// cancellation from per-sub-batch failure.
	EmbedBatchPartial(ctx context.Context, texts []string) ([][]float32, []error)

	// Dim returns the dimensionality of the embeddings.
	Dim() int
}

Embedder converts text into float32 embedding vectors.

type FakeEmbedder added in v0.0.2

type FakeEmbedder struct {
	Dimensions int
}

FakeEmbedder is a test embedder that returns deterministic embeddings based on the input text hash. It does not make any external calls.

func NewFakeEmbedder added in v0.0.2

func NewFakeEmbedder(dimensions int) *FakeEmbedder

NewFakeEmbedder creates a FakeEmbedder with the given dimensions.

func (*FakeEmbedder) Dim added in v0.0.2

func (f *FakeEmbedder) Dim() int

func (*FakeEmbedder) Embed added in v0.0.2

func (f *FakeEmbedder) Embed(_ context.Context, text string) ([]float32, error)

func (*FakeEmbedder) EmbedBatch added in v0.0.2

func (f *FakeEmbedder) EmbedBatch(_ context.Context, texts []string) ([][]float32, error)

func (*FakeEmbedder) EmbedBatchPartial added in v0.0.2

func (f *FakeEmbedder) EmbedBatchPartial(_ context.Context, texts []string) ([][]float32, []error)

Directories

Path Synopsis
Package openai provides an OpenAI-compatible embedding API client that implements [embedder.Embedder].
Package openai provides an OpenAI-compatible embedding API client that implements [embedder.Embedder].

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL