03-comment-je-travaille/tutos/ia/index-vectoriel-sqlite-vec.md

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=True cÎ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_hash du 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

Assistant documentaire

Posez une question sur la documentation. Les rĂ©ponses citent leurs sources — un clic ouvre le document Ă  gauche.

Loading…
Loading the web debug toolbar…
Attempt #