feat(rag): valider EngineEmbedder e5 in-app (E2E réel) + préfixe passage

Le dev kazeia-engine a livré le bridge CPU-only avec embedText (commit engine
655d65a) + un GGUF multilingual-e5-small fonctionnel (converter patché pour le
tokenizer XLM-R/unigram). Bridge swappé dans jniLibs (ABI cohérente : libllama/
libggml déjà = build CPU-only du 2 juin).

- Rag.ingest : les passages portent désormais le préfixe "passage: " (e5), en
  symétrie du "query: " de retrieve (EngineEmbedder.embedPassage).
- Intent `rag_real_test` : valide le VRAI EngineEmbedder (e5) in-app.

Validé device (CPU-only, untrusted_app) : embedder ready=true dim=384, AUCUN
crash SELinux/fastrpc (la .so ne tire plus hexagon), retrieval FR correct —
requête sommeil → doc insomnie en tête (top=0.88). embedText fonctionne in-app
end-to-end. Le .so (gitignored) est un artefact build, hors git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kazeia Team 2026-06-10 12:02:44 +02:00
parent 3eab29beb2
commit 95a7a25335
2 changed files with 27 additions and 1 deletions

View File

@ -48,7 +48,10 @@ class Rag(
val chunks = Chunker.chunk(rawText, source)
var n = 0
for ((i, ch) in chunks.withIndex()) {
val v = embedder.embed(ch) ?: continue
// e5 : les passages doivent porter le préfixe "passage: " (symétrique au
// "query: " de retrieve). EngineEmbedder.embedPassage le gère ; le fallback
// FakeEmbedder embed() sans préfixe (symétrique aussi).
val v = (embedder as? EngineEmbedder)?.embedPassage(ch) ?: embedder.embed(ch) ?: continue
db.insert(source, i, ch, modelTag, v); n++
}
log("ingest '$source': $n chunks")

View File

@ -637,6 +637,29 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
} catch (e: Exception) { log("RAG test error: ${e.message}"); e.printStackTrace() }
}
}
intent?.getStringExtra("rag_real_test")?.let { query ->
// Valide le VRAI EngineEmbedder in-app (e5-small via kazeia-engine CPU-only) :
// prouve loadEmbedder/embedText sans crash SELinux + retrieval FR réel.
log("RAG real test: '$query'")
serviceScope.launch {
try {
val embModel = "${KazeiaApplication.MODELS_DIR}/embed-e5-small.gguf"
val emb = com.kazeia.rag.EngineEmbedder(embModel, nThreads = 4, pooling = -1,
queryPrefix = "query: ", passagePrefix = "passage: ", log = { m -> log(m) })
log("RAG real: embedder ready=${emb.isReady} dim=${emb.dim}")
if (!emb.isReady) { log("RAG real: embedder NON prêt — abort"); return@launch }
val rag = com.kazeia.rag.Rag(this@KazeiaService, emb, "e5-small") { m -> log(m) }
rag.clear()
rag.ingest("insomnie", "L'insomnie est un trouble du sommeil fréquent. Pour mieux dormir, évitez les écrans le soir et gardez une heure de coucher régulière.")
rag.ingest("solitude", "Le sentiment de solitude peut être douloureux. Renouer un petit lien social, même bref, aide beaucoup.")
rag.ingest("respiration", "La respiration lente et profonde calme l'anxiété : inspirez quatre secondes, expirez six secondes, répétez.")
rag.loadIndex()
val ctx = rag.retrieve(query)
log("RAG real RESULT (corpus=${rag.count()}):\n${ctx ?: "(rien au-dessus du seuil)"}")
emb.close()
} catch (e: Exception) { log("RAG real test error: ${e.message}"); e.printStackTrace() }
}
}
if (intent?.hasExtra("cfg_set") == true) {
// Maintenance/bench : persiste engine+model+prompt sans passer par le
// ContentProvider CLI (qui casse sur les valeurs contenant ':'). Les