Skip to content

Commit 0158fd3

Browse files
committed
fixing vector search when not using 1024 dims
1 parent c9c82f3 commit 0158fd3

File tree

8 files changed

+204
-116
lines changed

8 files changed

+204
-116
lines changed

cmd/nornicdb/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ func runServe(cmd *cobra.Command, args []string) error {
237237
cfg = config.LoadFromEnv()
238238
}
239239

240+
// YAML config file is the source of truth for embedding settings
241+
// Always use config file values if they are set (non-zero/non-empty)
242+
if cfg.Memory.EmbeddingDimensions > 0 {
243+
embeddingDim = cfg.Memory.EmbeddingDimensions
244+
}
245+
if cfg.Memory.EmbeddingProvider != "" {
246+
embeddingProvider = cfg.Memory.EmbeddingProvider
247+
}
248+
if cfg.Memory.EmbeddingModel != "" {
249+
embeddingModel = cfg.Memory.EmbeddingModel
250+
}
251+
if cfg.Memory.EmbeddingAPIURL != "" {
252+
embeddingURL = cfg.Memory.EmbeddingAPIURL
253+
}
254+
240255
// Override with CLI flags if provided
241256
if memoryLimit != "" {
242257
cfg.Memory.RuntimeLimitStr = memoryLimit

macos/MenuBarApp/NornicDBMenuBar.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,7 @@ class ConfigManager: ObservableObject {
882882
@Published var encryptionKeychainAccessDenied: Bool = false // Track if user denied Keychain access
883883

884884
@Published var embeddingModel: String = "bge-m3.gguf"
885+
@Published var embeddingDimensions: Int = 1024 // Read from config, default 1024 for bge-m3
885886
@Published var heimdallModel: String = "qwen2.5-0.5b-instruct.gguf"
886887
@Published var availableModels: [String] = []
887888

@@ -950,6 +951,15 @@ class ConfigManager: ObservableObject {
950951
}
951952
return nil
952953
}
954+
955+
/// Get an integer value from a YAML section
956+
private func getYAMLInt(key: String, from section: String) -> Int? {
957+
if let stringValue = getYAMLString(key: key, from: section) {
958+
return Int(stringValue)
959+
}
960+
return nil
961+
}
962+
953963
private let firstRunPath = NSString(string: "~/.nornicdb/.first_run").expandingTildeInPath
954964
private let launchAgentPath = NSString(string: "~/Library/LaunchAgents/com.nornicdb.server.plist").expandingTildeInPath
955965
private let modelsPath = "/usr/local/var/nornicdb/models"
@@ -1007,6 +1017,12 @@ class ConfigManager: ObservableObject {
10071017
useAppleIntelligence = provider == "openai" && url.contains("localhost:\(ConfigManager.appleEmbeddingPort)")
10081018
print("✅ Loaded use Apple Intelligence: \(useAppleIntelligence)")
10091019
}
1020+
1021+
// Load embedding dimensions from config
1022+
if let dims = getYAMLInt(key: "dimensions", from: embeddingSection), dims > 0 {
1023+
embeddingDimensions = dims
1024+
print("✅ Loaded embedding dimensions: \(dims)")
1025+
}
10101026
}
10111027

10121028
// Load kmeans section
@@ -1743,7 +1759,7 @@ struct SettingsView: View {
17431759
<key>NORNICDB_EMBEDDING_MODEL</key>
17441760
<string>\(config.useAppleIntelligence ? "apple-ml-embeddings" : config.embeddingModel)</string>
17451761
<key>NORNICDB_EMBEDDING_DIMENSIONS</key>
1746-
<string>\(config.useAppleIntelligence ? "\(ConfigManager.appleEmbeddingDimensions)" : "1024")</string>
1762+
<string>\(config.useAppleIntelligence ? "\(ConfigManager.appleEmbeddingDimensions)" : "\(config.embeddingDimensions)")</string>
17471763
<key>NORNICDB_EMBEDDING_API_KEY</key>
17481764
<string>\(config.useAppleIntelligence ? ConfigManager.getAppleIntelligenceAPIKey() : "")</string>
17491765
<key>NORNICDB_KMEANS_CLUSTERING_ENABLED</key>
@@ -2635,7 +2651,7 @@ struct FirstRunWizard: View {
26352651
<key>NORNICDB_EMBEDDING_MODEL</key>
26362652
<string>\(config.useAppleIntelligence ? "apple-ml-embeddings" : config.embeddingModel)</string>
26372653
<key>NORNICDB_EMBEDDING_DIMENSIONS</key>
2638-
<string>\(config.useAppleIntelligence ? "\(ConfigManager.appleEmbeddingDimensions)" : "1024")</string>
2654+
<string>\(config.useAppleIntelligence ? "\(ConfigManager.appleEmbeddingDimensions)" : "\(config.embeddingDimensions)")</string>
26392655
<key>NORNICDB_EMBEDDING_API_KEY</key>
26402656
<string>\(config.useAppleIntelligence ? ConfigManager.getAppleIntelligenceAPIKey() : "")</string>
26412657
<key>NORNICDB_KMEANS_CLUSTERING_ENABLED</key>

pkg/cypher/executor.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ type StorageExecutor struct {
208208
// onNodeCreated is called when a node is created or updated via CREATE/MERGE
209209
// This allows the embed queue to be notified of new content requiring embeddings
210210
onNodeCreated NodeCreatedCallback
211+
212+
// defaultEmbeddingDimensions is the configured embedding dimensions for vector indexes
213+
// Used as default when CREATE VECTOR INDEX doesn't specify dimensions
214+
defaultEmbeddingDimensions int
211215
}
212216

213217
// QueryEmbedder generates embeddings for search queries.
@@ -276,6 +280,21 @@ func (e *StorageExecutor) SetNodeCreatedCallback(cb NodeCreatedCallback) {
276280
e.onNodeCreated = cb
277281
}
278282

283+
// SetDefaultEmbeddingDimensions sets the default dimensions for vector indexes.
284+
// This is used when CREATE VECTOR INDEX doesn't specify dimensions in OPTIONS.
285+
func (e *StorageExecutor) SetDefaultEmbeddingDimensions(dims int) {
286+
e.defaultEmbeddingDimensions = dims
287+
}
288+
289+
// GetDefaultEmbeddingDimensions returns the configured default embedding dimensions.
290+
// Returns 1024 as fallback if not configured.
291+
func (e *StorageExecutor) GetDefaultEmbeddingDimensions() int {
292+
if e.defaultEmbeddingDimensions > 0 {
293+
return e.defaultEmbeddingDimensions
294+
}
295+
return 1024 // Fallback only if not configured
296+
}
297+
279298
// notifyNodeCreated calls the onNodeCreated callback if set.
280299
// This is called internally after node creation/update operations.
281300
func (e *StorageExecutor) notifyNodeCreated(nodeID string) {
@@ -1386,7 +1405,7 @@ func (e *StorageExecutor) executeDelete(ctx context.Context, cypher string) (*Ex
13861405
if strings.HasPrefix(upperDeleteClause, "DELETE ") {
13871406
deleteClause = deleteClause[7:] // len("DELETE ")
13881407
}
1389-
1408+
13901409
// Strip RETURN clause from deleteVars if present
13911410
returnInDelete := findKeywordIndex(deleteClause, "RETURN")
13921411
if returnInDelete > 0 {

pkg/cypher/schema.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ func (e *StorageExecutor) executeCreateVectorIndex(ctx context.Context, cypher s
287287
label := matches[3]
288288
property := matches[5]
289289

290-
// Parse OPTIONS if present
291-
dimensions := 1024 // Default
290+
// Parse OPTIONS if present - use configured default dimensions
291+
dimensions := e.GetDefaultEmbeddingDimensions()
292292
similarityFunc := "cosine" // Default
293293

294294
if strings.Contains(cypher, "OPTIONS") {

pkg/embed/embed.go

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func DefaultOllamaConfig() *Config {
145145
Provider: "ollama",
146146
APIURL: "http://localhost:11434",
147147
APIPath: "/api/embeddings",
148-
Model: "mxbai-embed-large",
148+
Model: "bge-m3",
149149
Dimensions: 1024,
150150
Timeout: 30 * time.Second,
151151
}
@@ -241,12 +241,12 @@ type OllamaEmbedder struct {
241241
//
242242
// // Uses localhost:11434 by default
243243
// embedder := embed.NewOllama(nil)
244-
//
244+
//
245245
// vec, err := embedder.Embed(ctx, "Hello world")
246246
// if err != nil {
247247
// log.Fatal(err)
248248
// }
249-
//
249+
//
250250
// fmt.Printf("Generated %d-dimensional embedding\n", len(vec))
251251
// // Output: Generated 1024-dimensional embedding
252252
//
@@ -255,9 +255,9 @@ type OllamaEmbedder struct {
255255
// config := embed.DefaultOllamaConfig()
256256
// config.Model = "nomic-embed-text"
257257
// config.Dimensions = 768
258-
//
258+
//
259259
// embedder := embed.NewOllama(config)
260-
//
260+
//
261261
// // Good for English text
262262
// vec, _ := embedder.Embed(ctx, "The quick brown fox")
263263
// fmt.Printf("Nomic embedding: %d dims\n", len(vec)) // 768
@@ -267,9 +267,9 @@ type OllamaEmbedder struct {
267267
// config := embed.DefaultOllamaConfig()
268268
// config.APIURL = "http://ollama-server.internal:11434"
269269
// config.Timeout = 60 * time.Second
270-
//
270+
//
271271
// embedder := embed.NewOllama(config)
272-
//
272+
//
273273
// // Connect to remote Ollama instance
274274
// vec, err := embedder.Embed(ctx, "distributed embeddings")
275275
// if err != nil {
@@ -279,19 +279,19 @@ type OllamaEmbedder struct {
279279
// Example 4 - Batch Processing for Efficiency:
280280
//
281281
// embedder := embed.NewOllama(nil)
282-
//
282+
//
283283
// documents := []string{
284284
// "Document 1 about AI",
285285
// "Document 2 about ML",
286286
// "Document 3 about NLP",
287287
// }
288-
//
288+
//
289289
// // Process in batch
290290
// embeddings, err := embedder.EmbedBatch(ctx, documents)
291291
// if err != nil {
292292
// log.Fatal(err)
293293
// }
294-
//
294+
//
295295
// // Store embeddings in database
296296
// for i, emb := range embeddings {
297297
// storeEmbedding(documents[i], emb)
@@ -313,10 +313,10 @@ type OllamaEmbedder struct {
313313
// - OFFLINE (works without internet)
314314
//
315315
// How it works:
316-
// 1. Install Ollama: `ollama run mxbai-embed-large`
317-
// 2. Ollama runs on localhost:11434
318-
// 3. Send text, get back 1024 numbers
319-
// 4. Use numbers to find similar text
316+
// 1. Install Ollama: `ollama run mxbai-embed-large`
317+
// 2. Ollama runs on localhost:11434
318+
// 3. Send text, get back 1024 numbers
319+
// 4. Use numbers to find similar text
320320
//
321321
// Models Available:
322322
// - mxbai-embed-large: 1024 dims, best quality (default)
@@ -337,7 +337,8 @@ type OllamaEmbedder struct {
337337
// - Memory: ~500MB-2GB for model
338338
//
339339
// Thread Safety:
340-
// Safe to call from multiple goroutines.
340+
//
341+
// Safe to call from multiple goroutines.
341342
func NewOllama(config *Config) *OllamaEmbedder {
342343
if config == nil {
343344
config = DefaultOllamaConfig()
@@ -508,12 +509,12 @@ type OpenAIEmbedder struct {
508509
//
509510
// apiKey := os.Getenv("OPENAI_API_KEY") // sk-...
510511
// embedder := embed.NewOpenAI(embed.DefaultOpenAIConfig(apiKey))
511-
//
512+
//
512513
// vec, err := embedder.Embed(ctx, "artificial intelligence")
513514
// if err != nil {
514515
// log.Fatal(err)
515516
// }
516-
//
517+
//
517518
// fmt.Printf("Generated %d-dimensional embedding\n", len(vec))
518519
// // Output: Generated 1536-dimensional embedding
519520
//
@@ -522,9 +523,9 @@ type OpenAIEmbedder struct {
522523
// config := embed.DefaultOpenAIConfig(apiKey)
523524
// config.Model = "text-embedding-3-large"
524525
// config.Dimensions = 3072 // Maximum quality
525-
//
526+
//
526527
// embedder := embed.NewOpenAI(config)
527-
//
528+
//
528529
// // Higher quality embeddings for critical applications
529530
// vec, _ := embedder.Embed(ctx, "complex semantic meaning")
530531
// fmt.Printf("High-quality: %d dims\n", len(vec)) // 3072
@@ -534,9 +535,9 @@ type OpenAIEmbedder struct {
534535
// config := embed.DefaultOpenAIConfig(apiKey)
535536
// config.Model = "text-embedding-3-small"
536537
// config.Dimensions = 1536
537-
//
538+
//
538539
// embedder := embed.NewOpenAI(config)
539-
//
540+
//
540541
// // 5x cheaper than text-embedding-3-large
541542
// // $0.02 per 1M tokens vs $0.13 per 1M tokens
542543
// vec, _ := embedder.Embed(ctx, "cost effective")
@@ -545,9 +546,9 @@ type OpenAIEmbedder struct {
545546
//
546547
// config := embed.DefaultOpenAIConfig(apiKey)
547548
// config.Timeout = 30 * time.Second
548-
//
549+
//
549550
// embedder := embed.NewOpenAI(config)
550-
//
551+
//
551552
// texts := []string{"doc1", "doc2", "doc3"}
552553
// embeddings, err := embedder.EmbedBatch(ctx, texts)
553554
// if err != nil {
@@ -568,9 +569,9 @@ type OpenAIEmbedder struct {
568569
// Model: "text-embedding-ada-002",
569570
// Dimensions: 1536,
570571
// }
571-
//
572+
//
572573
// embedder := embed.NewOpenAI(config)
573-
//
574+
//
574575
// // Works with multiple languages
575576
// embeddings, _ := embedder.EmbedBatch(ctx, []string{
576577
// "Hello world", // English
@@ -602,20 +603,20 @@ type OpenAIEmbedder struct {
602603
//
603604
// Models & Pricing (2024):
604605
//
605-
// text-embedding-3-small:
606-
// - 1536 dimensions
607-
// - $0.02 per 1M tokens (~750k words)
608-
// - Best for: Cost-sensitive applications
606+
// text-embedding-3-small:
607+
// - 1536 dimensions
608+
// - $0.02 per 1M tokens (~750k words)
609+
// - Best for: Cost-sensitive applications
609610
//
610-
// text-embedding-3-large:
611-
// - 3072 dimensions (can truncate to 256-3072)
612-
// - $0.13 per 1M tokens
613-
// - Best for: Maximum quality
611+
// text-embedding-3-large:
612+
// - 3072 dimensions (can truncate to 256-3072)
613+
// - $0.13 per 1M tokens
614+
// - Best for: Maximum quality
614615
//
615-
// text-embedding-ada-002 (legacy):
616-
// - 1536 dimensions
617-
// - $0.10 per 1M tokens
618-
// - Still works but use v3 instead
616+
// text-embedding-ada-002 (legacy):
617+
// - 1536 dimensions
618+
// - $0.10 per 1M tokens
619+
// - Still works but use v3 instead
619620
//
620621
// Rate Limits:
621622
// - Free tier: 3 RPM (requests per minute)
@@ -634,7 +635,8 @@ type OpenAIEmbedder struct {
634635
// - Use batch processing to reduce costs
635636
//
636637
// Thread Safety:
637-
// Safe to call from multiple goroutines.
638+
//
639+
// Safe to call from multiple goroutines.
638640
func NewOpenAI(config *Config) *OpenAIEmbedder {
639641
if config == nil {
640642
config = DefaultOpenAIConfig("")

pkg/nornicdb/db.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func DefaultConfig() *Config {
389389
EncryptionPassword: "", // Must be set if encryption enabled
390390
BoltPort: 7687,
391391
HTTPPort: 7474,
392-
KmeansClusterInterval: 15 * time.Minute, // Run k-means every 15 min (skips if no changes)
392+
KmeansClusterInterval: 15 * time.Minute, // Run k-means every 15 min (skips if no changes)
393393
}
394394
}
395395

@@ -445,9 +445,9 @@ type DB struct {
445445
embedWorkerConfig *EmbedWorkerConfig // Configurable via ENV vars
446446

447447
// K-means clustering timer (runs on schedule instead of trigger)
448-
clusterTicker *time.Ticker
449-
clusterTickerStop chan struct{}
450-
lastClusteredEmbedCount int // Track embedding count at last clustering
448+
clusterTicker *time.Ticker
449+
clusterTickerStop chan struct{}
450+
lastClusteredEmbedCount int // Track embedding count at last clustering
451451

452452
// Encryption flag - when true, all data is encrypted at BadgerDB level
453453
encryptionEnabled bool
@@ -865,6 +865,11 @@ func Open(dataDir string, config *Config) (*DB, error) {
865865
// Initialize Cypher executor
866866
db.cypherExecutor = cypher.NewStorageExecutor(db.storage)
867867

868+
// Configure executor with embedding dimensions for vector index creation
869+
if config.EmbeddingDimensions > 0 {
870+
db.cypherExecutor.SetDefaultEmbeddingDimensions(config.EmbeddingDimensions)
871+
}
872+
868873
// Load function plugins from configured directory
869874
// Heimdall plugins will be loaded later by the server after Heimdall is initialized
870875
if db.config.PluginsDir != "" {
@@ -2909,7 +2914,9 @@ func (db *DB) CreateIndex(ctx context.Context, label, property, indexType string
29092914
case "fulltext":
29102915
return schema.AddFulltextIndex(indexName, []string{label}, []string{property})
29112916
case "vector":
2912-
return schema.AddVectorIndex(indexName, label, property, 1024, "cosine")
2917+
// Use configured embedding dimensions instead of hardcoded value
2918+
dims := db.config.EmbeddingDimensions
2919+
return schema.AddVectorIndex(indexName, label, property, dims, "cosine")
29132920
case "range":
29142921
return schema.AddRangeIndex(indexName, label, property)
29152922
default:

0 commit comments

Comments
 (0)