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).
This commit is contained in:
parent
868bd09397
commit
3722e2aaf5
2 changed files with 271 additions and 0 deletions
206
docs/Prompt_t1live.md
Normal file
206
docs/Prompt_t1live.md
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
# 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`. |
|
||||
Loading…
Add table
Add a link
Reference in a new issue