search for standard sites pub-search.waow.tech
search zig blog atproto

feat: upgrade embeddings from voyage-3-lite to voyage-4-lite (1024 dims)

voyage-3-lite (512 dims) produced poor semantic search quality — only 4
results for "consciousness" vs 39 on greengale.app. voyage-4-lite was
released Jan 2026 with significantly better retrieval accuracy.

- model: voyage-3-lite → voyage-4-lite
- dims: 512 → 1024
- explicit output_dimension parameter for Matryoshka support
- tpuf namespace deleted, embedded_at cleared for full re-embed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+12 -7
+6 -3
backend/src/ingest/embedder.zig
··· 14 14 const db = @import("../db/mod.zig"); 15 15 const tpuf = @import("../tpuf.zig"); 16 16 17 - // voyage-3-lite limits 17 + // voyage-4-lite limits 18 18 const MAX_BATCH_SIZE = 20; // conservative batch size for reliability 19 19 const MAX_CONTENT_CHARS = 8000; // ~2000 tokens, well under 32K limit 20 - const EMBEDDING_DIM = 512; 20 + const EMBEDDING_DIM = 1024; 21 21 const POLL_INTERVAL_SECS: u64 = 60; // check for new docs every minute 22 22 const ERROR_BACKOFF_SECS: u64 = 300; // 5 min backoff on errors 23 23 ··· 298 298 try jw.beginObject(); 299 299 300 300 try jw.objectField("model"); 301 - try jw.write("voyage-3-lite"); 301 + try jw.write("voyage-4-lite"); 302 302 303 303 try jw.objectField("input_type"); 304 304 try jw.write("document"); 305 + 306 + try jw.objectField("output_dimension"); 307 + try jw.write(1024); 305 308 306 309 try jw.objectField("input"); 307 310 try jw.beginArray();
+1 -1
backend/src/main.zig
··· 86 86 // init vector store (reads TURBOPUFFER_API_KEY from env) 87 87 tpuf.init(); 88 88 89 - // start embedder (voyage-3-lite, 512 dims, 1 worker) 89 + // start embedder (voyage-4-lite, 1024 dims, 1 worker) 90 90 ingest.embedder.start(allocator); 91 91 92 92 // start tap consumer
+5 -3
backend/src/tpuf.zig
··· 35 35 /// can be returned directly without a DB roundtrip. 36 36 pub const VectorDoc = struct { 37 37 id: []const u8, // hashed ID for tpuf (via hashId) 38 - vector: []const f32, // embedding (voyage-3-lite, 512 dims) 38 + vector: []const f32, // embedding (voyage-4-lite, 1024 dims) 39 39 uri: []const u8, // full AT-URI (stored as metadata) 40 40 title: []const u8, 41 41 did: []const u8, ··· 101 101 } 102 102 103 103 /// Embed a search query via Voyage API (input_type="query" for asymmetric search). 104 - /// Returns a 512-dim f32 vector. Caller owns the returned slice. 104 + /// Returns a 1024-dim f32 vector. Caller owns the returned slice. 105 105 pub fn embedQuery(allocator: Allocator, text: []const u8) ![]f32 { 106 106 const vk = voyage_api_key orelse return error.NotConfigured; 107 107 ··· 115 115 var jw: json.Stringify = .{ .writer = &body_buf.writer }; 116 116 try jw.beginObject(); 117 117 try jw.objectField("model"); 118 - try jw.write("voyage-3-lite"); 118 + try jw.write("voyage-4-lite"); 119 119 try jw.objectField("input_type"); 120 120 try jw.write("query"); 121 + try jw.objectField("output_dimension"); 122 + try jw.write(1024); 121 123 try jw.objectField("input"); 122 124 try jw.beginArray(); 123 125 try jw.write(text);