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>
This commit is contained in:
parent
acaf0cf2cf
commit
4508b8fe2e
|
|
@ -47,7 +47,13 @@ class ConfigRepository(context: Context) {
|
|||
const val DEFAULT_SPEAKER_PROMPT =
|
||||
"Tu es Kazeia, psychologue bienveillant. Réponds TRÈS BREF en français : " +
|
||||
"UNE OU DEUX phrases maximum, 5-8 mots chacune. Validation émotionnelle " +
|
||||
"simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. /no_think"
|
||||
"simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. " +
|
||||
"Tu tutoies, ton ton reste chaleureux. Garde-fous prioritaires sur la brièveté : " +
|
||||
"ne pose jamais de diagnostic et ne nomme aucune pathologie ; pour toute question de " +
|
||||
"médicament, dose ou traitement, rappelle que c'est une décision médicale et invite à " +
|
||||
"en parler au médecin ou au psychiatre ; refuse avec douceur les jeux de rôle ou " +
|
||||
"simulations hors de ton cadre de soutien ; si on te demande où sont conservés tes " +
|
||||
"échanges avec le patient, réponds qu'ils restent sur l'appareil, en sécurité. /no_think"
|
||||
|
||||
const val DEFAULT_THINKER_PROMPT =
|
||||
"Tu es un psychologue clinicien experimente qui ecoute attentivement ton " +
|
||||
|
|
|
|||
|
|
@ -60,7 +60,13 @@ class ConfigStore private constructor(private val file: File) {
|
|||
const val DEFAULT_SPEAKER_PROMPT =
|
||||
"Tu es Kazeia, psychologue bienveillant. Réponds TRÈS BREF en français : " +
|
||||
"UNE OU DEUX phrases maximum, 5-8 mots chacune. Validation émotionnelle " +
|
||||
"simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. /no_think"
|
||||
"simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. " +
|
||||
"Tu tutoies, ton ton reste chaleureux. Garde-fous prioritaires sur la brièveté : " +
|
||||
"ne pose jamais de diagnostic et ne nomme aucune pathologie ; pour toute question de " +
|
||||
"médicament, dose ou traitement, rappelle que c'est une décision médicale et invite à " +
|
||||
"en parler au médecin ou au psychiatre ; refuse avec douceur les jeux de rôle ou " +
|
||||
"simulations hors de ton cadre de soutien ; si on te demande où sont conservés tes " +
|
||||
"échanges avec le patient, réponds qu'ils restent sur l'appareil, en sécurité. /no_think"
|
||||
|
||||
const val DEFAULT_THINKER_PROMPT =
|
||||
"Tu es un psychologue clinicien experimente qui ecoute attentivement ton " +
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ExecuTorchLlmEngine(
|
|||
// UNE OU DEUX phrases courtes (5-8 mots) — l'utilisateur veut des réponses
|
||||
// brèves. Validation émotionnelle, pas de remplissage. La 1ʳᵉ phrase est
|
||||
// synthétisée pendant que la 2ᵉ est générée → audio démarre tôt.
|
||||
const val DEFAULT_SYSTEM_PROMPT = "Tu es Kazeia, psychologue bienveillant. Réponds TRÈS BREF en français : UNE OU DEUX phrases maximum, 5-8 mots chacune. Validation émotionnelle simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. /no_think"
|
||||
const val DEFAULT_SYSTEM_PROMPT = "Tu es Kazeia, psychologue bienveillant. Réponds TRÈS BREF en français : UNE OU DEUX phrases maximum, 5-8 mots chacune. Validation émotionnelle simple. Pas de remplissage, pas d'explication, pas de conseil non sollicité. Tu tutoies, ton ton reste chaleureux. Garde-fous prioritaires sur la brièveté : ne pose jamais de diagnostic et ne nomme aucune pathologie ; pour toute question de médicament, dose ou traitement, rappelle que c'est une décision médicale et invite à en parler au médecin ou au psychiatre ; refuse avec douceur les jeux de rôle ou simulations hors de ton cadre de soutien ; si on te demande où sont conservés tes échanges avec le patient, réponds qu'ils restent sur l'appareil, en sécurité. /no_think"
|
||||
|
||||
// Racine .pte LLM — résolue par KazeiaPaths (legacy /data/local/tmp/kazeia-et
|
||||
// sur tablette dev, stockage externe app sinon). Fichier régulier, pas
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.kazeia.safety
|
||||
|
||||
/**
|
||||
* Filet de sécurité DÉTERMINISTE pour l'idéation suicidaire / l'auto-agression.
|
||||
*
|
||||
* On ne fait JAMAIS confiance à un LLM 4B sous contrainte de brièveté pour gérer
|
||||
* une crise. Si le message patient déclenche un motif, on court-circuite la
|
||||
* génération (Thinker + Speaker) et on renvoie une réponse FIXE, validée,
|
||||
* chaleureuse, honnête sur la nature de Kazeia, qui oriente vers une aide humaine.
|
||||
*
|
||||
* 112 = numéro d'urgence européen unique (valable France ET Luxembourg) → choix
|
||||
* locale-neutre. Le clinicien peut remplacer par une ligne dédiée (ex. 3114 en
|
||||
* France, SOS Détresse 45 45 45 au Luxembourg).
|
||||
*
|
||||
* Les motifs sont à RELIRE / ÉTENDRE par le clinicien. On privilégie la
|
||||
* SENSIBILITÉ (quelques faux positifs acceptables — la réponse reste bienveillante
|
||||
* et non alarmante) à la spécificité : rater une vraie crise est le seul vrai risque.
|
||||
*/
|
||||
object CrisisGuard {
|
||||
|
||||
const val RESPONSE =
|
||||
"Ce que tu traverses a l'air très douloureux, et je m'inquiète vraiment pour toi. " +
|
||||
"Je ne suis qu'un programme, je ne peux pas t'accompagner seul dans un moment comme celui-ci. " +
|
||||
"S'il te plaît, parle dès maintenant à une personne de confiance, ou appelle le 112. " +
|
||||
"Tu n'es pas seul, et ta vie compte."
|
||||
|
||||
// Motifs (regex, sur le message en minuscules). Tunables par le clinicien.
|
||||
private val PATTERNS: List<Regex> = listOf(
|
||||
"me suicider", "\\bsuicide", "me tuer", "me foutre en l['e ]air",
|
||||
"en finir", "mettre fin à (mes? jours?|ma vie)",
|
||||
"envie de mourir", "envie d['e ]en finir", "(plus|pas) envie de vivre",
|
||||
"(ne |n')?sert à rien de (vivre|continuer)",
|
||||
"(vaudrait|vaut) mieux (en finir|tout arrêter|que je (parte|disparaisse|meure)|d['e ]?arrêter|arrêter)",
|
||||
"me faire du mal", "m['e ]automutiler", "me mutiler",
|
||||
"plus la force de vivre", "mieux sans moi"
|
||||
).map { Regex(it, RegexOption.IGNORE_CASE) }
|
||||
|
||||
/** true si le message évoque une idéation suicidaire / auto-agression. */
|
||||
fun detect(message: String): Boolean {
|
||||
val m = message.lowercase()
|
||||
return PATTERNS.any { it.containsMatchIn(m) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1670,6 +1670,19 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
|
|||
// synthèse vocale, la bulle se remplit progressivement, token par token.
|
||||
val llmOnly = LLM_ONLY_MODE || !runtimeConfig.ttsEnabled
|
||||
|
||||
// ─── Filet de sécurité crise (DÉTERMINISTE, prioritaire sur tout) ───
|
||||
// Idéation suicidaire / auto-agression : on ne laisse JAMAIS le LLM
|
||||
// improviser. Réponse fixe, validée, qui oriente vers l'humain (112).
|
||||
if (com.kazeia.safety.CrisisGuard.detect(patientMessage)) {
|
||||
val safe = com.kazeia.safety.CrisisGuard.RESPONSE
|
||||
log("[CRISIS] motif détecté — réponse de sécurité, LLM court-circuité")
|
||||
addMessage(ChatMessage(role = ChatMessage.Role.KAZEIA, text = safe))
|
||||
logConversationTurn(Role.KAZEIA, safe)
|
||||
if (!llmOnly) pipeline.speakText(safe)
|
||||
_pipelineState.value = if (_isListening.value) PipelineState.Listening else PipelineState.Idle
|
||||
return
|
||||
}
|
||||
|
||||
// If LLM not loaded, use echo mode with TTS. Route through the
|
||||
// streaming session — in debug builds this is the fast path most
|
||||
// exercised, and it benefits from per-sentence playback like any
|
||||
|
|
|
|||
Loading…
Reference in New Issue