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 ¶
- type CacheStats
- type CachedEmbedder
- func (c *CachedEmbedder) Backend() string
- func (c *CachedEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)
- func (c *CachedEmbedder) Clear()
- func (c *CachedEmbedder) Dimensions() int
- func (c *CachedEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (c *CachedEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (c *CachedEmbedder) Model() string
- func (c *CachedEmbedder) Stats() CacheStats
- type Config
- type Embedder
- type EmbedderStats
- type LocalGGUFEmbedder
- func (e *LocalGGUFEmbedder) AttachMetrics(m *observability.EmbedMetrics)
- func (e *LocalGGUFEmbedder) Backend() string
- func (e *LocalGGUFEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)
- func (e *LocalGGUFEmbedder) Close() error
- func (e *LocalGGUFEmbedder) Dimensions() int
- func (e *LocalGGUFEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (e *LocalGGUFEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (e *LocalGGUFEmbedder) Model() string
- func (e *LocalGGUFEmbedder) Stats() EmbedderStats
- type OllamaEmbedder
- func (e *OllamaEmbedder) Backend() string
- func (e *OllamaEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)
- func (e *OllamaEmbedder) Dimensions() int
- func (e *OllamaEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (e *OllamaEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (e *OllamaEmbedder) Model() string
- type OpenAIEmbedder
- func (e *OpenAIEmbedder) Backend() string
- func (e *OpenAIEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)
- func (e *OpenAIEmbedder) Dimensions() int
- func (e *OpenAIEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (e *OpenAIEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (e *OpenAIEmbedder) Model() string
- type TracedEmbedder
- func (t *TracedEmbedder) Backend() string
- func (t *TracedEmbedder) ChunkText(text string, maxTokens, overlap int) ([]string, error)
- func (t *TracedEmbedder) Dimensions() int
- func (t *TracedEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (t *TracedEmbedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (t *TracedEmbedder) Model() string
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 ¶
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 ¶
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
}
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 ¶
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 ¶
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:
- Build llama.cpp: ./scripts/build-llama.sh
- 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) EmbedBatch ¶
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:
- Install Ollama: `ollama run mxbai-embed-large`
- Ollama runs on localhost:11434
- Send text, get back 1024 numbers
- 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 ¶
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 ¶
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 ¶
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 ¶
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) EmbedBatch ¶ added in v1.1.0
func (*TracedEmbedder) Model ¶ added in v1.1.0
func (t *TracedEmbedder) Model() string