fix: défaut ttsEngine="lib" (crash-loop install fraîche) + ragCorpusDir externe-d'abord — v0.1.3

Deux bugs de prod trouvés en validant le cycle OTA corpus r2 :

1. ttsEngine default "prod" → toute install fraîche (release patient) chargeait
   Qwen3TtsEngine legacy → SIGSEGV déterministe dans llama_context ctor
   (libtts_talker_cpu compilé contre llama-upstream, libllama.so embarqué =
   fork ql depuis 2026-06-02, dérive ABI llama_context_params). La tablette
   release crash-loopait au démarrage du service depuis la migration ; la
   tablette dev ne le voyait pas (config tts_engine=lib persistée).
   → default "lib" (seul backend compatible avec les jniLibs livrés).

2. Le corpus RAG était résolu via MODELS_DIR/../rag_corpus : sur tablette dev,
   MODELS_DIR = legacy /data/local/tmp (modèles adb) → syncDir lisait l'ancien
   corpus alors que l'OTA dépose le composant rag_corpus dans le stockage
   externe. → KazeiaPaths.ragCorpusDir avec priorité INVERSE des modèles :
   l'externe (installeur/OTA) prime dès qu'il est non vide, fallback legacy.

versionCode 4 / 0.1.3, publié catalog v6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazeia Team 2026-06-11 23:32:16 +02:00
parent 747848a475
commit 16d56bfd4d
4 changed files with 29 additions and 10 deletions

View File

@ -44,8 +44,8 @@ android {
applicationId = "com.kazeia"
minSdk = 28
targetSdk = 36
versionCode = 2
versionName = "0.1.1"
versionCode = 4
versionName = "0.1.3"
// WebDAV de distribution — injecté depuis local.properties.
buildConfigField("String", "WEBDAV_BASE", "\"${localProp("webdav.base")}\"")

View File

@ -26,6 +26,7 @@ object KazeiaPaths {
private const val LEGACY_MODELS = "/data/local/tmp/kazeia/models"
private const val LEGACY_LLM = "/data/local/tmp/kazeia-et"
private const val LEGACY_RAG = "/data/local/tmp/kazeia/rag_corpus"
/** Racine modèles STT/TTS (Whisper, GGUF, qwen3-tts-npu ; voix via `/../`). */
@Volatile var modelsDir: String = LEGACY_MODELS
@ -35,6 +36,13 @@ object KazeiaPaths {
@Volatile var llmDir: String = LEGACY_LLM
private set
/** Corpus RAG. Priorité INVERSE des modèles : l'EXTERNE prime dès qu'il est
* non vide, car c'est que l'installeur/OTA dépose le composant
* `rag_corpus` sur une tablette dev, les modèles restent legacy mais le
* corpus à jour arrive par le catalogue. Fallback legacy (adb push) sinon. */
@Volatile var ragCorpusDir: String = LEGACY_RAG
private set
@Volatile private var initialized = false
/**
@ -50,8 +58,9 @@ object KazeiaPaths {
val root = File(context.getExternalFilesDir(null), "kazeia")
modelsDir = resolve(File(root, "models"), File(LEGACY_MODELS))
llmDir = resolve(File(root, "llm"), File(LEGACY_LLM))
ragCorpusDir = resolveExternalFirst(File(root, "rag_corpus"), File(LEGACY_RAG))
initialized = true
Log.i(TAG, "modelsDir=$modelsDir | llmDir=$llmDir")
Log.i(TAG, "modelsDir=$modelsDir | llmDir=$llmDir | ragCorpusDir=$ragCorpusDir")
}
}
@ -61,4 +70,11 @@ object KazeiaPaths {
legacy.absolutePath
else
external.absolutePath
/** Externe si non vide (déposé par l'installeur/OTA), sinon legacy (adb push). */
private fun resolveExternalFirst(external: File, legacy: File): String =
if (external.isDirectory && !external.list().isNullOrEmpty())
external.absolutePath
else
legacy.absolutePath
}

View File

@ -44,10 +44,13 @@ class ConfigStore private constructor(private val file: File) {
* EngineLlmAdapter (libkazeia_engine + GGUF Qwen3.5). Defaults safe = "prod".
* Bascule "lib" nécessite libllama.so fork ql dans jniLibs. */
val llmEngine: String = "prod",
/** Backend TTS : "prod" = Qwen3TtsEngine legacy, "lib" = libkazeia_tts unifié.
* Defaults safe = "prod". Bascule "lib" nécessite libllama.so fork ql + retrait
* des classes legacy `tts_talker_cpu`/`tts_cp_cpu` (incompat ABI struct). */
val ttsEngine: String = "prod",
/** Backend TTS : "lib" = libkazeia_tts unifié (SEUL compatible avec le
* libllama.so fork ql embarqué depuis 2026-06-02), "prod" = Qwen3TtsEngine
* legacy CRASHE (SIGSEGV nativeInit, dérive ABI llama_context_params) tant
* que les .so legacy ne sont pas recompilés contre les headers fork ql.
* Default "lib" depuis 2026-06-11 : l'ancien défaut "prod" mettait toute
* install fraîche en crash-loop au démarrage du service. */
val ttsEngine: String = "lib",
/** RAG (récupération de contexte injecté au prompt). Default OFF : tant que
* kazeia-engine n'expose pas embedText + qu'aucun corpus n'est ingéré,
* l'activer est inerte (l'embedder n'est pas prêt). Pilotable via admin. */
@ -113,7 +116,7 @@ class ConfigStore private constructor(private val file: File) {
ttsEnabled = true,
sttEngine = "prod",
llmEngine = "prod",
ttsEngine = "prod"
ttsEngine = "lib" // legacy "prod" = SIGSEGV avec le libllama fork ql embarqué
)
}

View File

@ -668,7 +668,7 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
intent?.getStringExtra("rag_ingest")?.let { dirArg ->
// (Ré)ingestion du corpus dans l'instance RAG live : clear + ingestDir.
// dirArg vide -> dossier corpus par défaut. Brique de l'app admin future.
val dir = dirArg.ifBlank { "${KazeiaApplication.MODELS_DIR}/../rag_corpus" }
val dir = dirArg.ifBlank { com.kazeia.KazeiaPaths.ragCorpusDir }
log("RAG ingest: $dir")
serviceScope.launch {
val r = rag
@ -2081,7 +2081,7 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
// que le delta) : couvre le 1er démarrage ET les MAJ OTA du composant
// rag_corpus (nouveaux fichiers ingérés au redémarrage suivant).
if (it.isReady)
it.syncDir("${KazeiaApplication.MODELS_DIR}/../rag_corpus")
it.syncDir(com.kazeia.KazeiaPaths.ragCorpusDir)
it.reingestMissing() // embed les docs admin ajoutés hors-ligne
rag = it
com.kazeia.rag.RagHolder.instance = it