Tuto â Index vectoriel avec SQLite et sqlite-vec
Public visé : développeur qui veut stocker et rechercher des embeddings sans serveur dédié.
Objectif : crĂ©er un index vectoriel embarquĂ© (SQLite + extension sqlite-vec) et faire une recherche par similaritĂ© â le stockage du cĆur RAG (specs/ia-coeur.md).
Concepts : voir la fiche Embeddings et recherche sémantique.
Pourquoi sqlite-vec ?
- Zéro infra : un simple fichier SQLite, pas de serveur vectoriel à administrer.
- Suffisant pour une base de quelques milliers de passages (notre cas).
- Portable : l'index voyage avec le fichier
.sqlite.
1. Installer et charger l'extension
sqlite-vec est une extension chargeable (non incluse). RĂ©cupĂ©rer la release loadable (github.com/asg017/sqlite-vec) â sqlite-vec-<ver>-loadable-linux-x86_64.tar.gz â vec0.so (~58 Ko), p. ex. dans /usr/local/lib/sqlite-vec/.
En CLI (test) :
.load /usr/local/lib/sqlite-vec/vec0 SELECT vec_version();
En PHP 8.5 (le cas du bundle, validĂ© sur le VPS â ) :
$pdo->loadExtension('/usr/local/lib/sqlite-vec/vec0.so'); // Pdo\Sqlite::loadExtension()
Chemin pointĂ© par RAG_SQLITE_VEC_PATH. â ïž La recherche kNN exige l'extension chargĂ©e dans PHP : si le build/open_basedir l'empĂȘche, il faut dĂ©porter la recherche hors PHP (le repli « indexation outillĂ©e » seul ne couvre que l'Ă©criture â cf. specs/ia-coeur.md §3.4).
2. Créer la table de vecteurs
Dimension = celle du modÚle (768 pour multilingual-e5-base), métrique cosinus :
CREATE VIRTUAL TABLE chunk_vector USING vec0( embedding float[768] distance_metric=cosine );
Le rowid de chunk_vector = l'id du chunk. Les métadonnées (chemin, section, contenu) restent dans la table classique chunk ; on relie par ce rowid (cf. schéma spec L0 §5).
3. Insérer des vecteurs
INSERT INTO chunk_vector(rowid, embedding) VALUES (:chunk_id, '[0.012, -0.044, 0.881, ...]'); -- 768 valeurs
Astuce cosinus : si les vecteurs sont normalisés à l'embedding (
normalize_embeddings=TruecÎté microservice Python), la distance L2 et le cosinus deviennent cohérents pour le classement.
4. Rechercher les k plus proches
SELECT rowid, distance FROM chunk_vector WHERE embedding MATCH :query_vec AND k = 5 ORDER BY distance;
On rĂ©cupĂšre les rowid (= chunk.id) les plus proches, puis on joint chunk pour le contenu + la source. Le score exposĂ© = 1 - distance (â ~[0,1]) :
SELECT c.path, c.section, c.anchor, (1 - v.distance) AS score FROM chunk_vector v JOIN chunk c ON c.id = v.rowid WHERE v.embedding MATCH :query_vec AND v.k = 5 ORDER BY v.distance;
5. IntĂ©gration dans le cĆur
VectorStoreInterface::upsertDocument()remplace transactionnellement un document et ses chunks/vecteurs ;::search()exĂ©cute la requĂȘte kNN ci-dessus.- La rĂ©indexation incrĂ©mentale s'appuie sur le
content_hashdu document (documentHash()) : inchangé = on ne ré-embedde pas (spec L0 §3.4).
6. â ïž PiĂšge vĂ©cu en PHP : binding PDO::PARAM_INT
Sur la table virtuelle vec0, rowid et k doivent ĂȘtre bindĂ©s en entier explicite ; sinon PDO les passe comme chaĂźnes et SQLite renvoie :
SQLSTATE[HY000]: Only integers are allowed for primary key values
Bonne pratique (corrigée dans telaria/rag-bundle v0.1.3) :
$stmt = $pdo->prepare( 'SELECT rowid, distance FROM chunk_vector WHERE embedding MATCH :vec AND k = :k ORDER BY distance' ); $stmt->bindValue(':vec', $jsonVec, PDO::PARAM_STR); // '[0.01, ...]' $stmt->bindValue(':k', $k, PDO::PARAM_INT); // â ïž INT, pas STR $stmt->execute();
Ă l'insertion :
$insert->bindValue(':rowid', $chunkId, PDO::PARAM_INT); $insert->bindValue(':vec', $jsonVec, PDO::PARAM_STR);
SymptĂŽme inverse possible (avant correction du bundle) : insertion OK mais requĂȘte MATCH qui plante seulement en runtime â d'oĂč l'intĂ©rĂȘt d'un test bout-en-bout.
7. Validation bout-en-bout sur VPS (2026-05-26)
Chemin nominal vĂ©rifiĂ© en production : Pdo\Sqlite::loadExtension() fonctionne sur le build PHP 8.5 du VPS, ingest complet de telaria-doc â 181 documents / 2430 chunks, requĂȘte « comment rĂ©initialiser mon mot de passe ? » â top-1 sur specs/auth-compte.md#5-mot-de-passe-oubliĂ© avec score ~0.86. Le repli « dĂ©port hors PHP » de §3.4 reste un plan B thĂ©orique.
Pour aller plus loin
- Spec du cĆur :
specs/ia-coeur.md - Calcul des vecteurs : microservice d'embeddings Python
- Assemblage complet : RAG de bout en bout