expria-backend/docs/Prompt_t1live.md
Hermann_Kitio 74770b6402
Some checks are pending
CI / quality (push) Waiting to run
fix(t1-live): remove questionnaire dependency from T1 Live session
- buildT1SystemPrompt() now static (no reponses param); examiner
  formulates questions from what it hears in real-time audio stream
- Remove context guard + close 4004 CONTEXT_MISSING; Gemini session
  opens immediately after auth (aligns with T2 flow)
- Remove parseT1Context, validateReponses import from route
- Unknown WS message types silently ignored (debug log + return)
- Update Prompt_t1live.md and CHANGELOG-backend
- Tests: 309/309 green
2026-06-30 02:57:17 +03:00

11 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 (écoute en temps réel) 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. 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)

Le prompt est statique : aucune variable substituée. L'examinateur formule ses relances à partir de ce qu'il entend en temps réel (son contexte audio interne) — il n'existe ni sujet T1 en base, ni questionnaire pré-rempli (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.

Écoute attentivement ce que le candidat dit. Quand on te le signale, formule UNE question de relance courte (10-20 mots) liée à ce que le candidat vient de dire.

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.
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).


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).

La session Gemini s'ouvre immédiatement après l'auth (pas de message de contexte ni de questionnaire). Le client envoie directement son audio. Tout message non reconnu (ni audio ni end) est ignoré silencieusement (log debug + return) — jamais de close.

4.1 Client → Backend

Message Forme Effet
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
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.