expria-backend/docs/Prompt_t1live.md
Hermann_Kitio 3722e2aaf5 docs(t1-live): contrat WS T1 + dette technique (Sprint 7a)
- Prompt_t1live.md : spec prompt examinateur T1, note anti-TD-22 (regle 7 du T2 non propagee), contrat WS complet (messages + close codes) pour le frontend 7b, comportement de fin (activityEnd final + relance terminale coupee).
- TECH_DEBT-backend.md : TD-23 (non-determinisme Gemini Live T1 + decouverte flush inputTranscription a activityEnd en VAD manuel), TD-24 (dette nommage gate oral_t2_live couvrant aussi T1), TD-25 (dette DRY runT1LiveCorrection ~= runT2LiveCorrection, report conscient).
2026-06-29 22:10:15 +03:00

12 KiB

Prompt_t1live.md — Expria Backend

Spécification du prompt système T1 EO Live + contrat WebSocket

Document de référence — Sprint 7a À lire conjointement avec Prompt_t2live.md (symétrie / divergences T1↔T2) et TECH_DEBT-backend.md (TD-22, TD-23, TD-24, TD-25). Source de vérité du prompt : buildT1SystemPrompt dans src/lib/geminiLiveT1.ts.


⚠ NOTE LIMINAIRE — anti-TD-22 (symétrie avec Prompt_t2live.md §3)

La règle 7 du T2 (« STRICTE INTERDICTION DE POSER DES QUESTIONS / ban du point d'interrogation ») n'est PAS propagée au T1. En Tâche 1, l'examinateur DOIT relancer le candidat par des questions : c'est le cœur de son rôle. Le point d'interrogation est utilisé normalement.

Le prompt T1 (buildT1SystemPrompt) et le prompt T2 (buildT2SystemPrompt) vivent dans des fonctions distinctes de geminiLive.ts / geminiLiveT1.ts précisément pour éviter toute contamination de règle.


1. Contexte pédagogique

La Tâche 1 de l'Expression Orale TCF Canada est un entretien dirigé : le candidat se présente (identité, parcours, situation familiale, loisirs, projet d'immigration au Canada) sous forme de monologue, et l'examinateur le relance ponctuellement par des questions courtes pour approfondir.

Différence structurelle avec le T2 :

Axe T1 (entretien dirigé) T2 (interaction de service)
Qui mène L'examinateur relance Le candidat mène
Questions de l'IA Obligatoires (relances) Interdites (rôle passif)
Forme candidat Monologue + relances Dialogue
Subject-based Non (questionnaire candidat) Oui (table sujets)

2. Rôle de l'IA (examinateur)

L'IA joue un examinateur bienveillant du TCF Canada. Son comportement :

  1. Silencieux par défaut. Tant que le candidat parle, elle n'intervient jamais de sa propre initiative.
  2. Relance sur signal uniquement. Elle ne prend la parole que lorsque le backend le lui signale (injection clientContent). C'est le backend — via une horloge probabiliste — qui décide du TIMING ; l'examinateur, lui, formule librement une relance courte à partir de son contexte audio interne. Le backend ne lit PAS la transcription partielle pour décider (Modèle 1 acté — cf. ROADMAP / geminiLiveT1.ts).
  3. Relance courte et unique. Une seule question de 10 à 20 mots, liée à ce que le candidat vient de dire ou à son contexte. Jamais d'enchaînement.
  4. Ton bienveillant et professionnel, français B2-C1.
  5. N'évalue jamais le candidat, ne corrige pas ses erreurs, ne commente pas sa langue.
  6. Ne sort jamais du rôle, ne mentionne jamais être une IA.

3. Prompt système (source : buildT1SystemPrompt)

Les variables ${...} sont substituées dynamiquement depuis les réponses du questionnaire candidat (PresentationReponses) — il n'existe pas de sujet T1 en base (T1 EO n'est PAS subject-based).

RÔLE : Tu es un examinateur bienveillant de l'épreuve d'Expression Orale du TCF Canada (Tâche 1, entretien dirigé). Le candidat se présente en monologue : identité, parcours, situation familiale, loisirs, et projet d'immigration au Canada.

CONTEXTE DU CANDIDAT (pour formuler des relances pertinentes et personnalisées) :
- Identité : ${reponses.prenom_age_ville}
- Formation / métier : ${reponses.formation_metier}
- Situation familiale : ${reponses.situation_familiale}
- Loisirs : ${reponses.loisirs}
- Projet Canada : ${reponses.motivation_canada}

RÈGLES :
1. Tu parles TOUJOURS en français naturel et courant, niveau B2-C1, sur un ton bienveillant et professionnel.
2. Tu RESTES SILENCIEUX par défaut. Tant que le candidat parle, tu n'interviens JAMAIS de ta propre initiative.
3. Tu prends la parole UNIQUEMENT lorsqu'on te le signale, et alors UNIQUEMENT pour relancer le candidat par UNE question.
4. Ta relance est COURTE : une seule question de 10 à 20 mots, liée à ce que le candidat vient de dire ou à son contexte ci-dessus.
5. Tu PEUX et tu DOIS poser des questions : c'est le cœur de ton rôle d'examinateur en Tâche 1. Utilise le point d'interrogation normalement.
6. Une seule question à la fois. Jamais de liste, jamais d'enchaînement de plusieurs questions dans la même prise de parole.
7. Tu ne corriges JAMAIS les erreurs du candidat et tu ne commentes jamais sa langue, ses erreurs ou sa performance.
8. Tu restes toujours dans ton rôle d'examinateur. Tu ne mentionnes jamais que tu es une IA ou un modèle.

⚠ Spécificité T1 — règle 5 : elle est l'exact inverse de la règle 7 du T2. Toute fusion des deux prompts est interdite (TD-22 / TD-23).

Variables à substituer dynamiquement (depuis le questionnaire candidat, pas d'un sujet en base) :

  • prenom_age_ville, formation_metier, situation_familiale, loisirs, motivation_canada — validés par validateReponses (presentationController.ts).

4. Contrat WebSocket T1 (figé — la suite Sprint 7b en dépend)

Route : WS /t1/live?token=<jwt> Auth : JWT Supabase + permission Premium oral_t2_live (réutilise authenticate de t2live.ts — cf. dette de nommage TD-24).

4.1 Client → Backend

Message Forme Effet
Contexte (1er message obligatoire) {type:'context', reponses} Validé par validateReponses ; démarre la session Gemini. Absent/invalide → close 4004.
Audio candidat {type:'audio', data} (PCM 16 kHz base64) Relayé à Gemini tant qu'un tour candidat est ouvert et qu'aucune interruption n'est en cours.
Fin de session {type:'end'} Déclenche endSession() (flush terminal + correction).

4.2 Backend → Client

Message Forme Sens
Audio examinateur frames Gemini verbatim (PCM 24 kHz) Relances audio de l'examinateur.
Début d'interruption {type:'interruption_start'} L'examinateur prend la parole ; le front doit suspendre la capture candidat.
Fin d'interruption {type:'interruption_end'} Le candidat peut reprendre.
Avertissement temps {type:'warning', message} 30 s avant le timeout (T1_SESSION_WARNING_MS).
Rapport final {type:'report', data} + close 1000 Évaluation EO_T1 prête.
Erreur applicative {type:'error', code, message} Codes : EMPTY_TRANSCRIPT, PERSISTENCE_FAILED, CORRECTION_FAILED.

4.3 Codes de fermeture WebSocket

Close code Cause Origine
1000 Fin normale + rapport prêt (ou transcript vide) runT1LiveCorrection
1011 PERSISTENCE_FAILED / CORRECTION_FAILED runT1LiveCorrection
4001 AUTH_REQUIRED (JWT absent/invalide) authenticate
4003 PLAN_INSUFFICIENT (pas Premium) authenticate
4004 CONTEXT_MISSING (1er message contexte absent/invalide) route t1live.ts
4005 GEMINI_CONFIG (clé API Gemini manquante côté serveur) openGeminiLiveT1Session
4006 GEMINI_DISCONNECTED (WS Gemini fermé/erreur) openGeminiLiveT1Session

5. Comportement de fin de session (flush terminal)

Contrainte VAD manuel (découverte spike — TD-23). En VAD manuel (realtimeInputConfig.automaticActivityDetection.disabled = true), Gemini ne flushe inputTranscription (le texte candidat) qu'à l'envoi d'un activityEnd, pas en continu. Le backend doit donc envoyer un activityEnd FINAL aux bornes de tour pour récupérer le dernier segment candidat.

Effet de bord et son traitement. Cet activityEnd final déclenche AUSSI une relance examinateur « terminale » non désirée. Elle est coupée :

  • L'audio de cette relance terminale (modelTurn … inlineData) n'est pas forwardé au client (le candidat ne l'entend jamais).
  • Le texte de cette relance terminale (outputTranscription) est jeté (non ajouté au transcript).
  • Seul le texte candidat final (inputTranscription) est conservé pour l'évaluation.

Le tri se fait champ par champ (pas message par message), car le segment candidat à garder et la relance terminale à couper peuvent arriver dans le même message Gemini. Implémentation : flag terminalFlush + T1_TERMINAL_FLUSH_GRACE_MS (3 s) avant finalize() (cf. geminiLiveT1.ts).


6. Évaluation finale (pipeline post-session)

runT1LiveCorrection (src/routes/t1live.ts) :

  1. Insert productions : tache='EO_T1', sujet_id=null (T1 non subject-based), mode='entrainement', contenu=transcript.
  2. correctEO(transcript, 'EO_T1', 9, null) (DeepSeek — pas de consigne de sujet en T1).
  3. Phonologie = PHONOLOGY_STUB (TD-08 — pas d'audio brut côté backend) : score textuel /16 + phonologie /4 = /20.
  4. Update productions (rapport, score, nclc).
  5. {type:'report', data} + close 1000.

Rappel TD-08 : la phonologie live reste gelée (stub) tant qu'aucun audio brut n'est bufferisé côté backend.


7. Spécifications audio

Direction Format Sample rate Encoding
Frontend → Gemini PCM brut 16 kHz 16 bits, little-endian, mono
Gemini → Frontend PCM brut 24 kHz 16 bits, little-endian, mono

MIME envoyé à Gemini : audio/pcm;rate=16000 (T1_INPUT_AUDIO_MIME).


8. Constantes de session (source : geminiLiveT1.ts)

Constante Valeur Rôle
T1_SESSION_TIMEOUT_MS 180 000 Filet de sécurité (fin forcée).
T1_SESSION_WARNING_MS 150 000 Émet {type:'warning'} 30 s avant timeout.
T1_INTERRUPTION_P0/P1/P2 0.2 / 0.6 / 0.2 Distribution du nombre de relances (0/1/2).
T1_INTERRUPTION_WINDOW_START/END_MS 25 000 / 75 000 Fenêtre où placer les relances.
T1_INTERRUPTION_MIN_SPACING_MS 20 000 Espacement minimal entre 2 relances.
T1_TERMINAL_FLUSH_GRACE_MS 3 000 Délai après activityEnd final avant finalize.