embed

package
v1.2.3 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 20 Imported by: 0

README

Embedding Package

Overview

This package provides embedding generation clients for NornicDB's vector search capabilities.

Production Status

✅ PRODUCTION READY

File: embed.go
Coverage: 90%+
Status: Fully tested and production ready

Features
  • Ollama client (local open-source models)
  • OpenAI client (cloud API)
  • Provider abstraction via Embedder interface
  • Comprehensive error handling
  • Context cancellation support
  • Batch processing
Usage Example
import "github.com/orneryd/nornicdb/pkg/embed"

// Use local Ollama (recommended)
config := embed.DefaultOllamaConfig()
embedder := embed.NewOllama(config)

embedding, err := embedder.Embed(ctx, "graph database")
if err != nil {
    log.Fatal(err)
}

// Or use OpenAI
apiKey := os.Getenv("OPENAI_API_KEY")
config := embed.DefaultOpenAIConfig(apiKey)
embedder := embed.NewOpenAI(config)

// Batch processing
texts := []string{"memory", "storage", "database"}
embeddings, err := embedder.EmbedBatch(ctx, texts)

See: AUTO_EMBED_STATUS.md for details.

Supported Providers

Ollama (Local)

Models:

  • mxbai-embed-large (1024 dimensions) - Recommended
  • nomic-embed-text (768 dimensions)
  • all-minilm (384 dimensions)

Setup:

$ ollama pull mxbai-embed-large
$ ollama serve

Configuration:

config := embed.DefaultOllamaConfig()
// Default: http://localhost:11434, mxbai-embed-large, 1024 dims
embedder := embed.NewOllama(config)
OpenAI (Cloud API)

Models:

  • text-embedding-3-small (1536 dimensions) - $0.02/1M tokens
  • text-embedding-3-large (3072 dimensions) - $0.13/1M tokens

Setup:

export OPENAI_API_KEY=sk-...

Configuration:

apiKey := os.Getenv("OPENAI_API_KEY")
config := embed.DefaultOpenAIConfig(apiKey)
embedder := embed.NewOpenAI(config)

Interface

All embedders implement the Embedder interface:

type Embedder interface {
    Embed(ctx context.Context, text string) ([]float32, error)
    EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
    Dimensions() int
    Model() string
}

Testing

# Run tests for production-ready features
cd nornicdb && go test ./pkg/embed/... -cover

# Expected coverage: ~90% (only embed.go is tested)

Configuration Examples

Custom Ollama Server
config := &embed.Config{
    Provider:   "ollama",
    APIURL:     "http://192.168.1.100:11434",
    Model:      "nomic-embed-text",
    Dimensions: 768,
    Timeout:    60 * time.Second,
}
embedder := embed.NewOllama(config)
Dynamic Provider Selection
provider := os.Getenv("EMBEDDING_PROVIDER") // "ollama" or "openai"

var config *embed.Config
if provider == "openai" {
    apiKey := os.Getenv("OPENAI_API_KEY")
    config = embed.DefaultOpenAIConfig(apiKey)
} else {
    config = embed.DefaultOllamaConfig()
}

embedder, err := embed.NewEmbedder(config)

Performance Considerations

Ollama (Local)
  • Latency: 10-50ms per embedding (CPU), 5-15ms (GPU)
  • Throughput: 20-100 embeddings/sec depending on hardware
  • Cost: Free (runs locally)
  • Privacy: Complete (no data leaves your machine)
OpenAI (Cloud)
  • Latency: 100-300ms per request (network dependent)
  • Throughput: Rate limited by API tier
  • Cost: $0.02-$0.13 per 1M tokens
  • Privacy: Data sent to OpenAI servers
Batch Processing

Always use EmbedBatch() for multiple texts:

// ❌ Inefficient (many API calls)
for _, text := range texts {
    emb, _ := embedder.Embed(ctx, text)
}

// ✅ Efficient (single API call for OpenAI, batched for Ollama)
embs, _ := embedder.EmbedBatch(ctx, texts)

Error Handling

Common errors and how to handle them:

embedding, err := embedder.Embed(ctx, text)
if err != nil {
    switch {
    case errors.Is(err, context.Canceled):
        // Context was cancelled
        return nil
    case errors.Is(err, context.DeadlineExceeded):
        // Request timed out
        return nil
    case strings.Contains(err.Error(), "401"):
        // Invalid API key (OpenAI)
        log.Fatal("Invalid API key")
    case strings.Contains(err.Error(), "429"):
        // Rate limited (OpenAI)
        time.Sleep(time.Second)
        // Retry...
    default:
        // Other error
        log.Printf("Embedding failed: %v", err)
    }
}

See Also


Production Status: ✅ Core features ready (embed.go)
Test Coverage: 90%+ (production features only)
Last Updated: 2025-11-26

Documentation

Overview

Package embed — build-tag matrix for the local GGUF backend label (Plan 04-05 D-06 / D-06a).

`localGGUFBackend` is the value LocalGGUFEmbedder.Backend() returns. The matrix mirrors Plan 04-01's pkg/observability/build_*.go pattern but lives inside pkg/embed so the leaf-package boundary stays intact — pkg/observability never imports pkg/embed (D-01a / RESEARCH §1).

Default file: amd64-cpu Docker variant + plain `go build` (RISK-5 prevention from Plan 04-01: avoid undeclared-var failure).

Package embed provides embedding generation with caching support.

CachedEmbedder wraps any Embedder with an LRU cache to avoid redundant embedding computations. This provides significant performance improvements for repeated queries without any changes to existing code.

Example:

// Wrap any embedder with caching
base := embed.NewOllama(nil)
cached := embed.NewCachedEmbedder(base, 10000) // Cache 10K embeddings

// Use exactly like the original - caching is transparent
vec, err := cached.Embed(ctx, "hello world")
vec2, err := cached.Embed(ctx, "hello world") // Cache hit!

Performance:

  • Cache hit: ~1µs (vs 50-200ms for actual embedding)
  • Memory: ~4KB per cached embedding (1024 dims × 4 bytes)
  • 10K cache = ~40MB memory

Package embed provides embedding generation clients for vector search.

This package supports multiple embedding providers:

  • Ollama: Local open-source models (mxbai-embed-large, nomic-embed-text)
  • OpenAI: Cloud API (text-embedding-3-small, text-embedding-3-large)

Embeddings convert text into high-dimensional vectors that capture semantic meaning. Similar texts have similar vectors, enabling semantic search.

Example Usage:

// Use local Ollama
config := embed.DefaultOllamaConfig()
embedder := embed.NewOllama(config)

embedding, err := embedder.Embed(ctx, "graph database")
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Embedding dimensions: %d\n", len(embedding))
// Output: 1024 (for mxbai-embed-large)

// Or use OpenAI
config := embed.DefaultOpenAIConfig("sk-...")
embedder := embed.NewOpenAI(config)

// Batch processing for efficiency
texts := []string{"memory", "storage", "database"}
embeddings, err := embedder.EmbedBatch(ctx, texts)

ELI12 (Explain Like I'm 12):

Embeddings are like a "smell" or "vibe" for text. Similar things have similar smells. "Cat" and "kitten" smell similar. "Cat" and "car" smell different.

The computer represents each text as a list of 1024 numbers (a vector). When you search, it finds texts with similar number patterns (similar vibes).

Think of it like this:

  • "Happy" might be [0.8, 0.2, 0.1, ...] (lots of positive vibes)
  • "Joyful" might be [0.7, 0.3, 0.1, ...] (similar vibes!)
  • "Sad" might be [0.1, 0.1, 0.9, ...] (very different vibes)

The search system measures how close these number lists are to find similar meanings!

Package embed — D-09 FFI recovery wrapper.

The recoverFFI function is the deferred-recover idiom for every CGo / purego call site that drives llama.cpp. A panic from inside C-land (segfault wrapped by Go runtime, Metal/CUDA driver fault, etc.) is recovered, counted as nornicdb_embed_ffi_panics_total{mode=<backend>}, and converted into a Go error. The server stays up — Phase 4 hardens availability per CONTEXT T-04-08.

Per AGENTS.md §8 (Separation of Concerns) the FFI semantics live in pkg/embed; pkg/observability remains the leaf-package authority for metric label-set discipline. The counter handle is injected via the EmbedMetrics bag (Plan 04-05-02) so this file only depends on the bag shape, not on prometheus internals.

Usage:

func (e *LocalGGUFEmbedder) Embed(ctx context.Context, text string) (vec []float32, err error) {
    defer recoverFFI(e.metrics, e.Backend(), &err)
    // ... C / purego call that may panic
}

The closed `mode` enum (gpu, cpu, cuda, metal, vulkan) is enforced by the call site itself — passing the embedder's Backend() return value guarantees a value in observability.AllowedEmbedBackends. Adding a new backend = update both enums (pkg/embed Backend() implementer + observability.AllowedEmbedBackends) plus an ADR §2.3 amendment.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CacheStats

type CacheStats struct {
	Size    int     `json:"size"`     // Current number of cached embeddings
	MaxSize int     `json:"max_size"` // Maximum cache capacity
	Hits    uint64  `json:"hits"`     // Number of cache hits
	Misses  uint64  `json:"misses"`   // Number of cache misses
	HitRate float64 `json:"hit_rate"` // Hit rate percentage (0-100)
}

CacheStats holds cache performance statistics.

type CachedEmbedder

type CachedEmbedder struct {
	// contains filtered or unexported fields
}

CachedEmbedder wraps an Embedder with LRU caching.

The cache is keyed by FNV-1a hash of the input text, providing:

  • Exact match caching (same text = same embedding)
  • Efficient lookup (O(1) for cache hits)
  • Bounded memory usage (LRU eviction)
  • Fast hashing (FNV-1a is non-cryptographic but fast)

Thread-safe: All methods can be called from multiple goroutines.

func NewCachedEmbedder

func NewCachedEmbedder(base Embedder, maxSize int) *CachedEmbedder

NewCachedEmbedder wraps an existing embedder with LRU caching.

Parameters:

  • base: The underlying embedder to wrap
  • maxSize: Maximum number of embeddings to cache (0 = 10000 default)

Example:

// Wrap Ollama with 10K cache
ollama := embed.NewOllama(nil)
cached := embed.NewCachedEmbedder(ollama, 10000)

// Or use default cache size
cached = embed.NewCachedEmbedder(ollama, 0)

func (*CachedEmbedder) Backend added in v1.1.0

func (c *CachedEmbedder) Backend() string

Backend delegates to the wrapped embedder per Plan 04-05 D-06. The wrapping layer never changes the execution backend; transparently forwards so the observability `mode` label reflects the underlying backend (e.g., "metal" for a cached LocalGGUF embedder running on Metal).

func (*CachedEmbedder) ChunkText

func (c *CachedEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)

ChunkText delegates chunking to the wrapped embedder.

func (*CachedEmbedder) Clear

func (c *CachedEmbedder) Clear()

Clear removes all cached embeddings.

func (*CachedEmbedder) Dimensions

func (c *CachedEmbedder) Dimensions() int

Dimensions returns the embedding vector dimension.

func (*CachedEmbedder) Embed

func (c *CachedEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

Embed generates or retrieves a cached embedding for the text.

On cache hit, returns immediately without calling the underlying embedder. On cache miss, calls the base embedder and caches the result.

func (*CachedEmbedder) EmbedBatch

func (c *CachedEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

EmbedBatch generates embeddings for multiple texts with caching.

Each text is checked against the cache individually. Only cache misses are sent to the underlying embedder.

func (*CachedEmbedder) Model

func (c *CachedEmbedder) Model() string

Model returns the model name.

func (*CachedEmbedder) Stats

func (c *CachedEmbedder) Stats() CacheStats

Stats returns cache statistics.

type Config

type Config struct {
	Provider   string        // ollama, openai, local
	APIURL     string        // e.g., http://localhost:11434
	APIPath    string        // e.g., /api/embeddings or /v1/embeddings
	APIKey     string        // For OpenAI
	Model      string        // e.g., mxbai-embed-large
	Dimensions int           // Expected dimensions (for validation)
	Timeout    time.Duration // Request timeout

	// Local GGUF model settings (used when Provider="local")
	ModelsDir      string        // Directory containing .gguf models (default: ./models)
	GPULayers      int           // GPU offload layers: -1=auto, 0=CPU only
	WarmupInterval time.Duration // Model warmup interval (0=disabled)

	// Llama.cpp context features (passthrough, env-configurable per model domain)
	CtxType       int // Context type: 0=default, 1=MTP
	PoolingType   int // Pooling: 1=mean, 2=cls, 3=last, 4=rank
	AttentionType int // Attention: 0=causal, 1=non-causal
	FlashAttn     int // Flash attn: -1=auto, 0=off, 1=on (embedding default: off)
}

Config holds embedding provider configuration.

Fields:

  • Provider: "ollama" or "openai" or "local"
  • APIURL: Base URL for API (e.g., http://localhost:11434)
  • APIPath: Endpoint path (e.g., /api/embeddings)
  • APIKey: Authentication key (OpenAI only)
  • Model: Model name (e.g., mxbai-embed-large)
  • Dimensions: Expected vector size for validation
  • Timeout: HTTP request timeout

Example:

// Custom Ollama config
config := &embed.Config{
	Provider:   "ollama",
	APIURL:     "http://192.168.1.100:11434",
	Model:      "nomic-embed-text",
	Dimensions: 768,
	Timeout:    60 * time.Second,
}

func DefaultOllamaConfig

func DefaultOllamaConfig() *Config

DefaultOllamaConfig returns configuration for local Ollama with mxbai-embed-large.

Default settings:

  • Provider: ollama
  • API URL: http://localhost:11434
  • Model: mxbai-embed-large (1024 dimensions)
  • Timeout: 30 seconds

This assumes Ollama is running locally. To start Ollama:

$ ollama pull mxbai-embed-large
$ ollama serve

Example:

config := embed.DefaultOllamaConfig()
embedder := embed.NewOllama(config)

embedding, err := embedder.Embed(ctx, "test")
if err != nil {
	log.Fatal(err)
}

func DefaultOpenAIConfig

func DefaultOpenAIConfig(apiKey string) *Config

DefaultOpenAIConfig returns configuration for OpenAI's text-embedding-3-small.

Default settings:

  • Provider: openai
  • API URL: https://api.openai.com
  • Model: text-embedding-3-small (1536 dimensions)
  • Timeout: 30 seconds

Requires an OpenAI API key. Get one at: https://platform.openai.com/api-keys

Example:

apiKey := os.Getenv("OPENAI_API_KEY")
config := embed.DefaultOpenAIConfig(apiKey)
embedder := embed.NewOpenAI(config)

embedding, err := embedder.Embed(ctx, "test")
if err != nil {
	log.Fatal(err)
}

Cost:

  • text-embedding-3-small: $0.02 per 1M tokens (~750k words)
  • text-embedding-3-large: $0.13 per 1M tokens (3072 dimensions)

type Embedder

type Embedder interface {
	// Embed generates embedding for single text
	Embed(ctx context.Context, text string) ([]float32, error)

	// EmbedBatch generates embeddings for multiple texts
	EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

	// ChunkText splits text into provider-appropriate chunks for embedding.
	ChunkText(text string, maxTokens, overlap int) ([]string, error)

	// Dimensions returns the embedding vector dimension
	Dimensions() int

	// Model returns the model name
	Model() string

	// Backend returns the runtime execution backend as a value in the
	// closed enum {gpu, cpu, cuda, metal, vulkan} per Plan 04-05 D-06 /
	// D-06a. The observability layer (pkg/observability EmbedMetrics) reads
	// this once at constructor time to bind the `mode` label on
	// processed_total / duration_seconds / ffi_panics_total. HTTP-fronted
	// embedders (Ollama, OpenAI) report "cpu" because the local process
	// only marshals JSON and waits — RESEARCH §Q9 A1. Local GGUF reports
	// the build-tag-derived backend (metal / cuda / vulkan / cpu) so a
	// dynamic CUDA→CPU fallback is captured truthfully. CachedEmbedder
	// delegates to the wrapped embedder.
	//
	// Adding a new backend = update {gpu,cpu,cuda,metal,vulkan} closed
	// enum in pkg/observability/catalog_embed.go AND amend ADR §2.3.
	Backend() string
}

Embedder generates vector embeddings from text.

Implementations must be safe for concurrent use from multiple goroutines.

Example:

var embedder embed.Embedder
embedder = embed.NewOllama(nil) // Uses defaults

// Single embedding
vec, err := embedder.Embed(ctx, "hello world")

// Batch for efficiency
vecs, err := embedder.EmbedBatch(ctx, []string{"one", "two", "three"})

func NewEmbedder

func NewEmbedder(config *Config) (Embedder, error)

NewEmbedder creates an embedder based on the provider specified in config.

Supported providers:

  • "local": Local GGUF models via llama.cpp (GPU-accelerated)
  • "ollama": External Ollama server
  • "openai": OpenAI cloud API

This is a convenience function for dynamic provider selection.

Example:

// Dynamic provider selection
provider := os.Getenv("EMBEDDING_PROVIDER") // "local", "ollama", or "openai"

var config *embed.Config
switch provider {
case "local":
	config = &embed.Config{
		Provider:   "local",
		Model:      "bge-m3",
		Dimensions: 1024,
	}
case "openai":
	apiKey := os.Getenv("OPENAI_API_KEY")
	config = embed.DefaultOpenAIConfig(apiKey)
default:
	config = embed.DefaultOllamaConfig()
}

embedder, err := embed.NewEmbedder(config)
if err != nil {
	log.Fatal(err)
}

// Use embedder regardless of provider
vec, err := embedder.Embed(ctx, "test")

Returns an Embedder interface, or an error if the provider is unknown or configuration is invalid (e.g., OpenAI without API key, local without model).

type EmbedderStats

type EmbedderStats struct {
	EmbedCount    int64     `json:"embed_count"`
	ErrorCount    int64     `json:"error_count"`
	PanicCount    int64     `json:"panic_count"`
	LastEmbedTime time.Time `json:"last_embed_time"`
	ModelName     string    `json:"model_name"`
	ModelPath     string    `json:"model_path"`
}

EmbedderStats holds embedding statistics (stub).

type LocalGGUFEmbedder

type LocalGGUFEmbedder struct{}

LocalGGUFEmbedder is a stub for when localllm build tag is not set. Build with -tags=localllm to enable local GGUF embedding support.

func NewLocalGGUF

func NewLocalGGUF(config *Config) (*LocalGGUFEmbedder, error)

NewLocalGGUF returns an error when localllm is not built in. To enable local GGUF embedding support:

  1. Build llama.cpp: ./scripts/build-llama.sh
  2. Build with tag: go build -tags=localllm ./cmd/nornicdb

func (*LocalGGUFEmbedder) AttachMetrics added in v1.1.0

func (e *LocalGGUFEmbedder) AttachMetrics(m *observability.EmbedMetrics)

AttachMetrics is a no-op (stub). Plan 04-05-03 D-09 — symmetric with the production type so cmd/nornicdb wiring can call AttachMetrics without build-tag branching.

func (*LocalGGUFEmbedder) Backend added in v1.1.0

func (e *LocalGGUFEmbedder) Backend() string

Backend returns the build-tag-derived backend label per Plan 04-05 D-06. On the !localllm path the value is whichever build_tag matrix file (backend_default.go / backend_metal.go / backend_cuda.go / backend_vulkan.go) was selected. The stub still returns a real value so the closed enum {gpu,cpu,cuda,metal,vulkan} holds for embedder probes even in nolocalllm builds.

func (*LocalGGUFEmbedder) ChunkText

func (e *LocalGGUFEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)

ChunkText returns an error (stub).

func (*LocalGGUFEmbedder) Close

func (e *LocalGGUFEmbedder) Close() error

Close is a no-op (stub).

func (*LocalGGUFEmbedder) Dimensions

func (e *LocalGGUFEmbedder) Dimensions() int

Dimensions returns 0 (stub).

func (*LocalGGUFEmbedder) Embed

func (e *LocalGGUFEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

Embed returns an error (stub).

func (*LocalGGUFEmbedder) EmbedBatch

func (e *LocalGGUFEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

EmbedBatch returns an error (stub).

func (*LocalGGUFEmbedder) Model

func (e *LocalGGUFEmbedder) Model() string

Model returns empty string (stub).

func (*LocalGGUFEmbedder) Stats

func (e *LocalGGUFEmbedder) Stats() EmbedderStats

Stats returns empty stats (stub).

type OllamaEmbedder

type OllamaEmbedder struct {
	// contains filtered or unexported fields
}

OllamaEmbedder implements Embedder for local Ollama models.

Ollama runs open-source embedding models locally:

  • mxbai-embed-large: 1024 dimensions, excellent quality
  • nomic-embed-text: 768 dimensions, faster
  • all-minilm: 384 dimensions, very fast but lower quality

Install models:

$ ollama pull mxbai-embed-large
$ ollama pull nomic-embed-text

Thread-safe: Can be used concurrently from multiple goroutines.

Example:

embedder := embed.NewOllama(nil) // Uses defaults

vec, err := embedder.Embed(ctx, "hello world")
if err != nil {
	return err
}

fmt.Printf("Model: %s, Dimensions: %d\n",
	embedder.Model(), embedder.Dimensions())

func NewOllama

func NewOllama(config *Config) *OllamaEmbedder

NewOllama creates a new Ollama embedder.

If config is nil, DefaultOllamaConfig() is used.

Example:

// Use defaults (localhost:11434, mxbai-embed-large)
embedder := embed.NewOllama(nil)

// Custom config
config := &embed.Config{
	Provider:   "ollama",
	APIURL:     "http://192.168.1.100:11434",
	Model:      "nomic-embed-text",
	Dimensions: 768,
}
embedder = embed.NewOllama(config)

Returns an embedder ready to generate embeddings.

Example 1 - Default Configuration (mxbai-embed-large):

// Uses localhost:11434 by default
embedder := embed.NewOllama(nil)

vec, err := embedder.Embed(ctx, "Hello world")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Generated %d-dimensional embedding\n", len(vec))
// Output: Generated 1024-dimensional embedding

Example 2 - Custom Model (nomic-embed-text):

config := embed.DefaultOllamaConfig()
config.Model = "nomic-embed-text"
config.Dimensions = 768

embedder := embed.NewOllama(config)

// Good for English text
vec, _ := embedder.Embed(ctx, "The quick brown fox")
fmt.Printf("Nomic embedding: %d dims\n", len(vec)) // 768

Example 3 - Remote Ollama Server:

config := embed.DefaultOllamaConfig()
config.APIURL = "http://ollama-server.internal:11434"
config.Timeout = 60 * time.Second

embedder := embed.NewOllama(config)

// Connect to remote Ollama instance
vec, err := embedder.Embed(ctx, "distributed embeddings")
if err != nil {
	log.Printf("Remote Ollama error: %v", err)
}

Example 4 - Batch Processing for Efficiency:

embedder := embed.NewOllama(nil)

documents := []string{
	"Document 1 about AI",
	"Document 2 about ML",
	"Document 3 about NLP",
}

// Process in batch
embeddings, err := embedder.EmbedBatch(ctx, documents)
if err != nil {
	log.Fatal(err)
}

// Store embeddings in database
for i, emb := range embeddings {
	storeEmbedding(documents[i], emb)
}

ELI12:

NewOllama is like hiring a translator who works at a local translation office:

  • Ollama is the office (running on your computer or server)
  • You give them text: "Hello world"
  • They give back a list of 1024 numbers (the "meaning" as numbers)
  • These numbers help computers understand if texts are similar

Why use Ollama?

  • FREE (no API costs like OpenAI)
  • PRIVATE (data stays on your server)
  • FAST (no internet latency)
  • OFFLINE (works without internet)

How it works:

  1. Install Ollama: `ollama run mxbai-embed-large`
  2. Ollama runs on localhost:11434
  3. Send text, get back 1024 numbers
  4. Use numbers to find similar text

Models Available:

  • mxbai-embed-large: 1024 dims, best quality (default)
  • nomic-embed-text: 768 dims, good balance
  • all-minilm: 384 dims, fast & small

Use Cases:

  • Document similarity search
  • Semantic search engines
  • Recommendation systems
  • Duplicate detection
  • Content clustering

Performance:

  • ~50-200ms per embedding (depends on model)
  • Batch processing is more efficient
  • GPU acceleration if available
  • Memory: ~500MB-2GB for model

Thread Safety:

Safe to call from multiple goroutines.

func (*OllamaEmbedder) Backend added in v1.1.0

func (e *OllamaEmbedder) Backend() string

Backend returns "cpu" for Ollama. Ollama runs the model in a separate process (or remote host) and the local process only marshals JSON over HTTP — from the NornicDB process perspective, the work is offloaded and the local execution path is CPU-only HTTP I/O. Per RESEARCH §Q9 A1 / CONTEXT D-06.

func (*OllamaEmbedder) ChunkText

func (e *OllamaEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)

func (*OllamaEmbedder) Dimensions

func (e *OllamaEmbedder) Dimensions() int

Dimensions returns the expected embedding dimensions.

func (*OllamaEmbedder) Embed

func (e *OllamaEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

Embed generates a vector embedding for a single text string.

The embedding is a float32 slice of length specified by Dimensions(). Empty or very short text may produce low-quality embeddings.

Example:

embedder := embed.NewOllama(nil)

vec, err := embedder.Embed(ctx, "machine learning")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Generated %d-dimensional vector\n", len(vec))
// Output: Generated 1024-dimensional vector

Returns the embedding vector, or an error if the API request fails.

func (*OllamaEmbedder) EmbedBatch

func (e *OllamaEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

EmbedBatch generates embeddings for multiple texts efficiently.

For Ollama, this currently makes one request per text. Future versions may support true batch processing for better performance.

Example:

embedder := embed.NewOllama(nil)

texts := []string{
	"graph database",
	"vector search",
	"semantic similarity",
}

vecs, err := embedder.EmbedBatch(ctx, texts)
if err != nil {
	log.Fatal(err)
}

for i, vec := range vecs {
	fmt.Printf("Text %d: %d dimensions\n", i, len(vec))
}

Returns a slice of embeddings (one per input text), or an error if any request fails.

func (*OllamaEmbedder) Model

func (e *OllamaEmbedder) Model() string

Model returns the model name.

type OpenAIEmbedder

type OpenAIEmbedder struct {
	// contains filtered or unexported fields
}

OpenAIEmbedder implements Embedder for OpenAI's embedding API.

Supported models:

  • text-embedding-3-small: 1536 dimensions, $0.02/1M tokens
  • text-embedding-3-large: 3072 dimensions, $0.13/1M tokens
  • text-embedding-ada-002: 1536 dimensions (legacy)

Thread-safe: Can be used concurrently from multiple goroutines.

Example:

apiKey := os.Getenv("OPENAI_API_KEY")
embedder := embed.NewOpenAI(embed.DefaultOpenAIConfig(apiKey))

vec, err := embedder.Embed(ctx, "hello world")
if err != nil {
	return err
}

func NewOpenAI

func NewOpenAI(config *Config) *OpenAIEmbedder

NewOpenAI creates a new OpenAI embedder.

If config is nil, DefaultOpenAIConfig("") is used (will fail without API key).

Example:

apiKey := os.Getenv("OPENAI_API_KEY")
config := embed.DefaultOpenAIConfig(apiKey)
embedder := embed.NewOpenAI(config)

// Or use custom model
config.Model = "text-embedding-3-large"
config.Dimensions = 3072
embedder = embed.NewOpenAI(config)

Returns an embedder ready to generate embeddings.

Example 1 - Basic Setup with API Key:

apiKey := os.Getenv("OPENAI_API_KEY") // sk-...
embedder := embed.NewOpenAI(embed.DefaultOpenAIConfig(apiKey))

vec, err := embedder.Embed(ctx, "artificial intelligence")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Generated %d-dimensional embedding\n", len(vec))
// Output: Generated 1536-dimensional embedding

Example 2 - High-Dimensional Model (text-embedding-3-large):

config := embed.DefaultOpenAIConfig(apiKey)
config.Model = "text-embedding-3-large"
config.Dimensions = 3072 // Maximum quality

embedder := embed.NewOpenAI(config)

// Higher quality embeddings for critical applications
vec, _ := embedder.Embed(ctx, "complex semantic meaning")
fmt.Printf("High-quality: %d dims\n", len(vec)) // 3072

Example 3 - Cost-Optimized (text-embedding-3-small):

config := embed.DefaultOpenAIConfig(apiKey)
config.Model = "text-embedding-3-small"
config.Dimensions = 1536

embedder := embed.NewOpenAI(config)

// 5x cheaper than text-embedding-3-large
// $0.02 per 1M tokens vs $0.13 per 1M tokens
vec, _ := embedder.Embed(ctx, "cost effective")

Example 4 - Production with Error Handling:

config := embed.DefaultOpenAIConfig(apiKey)
config.Timeout = 30 * time.Second

embedder := embed.NewOpenAI(config)

texts := []string{"doc1", "doc2", "doc3"}
embeddings, err := embedder.EmbedBatch(ctx, texts)
if err != nil {
	// Handle rate limits, quota errors, etc.
	if strings.Contains(err.Error(), "rate_limit") {
		time.Sleep(1 * time.Second)
		embeddings, err = embedder.EmbedBatch(ctx, texts) // Retry
	}
}

Example 5 - Multilingual with Azure OpenAI:

config := &embed.Config{
	Provider:   "openai",
	APIURL:     "https://your-resource.openai.azure.com",
	APIPath:    "/openai/deployments/embedding/embeddings?api-version=2023-05-15",
	APIKey:     azureAPIKey,
	Model:      "text-embedding-ada-002",
	Dimensions: 1536,
}

embedder := embed.NewOpenAI(config)

// Works with multiple languages
embeddings, _ := embedder.EmbedBatch(ctx, []string{
	"Hello world",           // English
	"Bonjour le monde",     // French
	"Hola mundo",           // Spanish
	"こんにちは世界",          // Japanese
})

ELI12:

NewOpenAI is like hiring a professional translator from a famous company:

  • OpenAI is like Google Translate (but specifically for AI)
  • You send text to their servers
  • They send back numbers that represent the "meaning"
  • You pay a tiny amount per text ($0.02 per 1 million words!)

Why use OpenAI instead of Ollama?

  • QUALITY: Often more accurate
  • CONVENIENCE: No setup required
  • LANGUAGES: Supports 100+ languages well
  • UPDATES: Always latest models

BUT:

  • COST: You pay for each request (though it's cheap)
  • PRIVACY: Your text goes to OpenAI servers
  • INTERNET: Needs internet connection
  • RATE LIMITS: Max requests per minute

Models & Pricing (2024):

text-embedding-3-small:
- 1536 dimensions
- $0.02 per 1M tokens (~750k words)
- Best for: Cost-sensitive applications

text-embedding-3-large:
- 3072 dimensions (can truncate to 256-3072)
- $0.13 per 1M tokens
- Best for: Maximum quality

text-embedding-ada-002 (legacy):
- 1536 dimensions
- $0.10 per 1M tokens
- Still works but use v3 instead

Rate Limits:

  • Free tier: 3 RPM (requests per minute)
  • Paid tier 1: 3,000 RPM
  • Paid tier 2+: 5,000+ RPM

Use Cases:

  • Same as Ollama: similarity search, recommendations, etc.
  • Multilingual applications
  • When you need the absolute best quality
  • When you don't want to run your own infrastructure

Performance:

  • ~100-300ms per request (network latency)
  • Batch up to 2048 texts per request
  • Use batch processing to reduce costs

Thread Safety:

Safe to call from multiple goroutines.

func (*OpenAIEmbedder) Backend added in v1.1.0

func (e *OpenAIEmbedder) Backend() string

Backend returns "cpu" for OpenAI. The local process performs HTTP I/O only; model execution happens in OpenAI's cloud, which is opaque to the NornicDB process. Per CONTEXT D-06 the `mode` label captures *local* execution backend, not remote service backend.

func (*OpenAIEmbedder) ChunkText

func (e *OpenAIEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)

func (*OpenAIEmbedder) Dimensions

func (e *OpenAIEmbedder) Dimensions() int

Dimensions returns the expected embedding dimensions.

func (*OpenAIEmbedder) Embed

func (e *OpenAIEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

Embed generates a vector embedding for a single text string.

Internally calls EmbedBatch with a single-element slice for API consistency.

Example:

apiKey := os.Getenv("OPENAI_API_KEY")
embedder := embed.NewOpenAI(embed.DefaultOpenAIConfig(apiKey))

vec, err := embedder.Embed(ctx, "artificial intelligence")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Generated %d-dimensional OpenAI embedding\n", len(vec))
// Output: Generated 1536-dimensional OpenAI embedding

Returns the embedding vector, or an error if the API request fails.

func (*OpenAIEmbedder) EmbedBatch

func (e *OpenAIEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

EmbedBatch generates embeddings for multiple texts in a single API call.

OpenAI's API supports true batch processing, making this more efficient than calling Embed() multiple times.

Maximum batch size: 2048 texts per request.

Example:

apiKey := os.Getenv("OPENAI_API_KEY")
embedder := embed.NewOpenAI(embed.DefaultOpenAIConfig(apiKey))

texts := []string{
	"First document about machine learning",
	"Second document about neural networks",
	"Third document about deep learning",
}

vecs, err := embedder.EmbedBatch(ctx, texts)
if err != nil {
	log.Fatal(err)
}

for i, vec := range vecs {
	fmt.Printf("Document %d: %d dimensions\n", i+1, len(vec))
}

Returns a slice of embeddings (one per input text), or an error if the API request fails.

func (*OpenAIEmbedder) Model

func (e *OpenAIEmbedder) Model() string

Model returns the model name.

type TracedEmbedder added in v1.1.0

type TracedEmbedder struct {
	// contains filtered or unexported fields
}

TracedEmbedder wraps an Embedder with nornicdb.embed.* spans (TRC-18).

func NewTracedEmbedder added in v1.1.0

func NewTracedEmbedder(inner Embedder) *TracedEmbedder

NewTracedEmbedder wraps inner with OTel span instrumentation.

func (*TracedEmbedder) Backend added in v1.1.0

func (t *TracedEmbedder) Backend() string

func (*TracedEmbedder) ChunkText added in v1.1.0

func (t *TracedEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)

func (*TracedEmbedder) Dimensions added in v1.1.0

func (t *TracedEmbedder) Dimensions() int

func (*TracedEmbedder) Embed added in v1.1.0

func (t *TracedEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

func (*TracedEmbedder) EmbedBatch added in v1.1.0

func (t *TracedEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)

func (*TracedEmbedder) Model added in v1.1.0

func (t *TracedEmbedder) Model() string

Jump to

Keyboard shortcuts

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