fix(t1-live): remove questionnaire dependency from T1 Live session
Some checks are pending
CI / quality (push) Waiting to run

- 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
This commit is contained in:
Hermann_Kitio 2026-06-30 02:57:17 +03:00
parent 01707c0b74
commit 74770b6402
6 changed files with 105 additions and 209 deletions

View file

@ -6,6 +6,28 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
---
## [Unreleased] — 2026-06-30 — Sprint 7a-patch — T1 Live : suppression de la dépendance au questionnaire
### Changed
- `buildT1SystemPrompt` (`geminiLiveT1.ts`) — prompt système T1 désormais **statique** (signature sans argument). La section « CONTEXTE DU CANDIDAT » (5 variables `${reponses.*}`) est retirée et remplacée par une consigne d'écoute : « É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. » Les 8 règles sont conservées (silence par défaut, relance sur signal, ton bienveillant, jamais d'évaluation/hors-rôle, règle 5 « DOIS poser des questions »). Règle 4 : retrait de « ou à son contexte ci-dessus ».
- `openGeminiLiveT1Session` (`geminiLiveT1.ts`) — option `reponses` retirée de `OpenGeminiLiveT1SessionOptions` et de la signature. Le handler de message client ignore désormais explicitement (log debug + return) tout message non reconnu (ni `audio` ni `end`) — un éventuel `{type:'context'}` d'un ancien front ne provoque ni crash ni close.
- `WS /t1/live` (`t1live.ts`) — la session Gemini s'ouvre **immédiatement après l'auth** (calque T2), dans `onOpen`. Le flux client devient `{type:'audio', data}` puis `{type:'end'}`.
- `docs/Prompt_t1live.md` — §1 (table « Subject-based »), §2 (règle 3), §3 (prompt statique, retrait des variables et de la section « Variables à substituer »), §4.1 (retrait du message de contexte), §4.3 (retrait du close 4004).
- Tests `geminiLiveT1.test.ts` / `t1live.test.ts` — appels `buildT1SystemPrompt()` sans argument, retrait de `reponses` des appels de session ; suppression du test d'intégration des réponses (remplacé par un test « écoute / plus de CONTEXTE DU CANDIDAT ») et du bloc `parseT1Context` ; tests interruption, flush terminal, timeout et correction inchangés.
### Removed
- Message client `{type:'context', reponses}` (1er message obligatoire) et close **4004 `CONTEXT_MISSING`** — la route ne lit plus de contexte.
- Fonction `parseT1Context` (`t1live.ts`) et ses imports `validateReponses` / `PresentationReponses`.
### Notes
- Tests backend : 309/309 verts. `tsc --noEmit` OK.
- **Suivi frontend (hors scope)** : le frontend Sprint 7b (non commité) envoie encore `{type:'context'}` et possède un `QuestionnaireT1Page` Live ; ce message est désormais inoffensif côté backend (ignoré). Le questionnaire Live devient sémantiquement obsolète — à retirer dans une session frontend dédiée.
---
## [Unreleased] — 2026-06-28 — Sprint 6d — T2 Live : durcissement prompt + VAD + cleanup SDK
### Changed

View file

@ -31,12 +31,12 @@ d'immigration au Canada) sous forme de **monologue**, et l'examinateur le
**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`) |
| 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`) |
---
@ -53,7 +53,7 @@ L'IA joue un **examinateur bienveillant** du TCF Canada. Son comportement :
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.
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.
@ -63,25 +63,21 @@ L'IA joue un **examinateur bienveillant** du TCF Canada. Son comportement :
## 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).
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.
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}
É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 ou à son contexte ci-dessus.
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.
@ -91,13 +87,6 @@ RÈGLES :
> **⚠ 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)
@ -106,13 +95,17 @@ 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 |
| ---------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
| 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). |
| 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
@ -127,15 +120,14 @@ Auth : JWT Supabase + permission Premium `oral_t2_live` (réutilise
### 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` |
| 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` |
---