// Embeddings locaux via transformers.js — multilingual-e5-small (384 dimensions). // Gratuit, privé, hors-ligne après le premier téléchargement (OpenRouter n'offre pas d'embeddings). // Convention E5 : préfixes "query: " et "passage: ". import { resolve } from "node:path"; export const EMBEDDING_DIM = 384; const MODEL_ID = "Xenova/multilingual-e5-small"; type FeatureExtractor = (texts: string[], opts: { pooling: "mean"; normalize: boolean }) => Promise<{ tolist(): number[][]; }>; let _extractor: Promise | null = null; async function extractor(): Promise { if (!_extractor) { _extractor = (async () => { const { pipeline, env } = await import("@huggingface/transformers"); env.cacheDir = resolve(process.cwd(), "data/models"); const p = await pipeline("feature-extraction", MODEL_ID, { dtype: "fp32" }); return p as unknown as FeatureExtractor; })(); } return _extractor; } export async function embedPassages(texts: string[]): Promise { const ex = await extractor(); const out: Float32Array[] = []; const BATCH = 16; for (let i = 0; i < texts.length; i += BATCH) { const batch = texts.slice(i, i + BATCH).map((t) => "passage: " + t.slice(0, 2000)); const res = await ex(batch, { pooling: "mean", normalize: true }); for (const v of res.tolist()) out.push(Float32Array.from(v)); } return out; } export async function embedQuery(text: string): Promise { const ex = await extractor(); const res = await ex(["query: " + text.slice(0, 2000)], { pooling: "mean", normalize: true }); return Float32Array.from(res.tolist()[0]); } // --- conversions blob <-> vecteur (SQLite stocke des BLOB) --- export function vecToBlob(v: Float32Array): Uint8Array { return new Uint8Array(v.buffer.slice(0), 0, v.length * 4); } export function blobToVec(b: Uint8Array): Float32Array { const buf = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); return new Float32Array(buf); } /** Similarité cosinus — les vecteurs E5 sont déjà normalisés → produit scalaire. */ export function cosine(a: Float32Array, b: Float32Array): number { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; }