Commit Graph

106 Commits

Author SHA1 Message Date
Kazeia Team 8bcf9f8ebe fix(voices): /voices reflète l'inventaire CosyVoice (.cvps), pas les vestiges Qwen3
voicesCursor() marquait une voix « ready » dès que ses embeddings Qwen3
(_voice_prefix/suffix.bin) existaient — des vestiges morts depuis la bascule
CosyVoice → des voix fantômes « prête » sans aucun .cvps (incident zelda).

Désormais : union (WAV = « à convertir ») ∪ (cosyvoice/*.cvps = « prête ») ;
les .bin Qwen3 ne sont plus comptés ; nouvelle colonne cvps_exists (additive).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:51:56 +02:00
Kazeia Team 613c7efc66 fix(tts): repli sur la voix par défaut si le .cvps configuré est absent
Incident device : profil actif PatientTest a voice_id=zelda, mais seuls
damien.cvps + elodie.cvps sont déployés → voiceHandleOrThrow() levait
« aucune voix chargée ('zelda') » → speakText catch → TTS MUET (aucun son),
alors que STT + LLM fonctionnaient.

voiceHandleOrThrow bascule désormais sur defaultVoiceId (damien, toujours livré)
quand la voix demandée n'a pas de .cvps, au lieu de couper toute parole. Une
voix manquante dégrade gracieusement au lieu de rendre Kazeia muet.

Note (hors scope) : l'endpoint /voices reflète l'inventaire LEGACY Qwen3
(WAV+.bin), pas les .cvps CosyVoice → l'admin peut sélectionner une voix
non déployée. À réconcilier séparément.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:40:42 +02:00
Kazeia Team ca530f6d85 fix(pipeline): régressions UI/STT post-bascule CosyVoice (orbe, texte, 1ère phrase)
Trois bugs introduits/exposés par la bascule CosyVoice (66e81f0) :

#3 orbe figé "réflexion" même fini — KazeiaService.finally : le reset du
   visualizer était conditionné à !_isListening → en écoute continue l'orbe ne
   quittait jamais Thinking. Reset VisualizerSignal.Idle désormais inconditionnel.

#2 texte non affiché pendant le TTS + #3 orbe non animé — cause commune : le
   callback onSegmentPlaying était accepté puis JETÉ par KazeiaPipeline.speakText
   (la branche streaming Qwen3 qui le câblait avait été retirée). Rebranché :
   - core.TtsEngine.synthesizeAndPlay(..., onSegment) ;
   - CosyVoiceTtsEngine émet onSegment(phrase, durée, enveloppe, spectro) par
     phrase au démarrage de sa lecture ;
   - nouvel util tts/AudioFeatures (enveloppe RMS + spectrogramme 12 bandes via
     FFT radix-2 maison, sans dépendance) remplaçant le calcul perdu du chemin
     Qwen3 ; le reveal mot-par-mot et l'orbe Speaking refonctionnent.

#1 1ère phrase ratée — boucle de capture KazeiaService :
   - pré-roll (~300 ms d'anneau sous-seuil préfixé au buffer) → l'attaque
     consonantique n'est plus clippée ;
   - tail buffer anti-écho 800→300 ms : 800 ms visait la queue MediaPlayer
     (disparue) ; CosyVoice draine via marker AudioTrack → 300 ms suffit et ne
     mange plus le début de la réponse après que Kazeia a parlé.

Build release vert + installé OPD2415 (vc15, données préservées). À valider à
l'usage : synchro texte, orbe, capture début de phrase, absence de larsen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:51:35 +02:00
Kazeia Team ecda7b5715 chore(stt): retrait du backend STT "lib" (non utilisé/non validé)
Le backend STT "lib" (KazeiaSttAdapter → com.kazeia.stt.SttEngine → libkazeia_stt)
n'était jamais le défaut (sttEngine="prod"=Whisper) et restait non validé
(transcriptions vides/drift, A/B non mergé). Retiré :
- KazeiaService : dispatch STT simplifié → WhisperHybridEngine seul.
- archivés : KazeiaSttAdapter.kt, stt/SttEngine.kt, BenchQnnHarness.kt + libkazeia_stt.so
  (/opt/Kazeia/archive/2026-06-18_cosyvoice-bascule/stt-lib/).
- KazeiaApplication : trigger bench_qnn retiré.
- ConfigStore.sttEngine documenté vestigial.
- manifest jniLibs re-figé (18→17).
Gardés : WhisperHybridEngine (NPU, gelé), MelExtractor (mel_extractor), core.SttEngine.
Build vert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:56:58 +02:00
Kazeia Team a1c785bdf8 chore(cleanup): purge des traces legacy résiduelles (audit post-bascule)
Audit complet : 0 référence de CODE aux classes/libs supprimées (vérifié).
Nettoyage des résidus :
- code mort : vars locales inutilisées (wavPath dans setVoiceId, sentenceCounter)
  + bindings JNI TTS-Talker Qwen3-TTS de EngineJni (nEmbd/nVocab/resetEmbeds/
  prefillEmbeds/decodeEmbed, 0 caller).
- commentaires périmés recalés : docstring ttsEngine (ConfigStore) + défaut field
  ttsEngine "lib"→"cosyvoice" ; commentaire cascade (ExecuTorchLlmEngine→UnifiedLlmAdapter,
  Guard-0.6B→Guard-4B) ; ttsStreamer (Qwen3→CosyVoice) ; AudioVisualizer SPECTRUM_BANDS.

Build app+admin vert. Restent uniquement des notes historiques exactes
(« X retiré 2026-06-18 ») — documentation, pas du code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:48:03 +02:00
Kazeia Team a2fc01987b release(app): 0.2.3 / versionCode 15 (APK lean sans natifs JNA)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:38:51 +02:00
Kazeia Team 22dbd2145f build: exclure les natifs JNA cross-plateforme (-2,3 Mo, APK = binaires utiles)
JNA (transitif sqlcipher/DJL) embarquait des natifs aix/darwin/win inutiles
(aucun android-aarch64 fourni, JNA non utilisé dans le code). APK ne contient
plus que les binaires nécessaires + le code ; tous les modèles passent par
l'OTA (rappel : l'APK ne doit contenir aucun modèle — vérifié, 0 fichier modèle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:20:13 +02:00
Kazeia Team 66e81f0668 refactor(tts): bascule CosyVoice = seul TTS + purge legacy (≈322 Mo libs)
Bascule définitive vers CosyVoice (clonage vocal 9 langues, CPU-only) :
- ConfigStore défaut ttsEngine = "cosyvoice" ; KazeiaService dispatch CosyVoice
  uniquement ; chemins TTS-B/streaming Qwen3 retirés (CosyVoice = batch
  speakText qui streame en interne) ; KazeiaPipeline.speakText simplifié.

Purge du superseded (archivé /opt/Kazeia/archive/2026-06-18_cosyvoice-bascule/) :
- Kotlin tts/ : Qwen3TtsEngine, KazeiaTtsEngine, TtsEngine(lib), TtsPipeline,
  Qwen3TtsGgmlDecoder, TtsTalkerCpuJni, TtsCpCpuJni, NeonOps, BenchTtsHarness,
  Qwen3BpeTokenizer. (Gardés : CosyVoiceTtsEngine, SentenceStreamer, core.TtsEngine.)
- v2/ (rewrite abandonné) + entrées manifest ChatActivityV2/KazeiaServiceV2.
- Handlers d'intents dev Qwen3 (run_pipeline/stream_*/full_pipeline/decode_codes).
- jniLibs (-322 Mo) : libexecutorch_jni(185M), libexecutorch, libtts_pipeline(51M),
  libQnnGpu*(3), libkazeia_tts, libfbjni, libllama-common(74M).
- CMake : neon_ops/tts_talker_cpu/tts_cp_cpu/tts_decoder_ggml + ggml/llama IMPORTED
  (gardé : mel_extractor pour STT).
- Gradle : executorch.jar + fbjni + soloader.
- Manifest jniLibs re-figé (27→18 libs).

Survivants prod : LLM GGUF (kazeia_engine+llama+ggml) + LLM .pte (kazeia_pte+QnnHtp)
+ STT (kazeia_stt+ORT+mel_extractor) + TTS (cosyvoice). Build debug+release vert.

Bump 0.2.2 / versionCode 14.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:07:41 +02:00
Kazeia Team 3d002318d5 feat(updates): MAJ manuelles pilotées depuis l'admin (plus d'auto-update)
L'app patient ne vérifie/installe plus de MAJ au lancement. OnboardingActivity
démarre directement si le cœur est présent (offline) ; le catalogue n'est
sollicité que pour la 1ʳᵉ installation. Plus de prompt APK ni de worker
d'update en arrière-plan au boot.

Côté patient :
- UpdateStatus (état live) + UpdateWorker (check / install) réutilisant
  ProvisioningManager (contenu) + ApkUpdater (APK, PackageInstaller).
- ContentProvider : call("update_check"|"update_install") enqueue le worker ;
  query /updates expose phase + dispo APK + nb composants + progression.

Côté admin (Kazeia Admin) :
- écran « Mises à jour » (nav) : bouton Vérifier + Installer, état + barre de
  progression (poll /updates), via KazeiaUpdateClient.
- L'install APK ouvre le dialogue système PackageInstaller sur la tablette.

Bump 0.2.1 / versionCode 13. Round-trip device validé (check → AVAILABLE,
1 composant GGUF 2.38 Go détecté).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:27:41 +02:00
Kazeia Team a2f82f69f9 release(app): refonte 0.2.0 (versionCode 12)
Jalon refonte : moteur LLM unifié GGUF+.pte (UnifiedLlmAdapter), presets
sampling + toggle debug admin, OTA catalogue v7 (Speaker GGUF). Bump pour
publication self-update aux tablettes de prod.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:09:06 +02:00
Kazeia Team 577aad3b2f fix(dist): hasCoreModels() reflète le cœur post-refonte (GGUF speaker + Whisper)
Le raccourci hors-ligne testait encore l'ancien .pte 4B + talker_f16 comme
« cœur », périmé depuis la refonte (Speaker = q35-lmq4.gguf, .pte = alternative
admin, TTS provisionné à part #286). Une tablette correctement provisionnée
était vue « cœur manquant » → re-check réseau inutile. Teste désormais
q35-lmq4.gguf + whisper-small-sm8750.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 07:52:22 +02:00
Kazeia Team 471fb5d5fb feat(admin): presets d'échantillonnage LLM (Speaker/Thinker) — app-side complet
Système de presets de sampling réglables depuis l'admin, applicables au Speaker
et au Thinker. Inspiré du tuning Kaz (temp/top-p/top-k/pénalités).

Core/config :
- SamplingParams (core) : +presencePenalty, +frequencyPenalty
- ConfigStore.ModelConfig : +topP/topK/repeatPenalty/presence/frequency/maxTokens
  +presetName ; SamplingPreset + RuntimeConfig.presets (bibliothèque) ; JSON ;
  5 presets d'usine (Équilibré/Kaz chaleureux/Factuel/Analyse/Créatif).
  Défauts maxTokens : Speaker 450, Thinker 128 (préserve la longueur prod).
- ContentProvider : colonnes speaker_*/thinker_* sampling + presets_json (R/W).
- KazeiaService : samplingFrom(ModelConfig) → SamplingParams pour Speaker+Thinker
  (au lieu du hardcodé). maxTokens honoré par le moteur ; le reste prêt.

Admin :
- KazeiaConfigClient : Sampling + Preset + parse/serialize presets_json + updateSampling/updatePresets
- ConfigRepository : updateSampling/updatePresets
- ParametersScreen (nav "Paramètres") : éditeurs sliders Speaker/Thinker + apply preset
  + bibliothèque CRUD ; bandeau honnête « seul Tokens max agit aujourd'hui ».

⚠ Le moteur natif n'honore que maxTokens à ce jour. Spec d'extension JNI sampling
pour le dev engine : docs/SAMPLING_ENGINE_SPEC.md (~10 lignes app à brancher après).

Vérifié device : colonnes sampling R/W (temp/max/preset_name), presets_json seedés
+ lus. Build app+admin verts, admin lancé sans crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 05:59:18 +02:00
Kazeia Team b11d8a96d4 diag(bench): option .pte multi-contexte en premier (bench_llm_pte_first)
Trigger de diagnostic pour charger les .pte multi-contextes (8B/7B) en
premier dans un process frais. A servi à prouver que l'échec in-app des
gros .pte est un artefact du bench (2ᵉ .pte multi-contexte dans le même
process), PAS un problème d'artefact/version : 8B chargé en 1er = génère
12 tok/s in-app. Prod = 1 .pte/process (restart au switch) → 7B/8B OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 00:13:54 +02:00
Kazeia Team 93a3953614 feat(admin): toggle "mode debug" — masque le bouton logs/métriques côté patient
Nouveau flag ConfigStore.debugEnabled (défaut false), propagé via le
ContentProvider (/config colonne debug_enabled) et piloté par un Switch dans
l'écran Système de l'app admin.

- ConfigStore : champ debugEnabled + (de)sérialisation JSON
- KazeiaTelemetryProvider : colonne debug_enabled (query + update)
- KazeiaService : applique debugEnabled → _debugMode au boot et au reload config
- ChatActivity : _debugMode gate désormais la VISIBILITÉ du bouton debug
  (applyDebugGate). Off → bouton masqué + panneau forcé fermé (écran patient
  épuré). On → bouton visible, ouvre logs + métriques CPU/GPU/NPU/RAM.
- activity_chat.xml : btnDebugToggle visibility=gone par défaut (pas de flash)
- app-admin : KazeiaConfigClient/ConfigRepository + DebugCard dans SystemScreen

Vérifié device : round-trip provider debug_enabled 0→1→0 OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:43:50 +02:00
Kazeia Team d1baacd096 refonte(llm): Phase 3 — archivage code mort (Genie + EngineLlmAdapter)
Après analyse de dépendances : l'essentiel d'ExecuTorch/QNN est VIVANT via le
TTS Qwen3TtsEngine "prod" (org.pytorch.executorch.Module → libexecutorch.so,
+ libQnnGpu.so backend Adreno, + QnnHtp). NE PAS archiver (stack TTS figée).

Réellement mort (refonte LLM) → archivé dans
/opt/Kazeia/archive/2026-06-17_refonte-engine/ :
- EngineLlmAdapter.kt (ancien adaptateur GGUF, remplacé par UnifiedLlmAdapter ;
  références restantes = commentaires uniquement)
- GenieJni.kt + genie_jni.cpp + libGenie.so (backend Genie mort ; libgenie_jni.so
  n'était même plus shipé → GenieJni ne pouvait plus se charger)
- cible CMake genie_jni retirée ; manifest jniLibs re-figé (27 libs, -libGenie)

Aussi : ConfigStore.llmEngine documenté comme VESTIGIAL (le dispatch passe par
UnifiedLlmAdapter magic-byte depuis la refonte ; modèle choisi par speaker.modelId).

Build release vert ; LLM device non-régressé (GGUF 18 / qwen3-4b .pte 18.6 /
guard4b .pte 21 tok/s) ; aucune lib genie dans l'APK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:28:14 +02:00
Kazeia Team 0108f47c09 refonte(llm): NPU .pte in-app sans root (ADSP_LIBRARY_PATH) + normalisation think-block
Validation device (OPD2415 rooté, mais .pte sans root via PD signé) :
- GGUF défaut Qwen3.5-4B : ~18 tok/s, FR cohérent (chemin session cache-préfixe)
- qwen3-4b .pte NPU : 18.6 tok/s | guard4b .pte NPU : 21 tok/s — FR cohérent
- DJL tokenizer arm64 (tokenizer-native:0.33.0) OK on-device

Fixes :
- KazeiaApplication : pose ADSP_LIBRARY_PATH (dossier natif extrait + chemins DSP)
  AVANT toute session QNN. Sans ça le CDSP ne trouve pas libQnnHtpV79Skel.so
  (errno 2) → device_handle 14001 → .pte renvoie handle 0. Absent de la spec
  engine (validée via qnn_llama_runner CLI, où le shell exporte ADSP_LIBRARY_PATH).
- UnifiedLlmAdapter : strip du bloc <think>…</think> en tête de sortie .pte
  (pas des special tokens => skipSpecialTokens ne les retire pas ; le GGUF le
  strippe nativement). Sinon le TTS le lit et la cascade parse faux.
- BenchLlmHarness : réécrit pour valider UnifiedLlmAdapter sur GGUF + chaque .pte.

Connu (hors périmètre intégration) :
- qwen3-8b .pte (export 05-23, sharding=2) : blob contexte incompatible runtime
  QAIRT 2.42 (magic 0x5678abcd attendu, 0x2000000 lu ; "Context group 1") →
  re-export 2.42 requis côté engine.
- qwen2.5-7b .pte absent du device (seul le GGUF est présent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:00:31 +02:00
Kazeia Team 0a72654093 refonte(llm): moteur LLM unifié GGUF + .pte NPU (UnifiedLlmAdapter)
Remplace la couche LLM par la dernière version de kazeia-engine
(cf dist/LLM_INTEGRATION.md). Chargement par magic-byte :
- GGUF ("GGUF"@0)  -> EngineLlmEngine (libllama CPU i8mm, session cache-préfixe KV)
- .pte  ("ET12"@4) -> PteLlmEngine (ExecuTorch+QNN, NPU V79, sans root)

- LlmLoader.kt copié de l'engine + fix encode DJL (encode(p,false,false))
- UnifiedLlmAdapter : pont com.kazeia.core.LlmEngine, generateWithSystem par tour
- ModelRegistry : 5 modèles (Qwen3.5-4B GGUF défaut + 4 .pte en sous-dossiers)
- KazeiaService recâblé ; DEFAULT_SYSTEM_PROMPT -> ConfigStore.DEFAULT_SPEAKER_PROMPT
- DJL tokenizer arm64 Android : tokenizers + tokenizer-native alignés 0.33.0
  (natif libdjl_tokenizer.so dans l'AAR ; natifs desktop exclus, -23 Mo APK)
- jniLibs : +libkazeia_pte.so, +libQnnHtpNetRunExtensions.so,
  libqnn_executorch_backend.so -> QAIRT 2.42 ; manifest re-figé (28 libs)
- fichiers morts archivés (ExecuTorchLlmEngine, GenieLlmEngine)

Phase 1 (#287) : build debug green. Phase 2 = validation device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 22:47:32 +02:00
Kazeia Team 3b58fcb467 feat(tts): CosyVoice rapide (hiftf16, RTF 0.88 + cache) + guard init — v0.1.10
Nouveau jeu CosyVoice livré par l'engine (mêmes 5 symboles JNI, façade inchangée,
toujours auto-contenu) : 2 .so swappés (libkazeia_cosyvoice + libcosyvoice ;
libomp/libc++_shared identiques). Modèle cv3_distilled_30k_hiftf16.gguf (953 Mo)
remplace cv3_distilled_30k.gguf — MODEL mis à jour dans l'adaptateur.

Validé device : charge OK, synthèse OK, cache de voix entre appels →
1er son tour 1 ~10,6s → tour 2 ~6,7s (-37%). (RTF dev annoncé 0.88 ; sur cette
tablette thermiquement saturée on mesure ~1,1-1,7 — à revérifier device froid.)

Bonus robustesse : guard `if (!::voiceCommands.isInitialized) return false` dans
handleVoiceCommand — une entrée arrivée avant la fin de l'init du service ne
crashe plus (UninitializedPropertyAccessException, vu en test en tirant un intent
trop tôt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:50:42 +02:00
Kazeia Team db0e56b26c perf(tts): NE PAS streamer LLM→CosyVoice (contention CPU) — documenté — v0.1.9
« Reste optionnel » : tenté le streaming LLM→TTS pour CosyVoice (synthèse de la
1ʳᵉ phrase pendant que le LLM décode, comme le chemin Qwen TTS-B).

Mesuré device : CONTRE-PRODUCTIF. LLM decode (6 threads CPU) et synthèse
CosyVoice (8 threads CPU) saturent tous les cœurs ; les chevaucher écroule le
LLM (17 → 0,55 tok/s, tour 1,1s → 41s, 1er son à 43s). Reverté.

Le streaming LLM→TTS n'a de sens que si les 2 étages utilisent des compute
distincts (Qwen3 NPU + TTS CPU). Pour CosyVoice (tout CPU) : laisser le LLM
finir vite PUIS synthesizeAndPlay, qui streame déjà phrase-par-phrase EN INTERNE
(synthèse N+1 pendant lecture N, sans contention). Net diff = un commentaire
garde-fou dans KazeiaService pour ne pas re-tenter ; code de streaming retiré.

Re-validé batch : LLM 17,1 tok/s (1,9s), TTS 1er son ~8s (RTF CosyVoice inhérent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:29:25 +02:00
Kazeia Team b822974b99 fix(llm): sessionReset natif corrigé (archi hybride) — reset de borne propre — v0.1.8
Bug : sessionReset()+sessionAsk() renvoyait VIDE. Cause = q35-lmq4 est l'archi
hybride Qwen3.5 (DeltaNet/SSM) ; llama_memory_seq_rm d'un suffixe « Returns false
if a partial sequence cannot be removed » (l'état récurrent ne se rembobine pas
partiellement). Le code ignorait ce retour → KV incohérent → tour suivant vide.

Fix natif (kazeia-engine dist/jni/kazeia_engine_jni.cpp, .so rebuildé CPU-only,
drop-in) : sessionReset teste le retour de seq_rm ; s'il échoue (récurrent/
hybride) → reset COMPLET (llama_memory_clear + re-prefill du system mémorisé) +
llama_sampler_reset. sessionStart mémorise désormais les tokens system.

App : EngineLlmAdapter.resetSession() rebascule sur le session.reset() natif
(au lieu du contournement newSession). Validé device : après reset, le tour
n'est plus vide ET la mémoire est effacée (ne connaît plus le prénom),
prefill ~285ms, decode ~17 tok/s, tours suivants OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:26:00 +02:00
Kazeia Team 8d548b1ca6 feat(llm): session cache-préfixe KV + streaming + mémoire conv. — v0.1.7
Intègre les 3 APIs livrées par l'engine (libkazeia_engine.so drop-in, façade
EngineLlmEngine.kt : generateStream / newSession / LlmSession / GenStats).

EngineLlmAdapter : crée une LlmSession au load (system prefillé 1×), et chaque
generate() fait session.ask() en STREAMING (onToken câblé au pipeline existant).
Le system n'est plus re-prefillé à chaque tour.

Mesuré device (q35-lmq4, t=6, v0.1.7) :
- tour : 2991→~1100 ms (prefill 2700→~300 ms, system caché). Decode réel
  17 tok/s (le « 3,2 » était l'artefact prefill-inclus ; tps vient maintenant
  de getLastStats, débit décode).
- streaming callback OK sous vraie JVM (le seul point que le dev n'avait pas
  pu tester) — aucun crash.
- mémoire conversationnelle (bonus) : rappelle « Richard » sur 3 tours.

Confidentialité : la mémoire est vidée aux bornes de conversation —
EngineLlmAdapter.resetSession() (recrée la session) appelé sur CLEAR_CHAT et
sur changement de profil actif (onProfileStoreChanged). Validé : après switch
de profil, le LLM ne connaît plus le prénom.

⚠ Bug natif à corriger côté engine : sessionReset()+sessionAsk() renvoie vide
(n_past non restauré à n_sys après seq_rm). Contourné en recréant la session
(newSession). Le mode mémoire (ask→ask sans reset) est, lui, OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:12:11 +02:00
Kazeia Team 2acf3922c9 feat(llm): défaut kazeia-engine GGUF (lib), plus de .pte au runtime — v0.1.6
Directive 2026-06-15 : si on passe par kazeia-engine, on n'utilise plus de .pte.

- ConfigStore.llmEngine défaut "prod"(.pte) → "lib" (EngineLlmAdapter +
  libkazeia_engine + GGUF). Le .pte n'est plus chargé au runtime ; il reste
  câblé en repli d'urgence seulement (et sur cette plateforme son runner QNN
  échoue de toute façon : « Failed to load llm runner [1] »).
- ModelRegistry.defaultSpeaker() "qwen3-4b-seq1024"(.pte) → "qwen3.5-4b" (GGUF,
  q35-lmq4) : config cohérente, le mode lib charge sp.ggufPath() directement
  sans repli, zéro référence .pte.

Validé device (release v0.1.6, SM8750) : libkazeia_engine charge q35-lmq4.gguf
en 1.8 s (backend='lib'), 2 générations FR thérapeutiques cohérentes
(« C'est compréhensible, je suis là avec toi… » / « C'est vraiment épuisant,
je te comprends. »). Règle aussi le mode perroquet observé avec le .pte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:15:27 +02:00
Kazeia Team 359252bf5e feat(voice): voix pilotée par profil (app admin) — retrait du sélecteur patient — v0.1.5
La voix n'est plus choisie côté patient ; elle est définie PAR PROFIL dans l'app
admin (le champ Profile.voiceId + le dropdown admin existaient déjà ; seul le
runtime ne l'appliquait pas).

App patient :
- ChatActivity : suppression de setupVoiceSelector() + des listes voiceFiles/
  voiceNames/voiceColors. activity_chat.xml : suppression de la voiceBar (spinner),
  l'orbe se contraint désormais sous tvStatus (couleur = défaut service).
- KazeiaService : nouvelle source unique = le profil actif.
  - applyActiveProfileVoice() lit ProfileStore.activeProfile().voiceId (défaut
    DEFAULT_VOICE_ID="damien" en invité), appelée à l'init ET via un listener
    ProfileStore → propagation LIVE quand l'admin édite la voix / change de profil.
  - setVoiceId(id) résout selon le moteur actif : CosyVoice → cosyvoice/<id>.cvps ;
    Qwen3/lib → ../voix/<id>.wav. setVoice(pathOrId) conservé (intents test) délègue.

Validé device (release v0.1.5, SM8750, moteur cosyvoice) :
- plus de barre de voix dans l'UI patient (confirmé).
- profil voice_id=elodie → service charge 'elodie' (≠ défaut) ; changement live
  via provider voice_id=damien → ré-appliqué sans redémarrage.
- moteur 'lib' charge toujours (pas de régression du stack gelé).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:51:42 +02:00
Kazeia Team cca38ef50f feat(tts): intégrer libs CosyVoice3 auto-contenues + validation device — v0.1.4
Blocage libggml levé côté engine : nouvelles libs auto-contenues (ggml statique
+ visibilité hidden dans libcosyvoice.so, 0 symbole ggml_ exporté). 3 .so ajoutés
à jniLibs/arm64-v8a/ (libkazeia_cosyvoice, libcosyvoice ~20 Mo, libc++_shared ;
libomp déjà présent identique). Manifest régénéré (26 libs).

Validé on-device (release v0.1.4, SM8750) :
- chargement libkazeia_cosyvoice.so OK, modèle cv3_distilled_30k.gguf chargé en
  997 ms, voix 'damien' chargée, aucune collision libggml ni crash.
- nativeSynthesize (le marshalling JNI String→FloatArray, seul point non testé
  par le dev) CONFIRMÉ : phrase FR → 214080 samples 24 kHz (8.92 s), RTF ≈ 1.0.
  Audio sain (RMS 2303, pic 19741, 51% non-silence), voix clonée audible.

jniLibs gitignorés (whitelist) : seul le manifest est versionné. Tarball
build-artifacts à régénérer pour les machines tierces (suivi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:31:42 +02:00
Kazeia Team ed61737199 feat(tts): adapter CosyVoice3 + dispatch ttsEngine="cosyvoice" (câblage, libs en attente)
Câblage Kotlin/APK du moteur TTS clonage vocal CosyVoice3 distillé livré par
kazeia-engine (dist/cosyvoice). Mirror du pattern sttEngine/ttsEngine="lib".

- CosyVoiceTtsEngine.kt : CosyVoiceJni (5 symboles) + adapter implémentant
  com.kazeia.core.TtsEngine. Cache de voix (.cvps), synthèse phrase-par-phrase,
  streaming AudioTrack MODE_STREAM en ENCODING_PCM_FLOAT @24 kHz (1er son joué
  pendant que la phrase suivante se génère ; RTF~1.4 masqué par le streaming),
  suspension jusqu'au drain réel (marker) pour garder le micro coupé (anti-écho).
- KazeiaService : branche when(ttsEngine) { "cosyvoice" -> ... } à l'init.
- ConfigStore : doc du nouveau backend.

⚠ NON validé device : BLOQUEUR côté libs engine. Les .so livrés embarquent leurs
propres libggml*.so (SONAME identiques au stack LLM/TTS gelé, 535 symboles ggml_*
en collision) → interposition ELF dans le namespace JNI (RTLD_GLOBAL) : le premier
libggml chargé (LLM, au boot) gagnerait, libcosyvoice se lierait au mauvais ggml →
crash. Ni écraser ni omettre les libggml app ne marche (consommés par libllama/
libkazeia_engine/libkazeia_tts gelés). Fix attendu : ggml statique + visibilité
hidden dans libcosyvoice.so côté engine. Code prêt à valider dès libs corrigées.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:53:30 +02:00
Kazeia Team 16d56bfd4d 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>
2026-06-11 23:32:16 +02:00
Kazeia Team 747848a475 feat(rag): syncDir — synchro corpus fichiers→base à chaque démarrage
L'auto-ingestion ne jouait qu'à base vide (1er démarrage) : une MAJ OTA du
composant rag_corpus déposait les nouveaux fichiers mais le RAG continuait de
servir l'ancien corpus, sans aucun déclencheur (sauf intent manuel rag_ingest).

Rag.syncDir(dir) : ingère tout fichier .txt/.md absent de la base ou dont le
texte diffère du brut stocké (comparaison exacte). Idempotent, n'embed que le
delta → appelé à CHAQUE buildRag, remplace le cas spécial « base vide ».
Ne supprime jamais (docs admin et docs retirés du dossier restent — suppression
via l'admin).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 22:51:30 +02:00
Kazeia Team 3761e03d52 feat(rag): corpus psychoéducation FR étendu à 18 fiches (corpus-seed-r2)
Le « corpus de Damien » (/opt/Kazeia/Kaz/knowledge_base) s'est révélé être des
fichiers de test de la machinerie RAG (licornes, Wikipédia Musk, scrape Tomatis)
— aucune valeur clinique. Corpus écrit ici à la place : +12 fiches (crise
d'angoisse, pensées automatiques, émotions, estime de soi, auto-compassion,
colère, deuil, stress, relaxation musculaire progressive, pleine conscience,
anxiété sociale, demander de l'aide) en plus des 6 existantes.

Lignes éditoriales (README) : une fiche = un thème + une technique concrète,
~1 chunk (maxChars=1200), vouvoiement neutre, jamais de médication/diagnostic/
crise suicidaire (CrisisGuard gère la crise hors LLM). À valider par le
clinicien avant activation patient.

Publié : composant catalogue rag_corpus → corpus-seed-r2, catalog v4 en ligne.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 22:49:36 +02:00
Kazeia Team d7e90c16c4 fix(safety): rappel « mieux mort » + regex normalize compilées une fois
Code-review (2e passe) :
- Faux négatif CrisisGuard : « je serais/suis mieux mort », « mieux mort que
  vivant » ne déclenchaient aucun motif. Ajout du motif « mieux mort » + 3 cas
  de test. (Sécurité vitale — sensibilité d'abord.)
- normalize() recompilait 3 Regex à chaque appel (1×/message patient) :
  hoistées en constantes (MARKS/APOS/SPACES).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:22:27 +02:00
Kazeia Team 7669a79f4f fix(rag): ingestion atomique + build/teardown sérialisés (code-review)
Rag.ingest : embed D'ABORD, n'écrit la base qu'ensuite, via la nouvelle
RagDb.replaceSource (delete+insert dans UNE transaction). Un échec transitoire
d'embedding ne détruit plus les chunks existants (auparavant deleteSource était
appelé avant la boucle d'embed → corpus tronqué/vidé en silence, non rattrapé
par reingestMissing qui ne reprend que les docs à zéro chunk).

KazeiaService.buildRag/teardownRag : synchronized(ragLock) + buildRag idempotent
(retourne l'instance existante si déjà active). onCreate et un toggle config RAG
peuvent se chevaucher (deux coroutines serviceScope) → sans verrou, deux
EngineEmbedder natifs étaient créés et le perdant fuyait (jamais close()).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:10:29 +02:00
Kazeia Team e1d9c94751 fix(safety,rag): correctifs critiques code-review
CrisisGuard (sécurité vitale) :
- normalize() : suppression accents (NFD) + apostrophes→espace + compactage,
  pour matcher la sortie STT FR (souvent sans accents) et la frappe libre.
- PATTERNS étendus : formulations directes manquantes (« je veux mourir »,
  « idées suicidaires/noires », « disparaître », « me pendre », « passer à
  l'acte », « plus la force de vivre »…). « me pendre » autonome (aucun usage
  bénin) ; « me noyer » reste gardé (idiome « se noyer dans le travail »).
- Tests étendus : 12 positifs + 4 négatifs (idiomes « mourir de rire/faim »,
  « idées plein la tête », « passe à la pharmacie »). 11/11 verts.

Rag.retrieve() : un chunk plus long que le budget ne fait plus retomber tout
le bloc à null — on garantit ≥1 hit (tronqué si besoin) puis on empile tant
qu'il reste du budget. Évite la perte de contexte pertinent.

Rag.close() : @Synchronized — sérialise avec retrieve()/searchScored() pour
qu'un teardown (toggle RAG off) ne libère pas le contexte natif de l'embedder
pendant une requête provider en cours (évite le SIGSEGV).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 16:07:51 +02:00
Kazeia Team 32669ae039 docs: emplacement Nextcloud du tarball jniLibs + recette clone→build complète
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:45:03 +02:00
Kazeia Team d47206bff8 release: bump v0.1.1 (versionCode 2) — premier cycle OTA réel validé
Migration tablette dev debug→release + cycle OTA complet exercé de bout en bout
pour la première fois :
- uninstall debug → install release v1 (signé clé Kazeia, cf RELEASE_SIGNING.md)
- bump versionCode 2 / versionName 0.1.1, assembleRelease (verifyJniLibs OK)
- publication Nextcloud : APK v2 + composants RAG (e5 + corpus) + catalog v3
- sur device : Onboarding détecte « Mise à jour disponible v0.1.1 », télécharge
  (106 Mo, SHA-256 vérifié), PackageInstaller installe in-place → versionCode=2.

PREUVE CLÉ (la raison d'être de la signature release) : un marqueur de config
posé en v1 (rag_enabled=1, rag_threshold=0.85) a SURVÉCU à la mise à jour v2 →
signature cohérente entre versions → une MAJ ne détruit pas les données patient.

Note : catalog.spec.json/dist non suivis (miroir data). versionCode=2 est la
seule trace git ; la prochaine release repart de là.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:20:39 +02:00
Kazeia Team 9c6f4e8838 build: reproductibilité des jniLibs — manifest sha256 + script + garde release
Les 23 .so de app/src/main/jniLibs/ (474 Mo, 5 chaînes de build : kazeia-engine,
ExecuTorch, QNN SDK 2.42, Genie, TTS pipeline) sont des artefacts gitignorés
posés à la main — « ça ne buildait que sur cette machine ». On ne les versionne
pas (l'historique gonflerait de ~500 Mo par update engine) ; on versionne leur
DÉFINITION :

- scripts/jnilibs.MANIFEST.sha256 : état attendu (sha256 + provenances).
- scripts/jnilibs.sh : verify (CI/Gradle) | sync-engine (rafraîchit le
  sous-ensemble kazeia-engine depuis /opt/Kazeia-engine/dist) | update-manifest
  (re-fige, à committer) | export/import (tarball pour autre machine).
- Garde Gradle : preReleaseBuild dépend de verifyJniLibs → un build RELEASE avec
  des libs manquantes/modifiées/non déclarées ÉCHOUE. Debug reste libre.

Validé : release passe avec libs conformes ; échoue sur lib altérée (MODIFIÉ
libomp.so) ; verify OK après restauration. Ménage au passage : 3 .bak morts
(~293 Mo) sortis de jniLibs vers _jnilibs_backup_baks/.

Nouvelle machine : git clone + ./scripts/jnilibs.sh import <tarball> (export
déposé sur box.kazeia.com privé) → build garanti conforme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 09:31:00 +02:00
Kazeia Team e5b48e4e0f test: tests unitaires JVM — CrisisGuard (sécurité vitale) + VectorIndex + Chunker
Premier harnais de tests du projet. testImplementation junit:4.13.2, src/test/.

CrisisGuardTest — NON-RÉGRESSION DE SÉCURITÉ : 26 formulations d'idéation qui
DOIVENT déclencher (toutes les familles de motifs, casse, variantes STT collées
validées device), 10 messages de détresse ordinaire qui ne doivent PAS déclencher
(tristesse, insomnie, colère, deuil d'autrui, « envie de dormir »…), 2 limites
ASSUMÉES documentées (sensibilité d'abord : « en finir avec ces insomnies » et
mention indirecte du suicide déclenchent), invariants de la réponse (112,
honnêteté « programme », anti-isolement). Toute retouche des PATTERNS doit garder
ces tests verts ; le clinicien ÉTEND les cas, ne supprime pas de positif.

VectorIndexTest : classement cosinus, top-k, seuil, index vide/dim incohérente.
ChunkerTest : tag source, découpe+overlap sous la limite ubatch, texte vide.

./gradlew :app:testDebugUnitTest → 11 tests, 0 échec, <5 s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:44:09 +02:00
Kazeia Team d834ea4955 docs: RELEASE_SIGNING à jour — keystore versionné dans le dépôt privé comme sauvegarde
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:38:16 +02:00
Kazeia Team d936b0d55d build: versionner le keystore release dans le dépôt privé (sauvegarde)
Décision : le dépôt git.kazeia.com étant privé (Gitea auto-hébergé, équipe
restreinte), le keystore + credentials y sont versionnés comme sauvegarde —
une panne de la machine de build ne peut plus coûter la clé.

Conséquence assumée : tout accès au dépôt = capacité de signer des mises à
jour Kazeia ; la clé reste dans l'historique git définitivement. Si le dépôt
devait un jour être ouvert/partagé plus largement, faire une rotation de clé
AVANT (cf docs/RELEASE_SIGNING.md §7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:37:34 +02:00
Kazeia Team 93f09c87d3 build: signature release (keystore partagé patient+admin) + doc de gestion
Keystore release créé : /opt/Kazeia/keystore/kazeia-release.jks (RSA 4096,
alias kazeia, valide 2056), credentials dans keystore/credentials.properties
(chmod 600, hors git — la liste blanche du .gitignore racine l'exclut d'office).

Les deux modules lisent credentials.properties : présent → assembleRelease signe
en release ; absent (autre machine/CI) → repli debug + warning, le build ne casse
pas. MÊME clé pour com.kazeia et com.kazeia.admin → permettra la
signature-permission sur le ContentProvider (plan admin).

Validé : assembleRelease des deux apps OK, apksigner confirme le certificat
CN=Kazeia (SHA-256 4b408d9d…) sur les deux APK.

docs/RELEASE_SIGNING.md : gestion complète — câblage, procédure de release via
catalogue (bump versionCode obligatoire), SAUVEGARDE du keystore (le point qui
ne pardonne pas), scénarios de panne (perte clé/mdp/machine/compromission),
migration one-shot des tablettes debug→release, dev quotidien reste en debug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:32:29 +02:00
Kazeia Team fd9e04a0cb feat(rag): gestionnaire RAG complet dans l'app admin
L'écran RAG admin passait du simple CRUD de documents à un gestionnaire complet.

Côté patient :
- ConfigStore : ragThreshold (0.82) + ragTopK (3) persistés ; le retrieve live les lit.
- KazeiaService : buildRag()/teardownRag() extraits → toggle RAG À CHAUD via
  handleConfigChange (build/teardown sans restart). RagHolder partage l'instance
  vivante (embedder + index) avec le ContentProvider (même process).
- Provider : config expose rag_threshold/rag_top_k (+ update) ; nouveaux chemins
  /rag_status (enabled, ready, model, dim, doc/chunk/pending counts) et /rag_query
  (test de récupération : candidats classés AVEC scores, seuil 0, k=8).

Côté admin (RagScreen refondu, 4 sections) :
- État : switch activer/désactiver + badge embedder prêt + modèle/dim/compteurs.
- Réglages : slider seuil + stepper top-k, persistés.
- Test de récupération : requête simulée → fragments classés + scores, marqués
  ✓ injecté / ✗ filtré selon le seuil courant (cale le seuil visuellement).
- Documents : liste + ajout/édition/suppression (existant).
+ KazeiaConfigClient/KazeiaRagClient/RagRepository étendus.

Validé device : status (ready=1, e5-small, 7 docs/7 fragments) ; toggle OFF→teardown
/ ON→rebuild sans restart ; test "je n'arrive pas à dormir" → sommeil 0.849 en tête,
"colère contre mon frère" → colere 0.821 ; admin se lance sans crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 07:53:31 +02:00
Kazeia Team 4508b8fe2e feat(safety): garde-fous cliniques + filet de crise déterministe
Fusionne les garde-fous du prompt de Damien dans la voix chaleureuse de Kazeia,
et ajoute un filet de sécurité crise indépendant du LLM.

Bornes "soft" (niveau prompt, DEFAULT_SPEAKER_PROMPT + DEFAULT_SYSTEM_PROMPT +
copie admin, synchronisés) : pas de diagnostic / pas de pathologie nommée ;
médicament/dose/traitement -> décision médicale, renvoi médecin/psychiatre ;
refus des jeux de rôle/simulations ; réponse cadrée sur le stockage local.
Tutoiement + ton chaleureux préservés (≠ style clinique/vouvoiement de Damien).

Borne "hard" (CrisisGuard, DÉTERMINISTE) : on ne laisse jamais un 4B sous
contrainte de brièveté improviser sur une idéation suicidaire. Détection par
motifs FR (tunables clinicien) -> court-circuite Thinker+Speaker -> réponse fixe,
validée, honnête (Kazeia = programme), oriente vers l'humain + 112 (urgence EU,
valable FR et Luxembourg). Placé en tête de processLlmResponse.

Validé device : "envie d'en finir / me faire du mal / vaudrait mieux d'en finir"
-> [CRISIS] -> réponse de sécurité synthétisée (TTS frames=192) + jouée.
"arrêter mes médicaments" -> redirection médecin (prompt, pas crise). "triste et
fatigué" -> réponse normale, AUCUN faux positif crise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 23:32:38 +02:00
Kazeia Team acaf0cf2cf feat(rag): écran admin d'ingestion du corpus (provider /rag + réindexation live)
Gestion du corpus RAG depuis l'app admin, sans adb.

Patient :
- RagDb v2 : table rag_docs (documents BRUTS, source de vérité) séparée des
  embeddings rag_chunks ; onUpgrade ajoute la table.
- Rag : ingest enregistre le doc brut ; reingestMissing() (ré)indexe les docs
  sans embeddings ; listDocs/deleteDoc.
- ContentProvider /rag : query liste (source+char+chunk_count), /rag/<src> texte
  pour édition, insert (upsert doc + invalide chunks + broadcast), delete /rag/<src>.
  Broadcast RELOAD_RAG EXPLICITE (setPackage) — l'implicite n'est pas délivré au
  receiver NOT_EXPORTED sur Android récent.
- KazeiaService : receiver RAG_RELOAD -> reingestMissing live ; au démarrage,
  embarque les docs ajoutés hors-ligne.

Admin : KazeiaRagClient + RagRepository + RagScreen (Compose : liste, badge
"à indexer", ajout/édition/suppression). Remplace le StubScreen du slot Rag.

Validé device E2E : admin insert 'colere' -> RELOAD -> embeddé live (chunk_count 1,
sans restart) -> tour patient "fou de rage" récupère [Source: colere] (top 0.83)
-> réponse ancrée. App admin se lance sans crash.

Au passage : .gitignore exclut kazeia-android/*/build/ et dé-suit app-admin/build/
(1017 artefacts qui étaient trackés par erreur).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 13:57:32 +02:00
Kazeia Team 62a9803fdd feat(rag): intégration RAG dans le flux patient (corpus + ingestion + injection live)
Active le RAG de bout en bout dans la vraie conversation :
- Rag.ingestDir(dir) : ingère tous les .txt/.md d'un dossier (source = nom fichier).
- KazeiaService : auto-ingestion du corpus au 1er démarrage si base vide
  (.../kazeia/rag_corpus/), + intent `rag_ingest` (clear+reingest, brique admin future).
- Injection live : seuil relevé à 0.82 (calé e5 : base ~0.80 / pertinent ~0.88), k=3,
  pour ne PAS injecter de hors-sujet.
- Corpus seed FR de psychoéducation (6 fichiers) dans docs/rag_corpus_seed/.

Validé device (ragEnabled ON, Speaker lib qwen3.5-4b) : 6 chunks ingérés, tour
patient « je n'arrive pas à dormir… » -> retrieve 1 hit (top 0.84, le chunk sommeil ;
les 5 autres filtrés par le seuil) -> bloc CONTEXTE injecté (577 c) -> réponse ancrée.
Aucun crash, coexistence RAM OK. ragEnabled reste OFF par défaut (opt-in clinicien).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 12:12:21 +02:00
Kazeia Team 95a7a25335 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>
2026-06-10 12:02:44 +02:00
Kazeia Team 3eab29beb2 feat(rag): module RAG on-device (indépendant de l'engine), flag OFF par défaut
Module com.kazeia.rag, conçu d'après le RAG PC de Damien mais porté on-device :
- Embedder : interface + FakeEmbedder (déterministe, test hors engine) +
  EngineEmbedder (réel, branché sur EngineJni.loadEmbedder/embedText — inerte
  via UnsatisfiedLinkError tant que la lib n'expose pas ces symboles).
- VectorIndex : recherche exhaustive cosinus (produit scalaire, vecteurs L2),
  top-k + seuil. Pas d'ANN : injustifié à l'échelle (10^2–10^3 chunks).
- Chunker : découpage par phrases + overlap, tag [Source: x] (mieux que le
  découpage au caractère du PC).
- RagDb : SQLite (vecteurs en BLOB float32 LE), garde-fou modèle+dim.
- Rag : façade ingest/loadIndex/retrieve -> bloc CONTEXTE budgété en tokens.

Bindings JNI embedder ajoutés à EngineJni (loadEmbedder/embedText/freeEmbedder).
Flag ragEnabled (ConfigStore + provider rag_enabled), DEFAULT OFF -> production
inchangée. Câblage live dans KazeiaService : si activé + embedder prêt + contexte
au-dessus du seuil, préfixe le bloc CONTEXTE au prompt patient (défensif, le RAG
ne porte jamais la gestion de crise).

Validé device via l'intent `rag_test` (FakeEmbedder) : ingest 2 docs -> SQLite ->
index -> retrieve classe correctement (requête sommeil -> doc insomnie en tête).
Démarrage prod : "[RAG] désactivé (config)", rag_enabled=0. Aucune régression.

Reste (dépend de l'engine) : embedText livré + modèle e5/bge FR + ingestion d'un
vrai corpus côté admin + entrée catalogue. Cf docs/RAG_EMBEDDINGS_ENGINE_SPEC.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:09:57 +02:00
Kazeia Team 6be3e47148 docs: spec embeddings RAG pour kazeia-engine + whitelist docs/
Ajoute docs/RAG_EMBEDDINGS_ENGINE_SPEC.md : instructions à transmettre au dev
de kazeia-engine pour exposer un entrypoint texte→vecteur poolé (loadEmbedder/
embedText/freeEmbedder) réutilisant la machinerie d'embedding déjà présente dans
le fork libllama. Calée sur dist/jni/kazeia_engine_jni.cpp réel.

Whitelist .gitignore élargie à /docs/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:54:32 +02:00
Kazeia Team 4498e5ac7a chore: dégraissage du dépôt — suivi restreint à l'app mobile (liste blanche)
Le working tree contenait ~21 k fichiers non suivis (modèles, backups, SDK,
venvs, arbres expérimentaux, intouchables beta_kazeia/root_oneplus, Kaz de
Damien…) qui polluaient `git status`. Remplace la blacklist fuyante par une
LISTE BLANCHE : on n'ignore plus au cas par cas, on ignore tout à la racine
sauf l'app mobile (kazeia-android), scripts/, executorch-*, et les docs *.md/
*.txt. Rien n'est supprimé du disque — juste sorti du suivi.

Résultat : non-suivis 21389 → 0. Ajoute au passage des fichiers légitimes qui
traînaient non suivis (FROZEN.md = contrat de stack figée, scripts d'export TTS,
rapport). Le moteur unifié reste un dépôt séparé (/opt/Kazeia-engine).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:21:00 +02:00
Kazeia Team 6f45a75197 chore: snapshot migration moteur lib (préservation avant dégraissage)
Capture l'état courant de kazeia-android (migration vers le moteur lib unifié :
suppression des anciens AudioCaptureManager/VadEngine/SileroVad, WhisperJni/
WhisperLiteRt/WhisperNpu/AndroidStt, KazeiaLlmJni/LlamaCppLlmEngine,
AndroidTts/ChatterboxTts ; édits app/JNI/gradle associés). L'app construit et
tourne sur device dans cet état. Commit de préservation pour ne rien perdre
avant de restreindre le périmètre de suivi du dépôt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:19:24 +02:00
Kazeia Team 06b333f0f0 feat(speaker): rendre les Speakers GGUF sélectionnables in-app (Qwen3.5-4B, Qwen2.5-7B)
ModelRegistry devient backend-aware (PTE | GGUF) : enum Backend, ggufFile +
ggufPath() sous MODELS_DIR, fileExists() par backend, et un type de template
ChatTemplate (QWEN35_THINKOFF | PLAIN_CHATML). Ajout de deux entrées Speaker
GGUF servies par le moteur lib : Qwen3.5-4B (q35-lmq4, le défaut lib jusqu'ici
codé en dur, désormais visible) et Qwen2.5-7B (Q4_K_M, arch qwen2 dense).

KazeiaService : le path lib route le GGUF du Speaker sélectionné (sp.ggufPath())
au lieu de q35-lmq4 en dur, et passe plainChatml selon le template. Pour les
modèles sans thinking (Qwen2.5), EngineLlmAdapter construit un ChatML standard
sans bloc <think> via generateRaw (le natif l'injecterait sinon).

Affordances de test/maintenance (pilotage adb sans UI) :
- llm_text / llm_max : génération LLM pure loggée (tok/s, chars), hors pipeline TTS.
- cfg_set : persiste engine+model+prompt via am --es (contourne content --bind
  qui casse sur ':'); cfg_sys=DEFAULT résout le prompt thérapeutique en interne.

Provider /models : expose le chemin GGUF pour les entrées GGUF.

Validé device : les deux GGUF chargent, FR cohérent, templates propres.
Bench : Qwen3.5-4B ~13.5 tok/s, Qwen2.5-7B ~6.8 tok/s (CPU-only) ; qualité FR
thérapeutique nettement en faveur du Qwen3.5-4B.

Note : KazeiaService.kt et les 3 fichiers nouveaux portent aussi du travail
antérieur non commité (migration moteur lib + app admin) ; ce commit en capture
l'état courant en plus de la fonctionnalité Speaker GGUF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 23:14:06 +02:00
Kazeia Team 8a3151681d llm: add llama.cpp backend (LlamaCppLlmEngine) for Qwen3.5-4B
- KazeiaLlmJni.kt : JNI bindings for 9 native fns (load/generate/setSystem/
  reset/snapshot/stats/kvTokens/free).
- LlamaCppLlmEngine.kt : implements core LlmEngine interface over KazeiaLlmJni,
  persistent KV cache across turns, streaming token callback.
- jni/kazeia_llm_jni.cpp : JNI bridge to /opt/Kazeia/kazeia-llm/ C++ wrapper
  (compiled inline via CMakeLists).
- jniLibs : libllama.so + ggml-* from /opt/Kazeia/llama-upstream/build-android
  (rebuilt with GGML_OPENMP=OFF to avoid libomp.so runtime dep).
- KazeiaApplication.LLM_BACKEND config + runtime override via
  debug.kazeia.llm_backend (llamacpp|executorch). Default stays executorch
  for now; flip to llamacpp per-boot for the new path.
- KazeiaService wires the selected engine at pipeline load.

Validated on OPD2415: app loads Qwen3.5-4B-Q4_0 in ~4s via llama.cpp,
ChatActivity opens, pipeline STT→LLM→TTS ready. Phase 2 complete.
2026-04-23 08:07:07 +02:00
Kazeia Team db281002d9 scripts: export per-voice prefix/suffix embeddings
New tool + generated artefacts so the on-device voice spinner can now
hot-swap between all 8 voices — previously only Damien's prefix/suffix
were present in the model dir, and the tablet fell back to him
regardless of selection.

scripts/export_voice_prefix_suffix.py runs Qwen3TTS's voice-clone
path under a forward hook, captures the first prefill call's 1024-dim
talker input embeddings, aborts the rest of the (very slow on CPU)
decode via a sentinel exception, and slices out the first 9 vectors
as <name>_voice_prefix.bin and the last 2 as <name>_voice_suffix.bin.
Validated against the shipped damien_voice_prefix.bin: using
damien_15s_24k.wav as the reference audio, max|diff| = 0, so the
extraction matches the original tooling bit-for-bit.

Generated and adb-pushed to
/data/local/tmp/kazeia/models/qwen3-tts-npu/:
  amir / didier / elodie / jerome / richard / sid / zelda
  (+ re-generated damien from the canonical 15s_24k reference)

Qwen3TtsEngine.setVoice (already wired) reads <voice>_voice_prefix.bin
/ <voice>_voice_suffix.bin by basename, so voice changes now take
effect from the next synthesized segment with no app restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:09:23 +02:00