TypeScript 98.3%
CSS 0.9%
Shell 0.7%
1// Embeddings locaux via transformers.js — multilingual-e5-small (384 dimensions).2// Gratuit, privé, hors-ligne après le premier téléchargement (OpenRouter n'offre pas d'embeddings).3// Convention E5 : préfixes "query: " et "passage: ".45import { resolve } from "node:path";67export const EMBEDDING_DIM = 384;8const MODEL_ID = "Xenova/multilingual-e5-small";910type FeatureExtractor = (texts: string[], opts: { pooling: "mean"; normalize: boolean }) => Promise<{11 tolist(): number[][];12}>;1314let _extractor: Promise<FeatureExtractor> | null = null;1516async function extractor(): Promise<FeatureExtractor> {17 if (!_extractor) {18 _extractor = (async () => {19 const { pipeline, env } = await import("@huggingface/transformers");20 env.cacheDir = resolve(process.cwd(), "data/models");21 const p = await pipeline("feature-extraction", MODEL_ID, { dtype: "fp32" });22 return p as unknown as FeatureExtractor;23 })();24 }25 return _extractor;26}2728export async function embedPassages(texts: string[]): Promise<Float32Array[]> {29 const ex = await extractor();30 const out: Float32Array[] = [];31 const BATCH = 16;32 for (let i = 0; i < texts.length; i += BATCH) {33 const batch = texts.slice(i, i + BATCH).map((t) => "passage: " + t.slice(0, 2000));34 const res = await ex(batch, { pooling: "mean", normalize: true });35 for (const v of res.tolist()) out.push(Float32Array.from(v));36 }37 return out;38}3940export async function embedQuery(text: string): Promise<Float32Array> {41 const ex = await extractor();42 const res = await ex(["query: " + text.slice(0, 2000)], { pooling: "mean", normalize: true });43 return Float32Array.from(res.tolist()[0]);44}4546// --- conversions blob <-> vecteur (SQLite stocke des BLOB) ---47export function vecToBlob(v: Float32Array): Uint8Array {48 return new Uint8Array(v.buffer.slice(0), 0, v.length * 4);49}50export function blobToVec(b: Uint8Array): Float32Array {51 const buf = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);52 return new Float32Array(buf);53}5455/** Similarité cosinus — les vecteurs E5 sont déjà normalisés → produit scalaire. */56export function cosine(a: Float32Array, b: Float32Array): number {57 let s = 0;58 for (let i = 0; i < a.length; i++) s += a[i] * b[i];59 return s;60}61