| import type { EmbeddedChunk, ScoredChunk } from "../types"; | |
| /** | |
| * Cosine similarity between two vectors. | |
| */ | |
| export function cosineSimilarity(a: Float32Array, b: Float32Array): number { | |
| let dot = 0; | |
| let normA = 0; | |
| let normB = 0; | |
| for (let i = 0; i < a.length; i++) { | |
| dot += a[i] * b[i]; | |
| normA += a[i] * a[i]; | |
| normB += b[i] * b[i]; | |
| } | |
| return dot / (Math.sqrt(normA) * Math.sqrt(normB)); | |
| } | |
| /** | |
| * Search embedded chunks by cosine similarity to query embedding. | |
| * Returns the top-K results sorted by descending similarity score. | |
| */ | |
| export function vectorSearch( | |
| queryEmbedding: Float32Array, | |
| chunks: EmbeddedChunk[], | |
| topK: number = 20, | |
| ): ScoredChunk[] { | |
| const scored = chunks.map((chunk) => ({ | |
| chunk, | |
| score: cosineSimilarity(queryEmbedding, chunk.embedding), | |
| source: "vector" as const, | |
| })); | |
| scored.sort((a, b) => b.score - a.score); | |
| return scored.slice(0, topK); | |
| } | |