Kazeia-engine/dist/RAG_INTEGRATION.md

4.5 KiB

Intégration RAG (embeddings) Kazeia-Engine — Guide développeur

Date : 09/06/2026 Cible : exposer texte → vecteur de phrase poolé pour le RAG de Kazeia mobile, sans toucher au chemin Speaker/TTS. Statut : entrypoint JNI livré + validé (parité bit-identique vs llama-embedding du fork).

⚠️ Même bloqueur SELinux que LLM/TTS (cf REBUILD_CPU_ONLY.md). L'embedder linke libllama.so qui dépend de libggml-hexagon.so ouvrant /dev/fastrpc-cdsp au llama_backend_init() → SIGABRT dans un APK untrusted_app. n_gpu_layers=0 ne suffit PAS (c'est l'énumération des devices qui plante). L'embedder DOIT rouler sur le libllama CPU-only (GGML_HEXAGON=OFF), comme LLM/TTS in-app. Les tests ci-dessous passent en adb shell (uid 2000, SELinux OK) mais c'est un FAUX positif pour l'app.


API

// EngineJni (dist/jni/EngineLlmEngine.kt)
external fun loadEmbedder(ggufPath: String, nThreads: Int, pooling: Int): Long
external fun embedText(handle: Long, text: String): FloatArray?   // float[n_embd] L2-normalisé, ou null
external fun freeEmbedder(handle: Long)

// Wrapper fourni
val emb = EmbedderEngine("$dir/multilingual-e5-small.gguf", nThreads = 4, pooling = -1)
val v = emb.embed("query: j'ai du mal à dormir")   // FloatArray(384), norme 1.0
emb.release()

pooling : -1 = défaut du modèle (recommandé) ; 1 = MEAN (e5) ; 2 = CLS (bge).

Handle séparé du LLM Speaker/TTS. CPU pur, aucun HTP. Déterministe (pas d'échantillonnage).


Modèle d'embedding — décision critique (point #1 du spec)

Statut
Recommandé multilingual-e5-small (384-dim, FR-capable, pooling MEAN) — à sourcer/convertir + versionner
Testé localement nomic-embed-text-v1.5 (768-dim) — anglo-centré, marge FR fine

Mesure FR (nomic, sur tablette) : cos("mal à dormir","insomnie")=0.556 vs cos("mal à dormir","recette de tarte")=0.517 — ordre correct mais marge de 0.04 seulement. Avec un mauvais préfixe l'ordre s'inverse (0.477 vs 0.611). Conclusion : nomic n'est pas assez robuste en FR pour un RAG thérapeutique. Shipper multilingual-e5-small comme le spec l'exige.

Impératifs (contrat de distribution) :

  • Le MÊME GGUF à l'ingestion ET à la requête (vecteurs incompatibles sinon). Versionner le n° de modèle.
  • Préfixes e5 (query: / passage:) = côté appelant, jamais dans le moteur.

Validation effectuée (tests d'acceptation du spec)

Sur tablette SM8750, adb shell, nomic-embed-text-v1.5 Q4_K_M, CPU :

Test Résultat
Arch BERT/nomic-bert charge end-to-end vecteur produit
embedText renvoie float[n_embd] 768 (nomic) / 384 attendu (e5-small)
Déterminisme (2 runs, même texte) max|diff| = 0.0 bit-identique
Norme L2 ≈ 1.0 1.0000
Cohérence sémantique (proche > loin) avec bons préfixes (model-dépendant, cf ci-dessus)
Parité référence vs llama-embedding max|diff| = 5e-7, cos = 1.000000

La parité prouve que le chemin C++ (tokenize add_special + batch manuel logits=1 + llama_decode + llama_get_embeddings_seq + L2) est bit-identique à l'outil de référence du fork. Confirmé : BERT encoder-only est routé via llama_decode (pas llama_encode) dans ce fork — examples/embedding/embedding.cpp ne bascule sur erreur que pour les modèles encoder ET decoder (T5).


Contraintes techniques (impératifs)

  1. n_ctx = n_batch = n_ubatch = 512 : le pooling MEAN/CLS exige toute la séquence dans un seul ubatch. Cap à 512 tokens (chunker plus court en amont).
  2. CPU only (n_gpu_layers=0) + build CPU-only (cf bloqueur SELinux).
  3. Pas de réutilisation de prefillEmbeds/decodeEmbed (I/O float du Talker TTS, pas un embedding de phrase poolé).

Build

Rebuild libkazeia_engine.so (les 3 symboles loadEmbedder/embedText/freeEmbedder y sont exportés). Aucune nouvelle dépendance (tout est dans libllama). Pour l'in-app : build CPU-only (REBUILD_CPU_ONLY.md). Livrer la .so + un GGUF d'embedding multilingual-e5-small versionné.


Reste côté app (hors moteur, pour info)

Schéma SQLite (chunk_text, source, embedding BLOB) dans SQLCipher → ingestion admin (chunk sémantique + embedText) → runtime : matrice en RAM → brute-force cosinus NEON top-k 3-5 + seuil → bloc « CONTEXTE » budgété en tokens dans le system prompt. Pas de vector-DB, pas d'ANN, pas d'ONNX. Le RAG ne porte jamais la gestion de crise.