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
198 lines
11 KiB
Markdown
198 lines
11 KiB
Markdown
# 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`. |
|