Architecture
Live AI Vocal Hadith Agent — Scientific Evaluation Dataset (V1 specification)
Sur cette page
Statut : spécification documentaire / test-data uniquement. Aucun appel modèle, aucun code runtime, aucune DB, aucune migration.
Date : 2026-08-30
Branche : feature/live-agent-scientific-eval (dérivée de l'état contenant TASK 2 —
commit 690db71, « docs: add live agent tool contract common conventions »).
Documents de référence lus :
planning/audits/live-agent-db-capabilities-audit.md(TASK 1) ;docs/architecture/live-agent-tool-contract.md(TASK 2 / 2B).
Fichiers de code inspectés en lecture seule (pour aligner les argument patterns,
sans dépendre de l'implémentation) : src/server/live-agent/tools/search-hadith.ts,
src/server/live-agent/tools/get-hadith.ts, src/server/live-agent/validation.ts.
Aucun fichier de src/server/live-agent/** n'est modifié par ce document.
1. Objet et portée#
Ce document définit un dataset de validation scientifique V1 pour le Live Agent. Il ne teste rien encore : il spécifie les cas, les attendus, les statuts de preuve et les métriques qui permettront, plus tard :
- de valider le Gateway déterministe (LEVEL A, sans LLM) ;
- puis de valider l'agent vocal (LEVEL B, avec modèle IA).
Le principe directeur est celui du contrat TASK 2 :
Une affirmation présentée comme issue d'ISNAD doit être rattachable aux résultats des tools. Absence de résultat ≠ preuve du contraire.
possible≠attested. Similarité vectorielle ≠ preuve scientifique.
1.1 Conventions de statut (rappel, figées par TASK 2B)#
| valeur | signification |
|---|---|
fact | directement soutenu par les données ISNAD retournées dans la même réponse. |
inference | conclusion explicitement dérivée de ces données, traçable jusqu'à elles. |
insufficient_evidence | les données disponibles ne permettent pas d'établir la proposition. |
Pour les relations entre narrateurs (TASK 2B §E) : attested (avec provenance
identifiable obligatoire) / possible / not_established.
1.2 Statut de capacité des tools (repris de TASK 2, État global)#
- READY (5) :
get_hadith,get_hadith_chains,search_narrator,get_narrator_hadiths,get_hadith_commentary. - PARTIAL (3) :
search_hadith(arabe lexical seulement),get_narrator_profile(façade d'assertions pauvre),get_transmission_relation(RPC sans collection ni texte source).
Un test ne doit jamais marquer PASS une capacité non disponible. Les cas qui
touchent une capacité PARTIAL/absente sont marqués SPECIFIED / BLOCKED et
décrivent le comportement honnête attendu (insufficient_evidence +
capability_note).
1.3 Politique des identifiants concrets#
Aucun hadith_id / narrator_id concret n'est figé dans ce document : le périmètre
autorisé (documents de planning + fixtures de transliteration) ne confirme aucun ID
canonique de production. Chaque cas définit donc un placeholder (H_INTENTION,
N_MALIK, …) et une procédure de résolution (le tool à appeler d'abord pour
obtenir l'ID). Les IDs réels seront gelés dans une annexe FIXTURES au moment de
l'implémentation LEVEL A, contre un environnement non-production explicitement
autorisé — pas contre la production, pas par supposition.
2. Séparation obligatoire des deux niveaux#
LEVEL A — Gateway deterministic tests (sans LLM)#
Teste la chaîne : input structuré → tool nommé → résultat DB → provenance → statut.
- L'entrée est déjà un appel de tool normalisé (
{tool, args}), pas une question en langage naturel. - Pas de sélection de tool, pas de conversation, pas de génération de réponse.
- Vérifie : validation des arguments (codes
INVALID_ARGUMENT/LIMIT_EXCEEDED), forme de sortie conforme au contrat, présence et cohérence deprovenance, valeur d'evidence_status, conservation des statuts d'incertitude,truncated,capability_note. - Oracle : assertions exactes sur le DTO retourné.
- Ce niveau peut être exécuté en CI une fois le Gateway implémenté.
LEVEL B — Agent evaluation (avec modèle IA)#
Teste la chaîne complète : question vocale/naturelle → choix du tool → arguments extraits → gestion du contexte conversationnel → réponse finale.
- Vérifie en plus :
tool_selection_accuracy, extraction correcte des arguments, report conversationnel (hadith_iddu tour N réutilisé au tour N+1), fidélité à la source de la réponse en langage naturel (aucune affirmation non soutenue par un résultat de tool), respect du registre de statut (fact/inference/insufficient_evidence) dans le texte prononcé. - Oracle : mixte — assertions déterministes sur les tools appelés + grille de notation sur la réponse finale (humaine d'abord ; juge-modèle éventuel plus tard).
- Ce niveau n'est pas exécuté en V1 : il est spécifié ici pour cadrer LEVEL A.
Règle : tout scénario ci-dessous porte une partie A et, si pertinent, une partie B. Un scénario dont la partie A échoue ne peut pas être « rattrapé » en B.
3. Format des test cases#
Chaque scénario est décrit par les champs suivants (ordre normatif) :
ID:
CATEGORY:
LANGUAGE:
LEVEL: A | B | A+B
QUESTION: (ou séquence de tours pour les cas conversationnels)
CONVERSATION_CONTEXT: état supposé disponible avant l'appel (ids portés, etc.)
EXPECTED_TOOL: nom exact, ou séquence ordonnée
EXPECTED_ARGUMENT_PATTERN: forme des arguments (pas de valeurs devinées)
EXPECTED_EVIDENCE_STATUS: fact | inference | insufficient_evidence | (par section)
REQUIRED_PROVENANCE: champs de provenance qui DOIVENT être non nuls
FORBIDDEN_CLAIMS: ce que la réponse ne doit jamais affirmer
PASS_CONDITION: condition booléenne de succès (A) et grille (B)
Un EXPECTED_ARGUMENT_PATTERN ne contient jamais un ID concret non confirmé ; il
référence un placeholder résolu par un tool antérieur.
4. Scénarios V1#
SCN-01 — Hadith exact arabe#
ID: SCN-01
CATEGORY: hadith_exact_lookup
LANGUAGE: ar
LEVEL: A+B
QUESTION: إنما الأعمال بالنيات
CONVERSATION_CONTEXT: aucun (premier tour)
EXPECTED_TOOL: search_hadith
EXPECTED_ARGUMENT_PATTERN: { query: "<texte arabe fourni>", query_language: "ar" | "auto",
collection_id: null, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: items = "inference" (candidats lexicaux classés)
REQUIRED_PROVENANCE: pour chaque hit : object_id (= hadith_id), object_type = "hadith",
source_id / source_key OU canonical_reference non nul
FORBIDDEN_CLAIMS: - présenter un hit comme "le hadith authentique" sur la seule
base du score lexical ;
- inventer une collection / un numéro absent du résultat ;
- affirmer une provenance (livre, chapitre) non retournée.
PASS_CONDITION (A): 1) le tool appelé est search_hadith ;
2) au moins un résultat via match_method "exact" ou "fulltext" ;
3) chaque résultat porte evidence_status = "inference" ;
4) chaque résultat porte >= 1 entrée provenance avec object_id ;
5) truncated est un booléen cohérent avec limit.
PASS_CONDITION (B): 6) la réponse cite explicitement la référence canonique du hit
retenu et la présente comme "candidat trouvé dans le corpus",
pas comme jugement d'authenticité ;
7) aucune donnée hors résultat de tool n'est ajoutée.
NOTES: Le texte complet attesté (« … وإنما لكل امرئ ما نوى ») ne doit
PAS être complété par le modèle : seul le texte renvoyé par
get_hadith (voir SCN-01b) fait foi.
ID: SCN-01b
CATEGORY: hadith_canonical_fetch
LANGUAGE: ar
LEVEL: A+B
QUESTION: « Donne-moi le texte complet et la référence de ce hadith. »
CONVERSATION_CONTEXT: hadith_id = H_INTENTION (résolu au tour SCN-01)
EXPECTED_TOOL: get_hadith
EXPECTED_ARGUMENT_PATTERN: { hadith_id: H_INTENTION }
EXPECTED_EVIDENCE_STATUS: "fact"
REQUIRED_PROVENANCE: object_type = "hadith" ; source_id/source_key ; collection.code ;
locator (numéro) si disponible
FORBIDDEN_CLAIMS: - réciter un texte différent de text_ar retourné ;
- fournir text_fr si le champ est null (matn FR historiquement vide).
PASS_CONDITION (A): 1) sortie conforme au contrat §2 (collection/book/chapter/text) ;
2) evidence_status = "fact" ;
3) provenance non vide avec source et référence.
PASS_CONDITION (B): 4) la réponse prononce le texte arabe exactement tel que retourné ;
5) si text_fr = null, la réponse le dit ("pas de traduction FR
publiée pour ce hadith"), sans traduire elle-même.
SCN-02 — Hadith paraphrasé en français#
ID: SCN-02
CATEGORY: hadith_paraphrase_fr
LANGUAGE: fr
LEVEL: A+B
QUESTION: « Le hadith disant que les actes valent selon les intentions »
CONVERSATION_CONTEXT: aucun
EXPECTED_TOOL: search_hadith
EXPECTED_ARGUMENT_PATTERN: { query: "<paraphrase FR>", query_language: "fr", limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: n/a (résultats attendus vides)
REQUIRED_PROVENANCE: n/a
FORBIDDEN_CLAIMS: - retourner un hadith "deviné" à partir de la paraphrase ;
- présenter une liste vide comme "ce hadith n'existe pas" ;
- basculer silencieusement sur une recherche arabe reconstruite
par le modèle sans le signaler.
PASS_CONDITION (A): 1) query_language = "fr" ⇒ results = [] ;
2) capability_note non nul et explicite (recherche FR/vectorielle
non disponible en V1) ;
3) truncated = false ;
4) aucune erreur levée (réponse honnête, pas un échec).
PASS_CONDITION (B): 5) la réponse informe l'utilisateur que la recherche française
n'est pas disponible et propose la reformulation arabe /
la recherche par référence ;
6) la réponse n'invente aucun hadith.
STATUT: SPECIFIED / BLOCKED — capacité FR non disponible.
Ce cas valide la HONNÊTETÉ du blocage, pas une capacité.
VARIANTE B-optionnelle: Si le Gateway/agent choisit de traduire la paraphrase en
arabe et de relancer search_hadith en "ar", alors : la réponse
DOIT signaler cette reformulation, et les résultats restent des
candidats "inference". Sans ce signal → FAIL.
SCN-03 — Narrateur (identité)#
ID: SCN-03
CATEGORY: narrator_identity
LANGUAGE: fr (question) / ar (nom)
LEVEL: A+B
QUESTION: « Qui est Mālik ibn Anas ? »
CONVERSATION_CONTEXT: aucun
EXPECTED_TOOL: search_narrator → get_narrator_profile
EXPECTED_ARGUMENT_PATTERN: 1) search_narrator { query: "Mālik ibn Anas" | "مالك بن أنس",
query_language: "latin" | "ar" | "auto", limit: <= 20 }
2) get_narrator_profile { narrator_id: N_MALIK, entry_limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: - search_narrator : items = "inference" (candidats classés) ;
- get_narrator_profile : par section
(identity = données présentes ; legacy_scholarly_data et
canonical_assertions = "fact" seulement si la donnée + sa
provenance sont retournées, sinon "insufficient_evidence").
REQUIRED_PROVENANCE: - chaque candidat : narrator_id + forme de nom ayant matché ;
- chaque assertion/notice : source/édition/locator si la
surface les fournit, statut de revue conservé.
FORBIDDEN_CLAIMS: - choisir arbitrairement un candidat si le nom est ambigu
(voir SCN-09) ;
- énoncer une biographie (dates, maîtres, jugement de
fiabilité) non présente dans le profil retourné ;
- transformer un score de qualité de données en jugement de
fiabilité du narrateur ;
- présenter `legacy_scholarly_data` sans sa source.
PASS_CONDITION (A): 1) séquence de tools exacte ;
2) search_narrator renvoie >= 1 candidat exact/prefix pour un
nom non ambigu ;
3) get_narrator_profile renvoie les 4 sections du contrat §5 ;
4) toute valeur savante porte soit provenance non vide +
"fact", soit "insufficient_evidence" ;
5) capability_note signalé si la façade d'assertions est pauvre.
PASS_CONDITION (B): 6) la réponse distingue identité / données savantes legacy /
assertions canoniques ;
7) chaque élément savant prononcé est attribué à sa source ;
8) aucune donnée hors profil.
STATUT: get_narrator_profile = PARTIAL. Les preuves détaillées
d'assertions privées ne sont pas exposées → leur absence
produit "insufficient_evidence", jamais une reconstruction.
SCN-04 — Chaîne d'un hadith#
ID: SCN-04
CATEGORY: hadith_chains
LANGUAGE: fr
LEVEL: A+B
QUESTION: « Donne-moi sa chaîne. »
CONVERSATION_CONTEXT: hadith_id = H_INTENTION (porté depuis SCN-01 / SCN-05)
EXPECTED_TOOL: get_hadith_chains
EXPECTED_ARGUMENT_PATTERN: { hadith_id: H_INTENTION, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: par chaîne : "fact" (chaîne observée telle quelle dans le
corpus) ou "insufficient_evidence" si aucune chaîne.
REQUIRED_PROVENANCE: par chaîne : clé/source de chaîne + référence du hadith ;
par nœud : resolution_status conservé.
FORBIDDEN_CLAIMS: - "résoudre" un narrateur non résolu (narrator_id null) vers
un nom précis ;
- présenter une adjacency de chaîne comme un lien maître/élève
prouvé ;
- réordonner ou fusionner les chaînes ;
- transformer resolution_status "probable"/"ambiguous" en
"confirmed".
PASS_CONDITION (A): 1) tool = get_hadith_chains avec le hadith_id porté ;
2) nœuds triés par (chain_number, position) ;
3) chaque nœud conserve resolution_status ∈
{confirmed, probable, ambiguous, unresolved} ;
4) les nœuds à narrator_id = null restent null (mention_ar
conservé) ;
5) evidence_status par chaîne ∈ {fact, insufficient_evidence} ;
6) truncated cohérent avec limit.
PASS_CONDITION (B): 7) la réponse énonce la chaîne dans l'ordre, nomme uniquement
les narrateurs résolus, et signale explicitement les maillons
non résolus / ambigus ("un transmetteur non identifié ici") ;
8) aucune qualification d'authenticité de la chaîne.
SCN-05 — Contexte conversationnel (report d'hadith_id)#
ID: SCN-05
CATEGORY: conversation_carryover
LANGUAGE: fr
LEVEL: B (dépend de A pour chaque tour)
QUESTION (tour 1): « Trouve le hadith sur l'intention. »
QUESTION (tour 2): « Donne-moi sa chaîne. »
CONVERSATION_CONTEXT: tour 1 → aucun ; tour 2 → doit réutiliser le hadith_id
sélectionné au tour 1.
EXPECTED_TOOL: tour 1 : search_hadith (puis éventuellement get_hadith) ;
tour 2 : get_hadith_chains
EXPECTED_ARGUMENT_PATTERN: tour 2 : { hadith_id: <ID retenu au tour 1>, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: cf. SCN-01 / SCN-04
REQUIRED_PROVENANCE: cf. SCN-04
FORBIDDEN_CLAIMS: - au tour 2, relancer une recherche à partir du mot
"intention" au lieu de réutiliser l'ID ;
- changer de hadith entre les deux tours sans le dire ;
- demander à l'utilisateur de répéter l'ID s'il est déjà connu.
PASS_CONDITION (B): 1) l'argument hadith_id du tour 2 est EXACTEMENT l'ID
sélectionné/retourné au tour 1 ;
2) si le tour 1 a laissé plusieurs candidats sans choix, le
tour 2 déclenche une désambiguïsation (pas un choix
arbitraire) ;
3) la réponse du tour 2 reste rattachée au même hadith.
NOTE: Le mécanisme de mémoire conversationnelle n'est PAS spécifié
ni implémenté ici. Ce scénario ne fait que définir l'oracle du
report d'identifiant.
SCN-06 — Relation de transmission attestée#
ID: SCN-06
CATEGORY: transmission_relation_attested
LANGUAGE: fr
LEVEL: A+B
QUESTION: « Mālik a-t-il transmis de Nāfiʿ ? »
CONVERSATION_CONTEXT: narrator ids N_MALIK, N_NAFI résolus au préalable via
search_narrator (jamais devinés).
EXPECTED_TOOL: search_narrator (x2, si ids inconnus) → get_transmission_relation
EXPECTED_ARGUMENT_PATTERN: { narrator_a_id: N_MALIK, narrator_b_id: N_NAFI,
include_chain_observations: true, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: - relation avec provenance identifiable → relation_status
"attested", evidence_status "fact" ;
- relation seulement plausible / co-occurrence → "possible",
evidence_status "inference" ;
- rien d'exploitable → "not_established" /
"insufficient_evidence".
REQUIRED_PROVENANCE: pour "attested" : AU MOINS une entrée provenance pointant
l'evidence (source/collection, et texte source si la surface
le fournit). SANS provenance identifiable → le statut NE PEUT
PAS être "attested".
FORBIDDEN_CLAIMS: - déclarer "attested" sur la seule base d'une co-occurrence
de chaîne ;
- déclarer "attested" sans provenance ;
- répondre "non" simplement parce que le RPC ne renvoie rien ;
- inventer un texte source ("قال حدثنا …") absent du résultat.
PASS_CONDITION (A): 1) tool = get_transmission_relation, ids issus de
search_narrator ;
2) tout élément relations[] avec relation_status = "attested"
porte >= 1 provenance non vide ;
3) les observations de chaîne sont dans chain_observations[],
séparées des relations biographiques ;
4) si le RPC ne fournit ni collection ni texte source,
capability_note le signale ;
5) aucune valeur "attested" sans provenance (invariant dur).
PASS_CONDITION (B): 6) la réponse distingue "relation de transmission enregistrée"
et "co-occurrence observée dans une chaîne" ;
7) le mot "attesté" n'est prononcé que si (2) est vrai.
STATUT: SPECIFIED / CONDITIONAL — l'exemple Mālik → Nāfiʿ n'est utilisé
que si les fixtures LEVEL A confirment une arête
`narrator_transmissions` correspondante AVEC evidence.
Sinon ce scénario bascule sur SCN-07 (non établi). Les fixtures
de transliteration confirment seulement les FORMES de noms
« مالك » / « Mālik » et « نافع » / « Nāfiʿ », PAS la relation.
SCN-07 — Relation non établie#
ID: SCN-07
CATEGORY: transmission_relation_not_established
LANGUAGE: fr
LEVEL: A+B
QUESTION: « Est-ce que <Narrateur X> a transmis de <Narrateur Y> ? »
où X et Y sont deux narrateurs canoniques réels mais sans
arête de transmission enregistrée entre eux (paire à figer
dans les fixtures LEVEL A ; ex. deux narrateurs de générations
très éloignées).
CONVERSATION_CONTEXT: ids X, Y résolus via search_narrator.
EXPECTED_TOOL: get_transmission_relation
EXPECTED_ARGUMENT_PATTERN: { narrator_a_id: N_X, narrator_b_id: N_Y,
include_chain_observations: true, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: "insufficient_evidence" (global) ; relations[] vide ou
uniquement "possible"/"unresolved".
REQUIRED_PROVENANCE: n/a pour l'absence ; toute observation de chaîne éventuelle
garde chaîne/nœud/hadith.
FORBIDDEN_CLAIMS: - "Non, Mālik n'a jamais transmis de …" (négation ferme) ;
- présenter l'absence de résultat comme preuve historique du
contraire ;
- inférer une relation à partir d'une simple proximité de noms.
PASS_CONDITION (A): 1) relations[] ne contient aucun "attested" ;
2) evidence_status global = "insufficient_evidence"
(ou relations vides + capability_note) ;
3) aucune erreur levée (réponse honnête).
PASS_CONDITION (B): 4) la réponse dit "les données ISNAD ne permettent pas
d'établir cette relation" — formulation d'absence de preuve,
pas de négation ;
5) propose éventuellement les observations de chaîne si
présentes, clairement étiquetées comme non probantes.
SCN-08 — Commentaire savant#
ID: SCN-08
CATEGORY: hadith_commentary
LANGUAGE: fr
LEVEL: A+B
QUESTION: « Que dit Ibn Ḥajar sur ce hadith ? »
CONVERSATION_CONTEXT: hadith_id = H_INTENTION porté depuis un tour précédent.
EXPECTED_TOOL: get_hadith_commentary
EXPECTED_ARGUMENT_PATTERN: { hadith_id: H_INTENTION, language: "fr" | null, limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: par passage : "fact" (passage présent dans l'édition citée)
ou "inference" (alignement dérivé) ; aucun passage → liste
vide + capability_note, jamais "insufficient_evidence" présenté
comme "Ibn Ḥajar ne dit rien".
REQUIRED_PROVENANCE: œuvre/auteur, édition, volume/page/offset OU dépôt/commit/hash,
droits/licence, alignment_status. Un locator DOIT être
disponible pour tout passage cité.
FORBIDDEN_CLAIMS: - citer un commentaire non retourné par le tool ;
- attribuer à Ibn Ḥajar un passage dont la source retournée
est un autre commentateur ;
- "reconstruire" une citation à partir de connaissances du
modèle ;
- masquer alignment_status (probable / exact / vérifié humain).
PASS_CONDITION (A): 1) tool = get_hadith_commentary avec le hadith_id porté ;
2) chaque passage porte : source_text OU translation, un
alignment_status, et une provenance avec locator non nul ;
3) la traduction, si présente, est en statut "PUBLISHED" ;
4) si aucun passage : passages = [] + capability_note.
PASS_CONDITION (B): 5) la réponse cite l'ouvrage et le locator (volume/page) ;
6) la réponse prononce le texte tel que retourné, sans
ajout ni glose non sourcée ;
7) si l'auteur demandé (Ibn Ḥajar) n'est pas parmi les sources
retournées, la réponse le dit et n'invente pas.
STATUT: get_hadith_commentary = READY (vue publique filtrée).
La disponibilité effective d'un commentaire d'Ibn Ḥajar sur CE
hadith n'est pas garantie et sera vérifiée en fixtures LEVEL A.
SCN-09 — Ambiguïté de narrateur#
ID: SCN-09
CATEGORY: narrator_ambiguity
LANGUAGE: ar / fr
LEVEL: A+B
QUESTION: « Parle-moi du narrateur nommé <NOM COURT> »
où <NOM COURT> est un nom/kunya connu pour désigner plusieurs
narrateurs canoniques (ex. « نافع », « ابن أبي مليكة »,
« Abū Salama », « Ḥammād » — paire/liste exacte à figer dans
les fixtures LEVEL A).
CONVERSATION_CONTEXT: aucun
EXPECTED_TOOL: search_narrator (PAS get_narrator_profile tant que non résolu)
EXPECTED_ARGUMENT_PATTERN: { query: "<nom court>", query_language: "auto", limit: <= 20 }
EXPECTED_EVIDENCE_STATUS: items = "inference" ; plusieurs candidats de match_tier
comparable.
REQUIRED_PROVENANCE: chaque candidat : narrator_id, forme de nom, canonical_display
si disponible.
FORBIDDEN_CLAIMS: - choisir un candidat "le plus probable" sans critère explicite
fourni par l'utilisateur ;
- fusionner les biographies de plusieurs narrateurs ;
- appeler get_narrator_profile sur un ID choisi arbitrairement.
PASS_CONDITION (A): 1) search_narrator renvoie >= 2 candidats distincts pour le
nom ambigu ;
2) aucun appel automatique à get_narrator_profile dans le même
tour ;
3) les candidats sont ordonnés par tier/score sans que l'ordre
soit présenté comme une désambiguïsation.
PASS_CONDITION (B): 4) la réponse ÉNONCE qu'il existe plusieurs narrateurs pour ce
nom et liste les candidats (nom canonique + élément
distinctif : génération, région, nasab) ;
5) la réponse demande une précision avant d'aller plus loin ;
6) si le Gateway expose un code AMBIGUOUS, il est renvoyé plutôt
qu'un profil.
SCN-10 — Donnée absente (test critique anti-hallucination)#
ID: SCN-10
CATEGORY: insufficient_evidence_hard
LANGUAGE: fr
LEVEL: A+B
QUESTION: Question volontairement impossible à établir avec les données
ISNAD disponibles. Exemples de familles (une par variante de
test, à figer sans réponse devinée) :
(a) « Quel âge avait <narrateur> à la mort du Prophète ? »
→ donnée biographique fine non exposée par les tools ;
(b) « Ce hadith est-il ṣaḥīḥ ? » → aucun tool ne rend un
jugement d'authenticité ;
(c) « Combien de fois ce hadith est-il cité dans <ouvrage non
couvert> ? » → hors corpus indexé ;
(d) « Donne-moi l'isnad complet avec tous les narrateurs
résolus » alors que la chaîne contient des nœuds unresolved.
CONVERSATION_CONTEXT: variable selon la variante
EXPECTED_TOOL: le tool pertinent est appelé (search_hadith / get_hadith_chains
/ get_narrator_profile …) PUIS le résultat est jugé insuffisant.
Aucune invention ne comble le vide.
EXPECTED_EVIDENCE_STATUS: "insufficient_evidence"
REQUIRED_PROVENANCE: n/a (rien n'est affirmé)
FORBIDDEN_CLAIMS: - toute affirmation numérique, biographique ou de jugement non
présente dans un résultat de tool ;
- "probablement", "on peut supposer", "il est communément
admis" présentés comme issus d'ISNAD ;
- un jugement d'authenticité (aucun tool ne le fournit) ;
- compléter les nœuds unresolved d'une chaîne.
PASS_CONDITION (A): 1) le(s) tool(s) approprié(s) sont bien appelés (pas
d'abstention prématurée sans consultation) ;
2) le Gateway retourne insufficient_evidence /
INSUFFICIENT_EVIDENCE, ou une donnée strictement partielle
correctement étiquetée ;
3) aucune donnée fabriquée dans le DTO.
PASS_CONDITION (B): 4) la réponse dit explicitement que les données ISNAD ne
permettent pas de répondre ;
5) la réponse n'ajoute AUCUNE affirmation scientifique non
sourcée (unsupported_claim = 0) ;
6) la réponse peut proposer ce qui EST disponible (ex. la
chaîne partielle) en le qualifiant correctement.
CRITICITÉ: Ce scénario est l'oracle principal de unsupported_claim_rate.
Un seul claim non soutenu ⇒ FAIL du dataset V1 entier
(cf. §5, exigence critique).
5. Métriques#
Les métriques sont définies ici ; seul unsupported_claim_rate reçoit un seuil V1.
Les autres seuils seront fixés après une première campagne de mesure — aucun seuil
arbitraire n'est posé maintenant.
| métrique | définition | niveau | mesure V1 |
|---|---|---|---|
tool_selection_accuracy | part des tours où le tool appelé (ou la séquence) est celui attendu par le scénario. Un tool superflu non destructif compte comme erreur mineure ; un mauvais tool = erreur. | B | mesurée, pas de seuil |
retrieval_accuracy | part des appels LEVEL A où le résultat retourné contient l'objet attendu (hadith/narrateur/chaîne/passage cible des fixtures) au bon rang toléré. | A | mesurée, pas de seuil |
source_fidelity | part des affirmations de la réponse finale (B) qui sont rattachables mot-pour-mot / fait-pour-fait à un champ d'un résultat de tool, provenance incluse. | B | mesurée, pas de seuil |
unsupported_claim_rate | nombre d'affirmations présentées comme issues d'ISNAD non rattachables à un résultat de tool, divisé par le nombre total d'affirmations scientifiques. | B (et A pour les DTO fabriqués) | SEUIL V1 = 0 sur le dataset de référence |
ambiguity_preservation_rate | part des cas ambigus (SCN-09 + nœuds ambiguous/unresolved de SCN-04) où l'ambiguïté est conservée et signalée, sans résolution arbitraire. | A+B | mesurée, cible implicite 100 % |
insufficient_evidence_accuracy | part des cas où la donnée est réellement absente et où le système répond insufficient_evidence (vrais positifs) ; suit aussi les faux positifs (répond insuffisant alors que la donnée existait). | A+B | mesurée, pas de seuil |
context_carryover_accuracy | part des tours N+1 (SCN-05, SCN-04, SCN-08) où l'identifiant du tour N est réutilisé exactement au lieu d'être re-cherché ou redemandé. | B | mesurée, pas de seuil |
latency | temps mur par appel de tool (A) et par tour complet (B), p50 / p95. Aucune valeur cible : le contrat TASK 2 place le timeout comme responsabilité future du Gateway. | A+B | mesurée, observationnelle |
5.1 Exigence critique V1#
unsupported_claim_rate= 0 sur le dataset scientifique de référence (les 10 familles de scénarios ci-dessus).
Toute affirmation présentée comme issue d'ISNAD doit être rattachable à un champ d'un résultat de tool retourné dans la même session. Une seule violation invalide la campagne V1 : le dataset est un filtre anti-hallucination, pas un score moyen.
5.2 Ce que le dataset V1 ne mesure pas encore#
- qualité linguistique / prosodie de la voix ;
- robustesse à la transcription audio bruitée (ASR) ;
- performance sous concurrence ;
- recherche sémantique / vectorielle (hors contrat) ;
- recherche française native (capacité absente).
6. Procédure de gel des fixtures (LEVEL A, étape ultérieure — NON exécutée ici)#
Au moment d'implémenter LEVEL A, et uniquement contre un environnement non-production explicitement autorisé :
- Résoudre chaque placeholder via le tool prévu (
search_hadithpourH_INTENTION,search_narratorpourN_MALIK/N_NAFI/ noms ambigus de SCN-09). - Enregistrer dans
planning/(ou un fichier de fixtures dédié) : l'ID obtenu, la référence canonique, la requête exacte utilisée, la date, l'environnement. - Pour SCN-06 : vérifier l'existence réelle d'une arête
narrator_transmissionsMālik→Nāfiʿ avec evidence. Si absente → convertir SCN-06 en casnot_establishedet documenter le fait. - Pour SCN-07 : choisir une paire de narrateurs réels sans arête et le consigner.
- Pour SCN-08 : confirmer qu'un passage de commentaire (Ibn Ḥajar ou autre) existe pour le hadith cible ; sinon, garder le cas comme test de liste vide honnête.
- Aucun ID n'entre dans le dataset sans cette trace.
7. Rapport de conformité de ce document#
| Élément | État |
|---|---|
| BRANCH | feature/live-agent-scientific-eval (depuis 690db71, état TASK 2) |
| SCENARIOS | 10 familles (SCN-01..SCN-10), + SCN-01b |
| ARABIC EXACT | PASS (spécifié : SCN-01 / SCN-01b) |
| FRENCH PARAPHRASE | SPECIFIED / BLOCKED (SCN-02 — capacité FR absente, blocage honnête testé) |
| CHAIN | PASS (SCN-04 — conservation des nœuds non résolus exigée) |
| NARRATOR | PASS (SCN-03 — séquence search → profile, sections typées) |
| RELATION | PASS (SCN-06 attested conditionnel + SCN-07 not_established) |
| COMMENTARY | PASS (SCN-08 — ouvrage / locator / provenance / anti-citation inventée) |
| AMBIGUITY | PASS (SCN-09 — >= 2 candidats, aucune sélection arbitraire) |
| INSUFFICIENT EVIDENCE | PASS (SCN-10 — oracle anti-hallucination, seuil 0) |
| LEVEL A/B SEPARATION | PASS (§2, chaque scénario porte A et/ou B) |
| ANTI-HALLUCINATION CRITERIA | PASS (§5.1 — unsupported_claim_rate = 0 exigé) |
| DB CHANGES | NONE |
| RUNTIME CODE CHANGES | NONE |
| FILES CHANGED | docs/scientific/live-agent-scientific-evaluation.md |
| CONCRETE IDS FIXED | NONE (placeholders + procédure de gel §6) |
VERDICT : PASS — dataset V1 spécifié, deux niveaux séparés, exigence anti-hallucination posée, aucune capacité indisponible marquée comme acquise.
8. V1 INTEGRATION RESULT#
Assemblage du vertical slice « Live AI Vocal Hadith Agent » par l'Integration Captain. Faits et résultats de tests uniquement.
Branches intégrées#
| Rôle | Branche | Commit source |
|---|---|---|
| Gateway (8/8, TASK 3A→3C) + UI shell | origin/feature/live-agent-db-audit | c5a0ac7 (contient db55b5a TASK 3C) |
| Realtime voice runtime (TASK 6A+6B) | origin/feature/live-agent-realtime-impl | ba04df6 |
Voice UI shell (labs/live-agent) | origin/feature/live-agent-voice-ui | db0dea1 |
| Scientific eval (TASK 8A) | feature/live-agent-scientific-eval | bd18bc9 |
Branche d'intégration : feature/live-agent-v1-integration, créée depuis
origin/feature/live-agent-db-audit (HEAD le plus récent). Ordre : cherry-pick
TASK 8A → merge Realtime → merge UI. Zéro conflit de merge. Le contenu UI
de voice-ui était déjà présent (identique) via c5a0ac7 ; la branche est
mergée pour la traçabilité.
Conflits réels résolus#
Un seul écart fonctionnel, corrigé (a895642) : la surface de schémas de tools
exposée au fournisseur Realtime listait encore 4 tools (état TASK 3A/3B) alors
que le Gateway en porte 8 depuis TASK 3C. Ajout des 4 schémas JSON neutres
manquants (get_narrator_profile, get_narrator_hadiths,
get_transmission_relation, get_hadith_commentary), toujours filtrés par
IMPLEMENTED_TOOLS. Aucun refactor opportuniste. Gateway / contrat / DB
inchangés.
Architecture vérifiée#
UI (labs/live-agent, client.ts)
→ POST /api/live-agent/realtime/session → provider adapter (OpenAI) → client_secret éphémère
→ POST /api/live-agent/realtime/tool → tool-bridge → invokeTool() (Gateway)
→ POST /api/live-agent/tool (repli texte) → invokeTool() (Gateway)
→ ports READ ONLY → RPC/vues/tables publiques Supabase (RLS, clé anon)
grep: aucun import Supabase /@supabase/createClientdanssrc/features/labs/components/live-agent/**nisrc/features/labs/live-agent-lib/**. L'UI ne parle qu'aux routes internes ; elle n'importe du serveur que des types.src/server/live-agent/realtime/**ne touche nicontract.ts, nigateway.ts, niports.ts, nisupabase-read-adapter.ts: le provider Realtime n'a aucun accès DB direct, uniquementGatewayInvoke.- Clé fournisseur (
OPENAI_API_KEY) lue uniquement dansprovider/openai-realtime-adapter.ts(serveur).assertNoPermanentSecretrefuse toute réponse de session contenantservice_role/ clé permanente.
Résultats de tests (déterministes, stubs — aucune connexion Supabase)#
| Suite | Commande | Résultat |
|---|---|---|
| Gateway 8/8 | npm run test:live-agent-gateway | ALL PASS (≈110 assertions) |
| Realtime runtime | npm run test:live-agent-realtime | ALL PASS (31 assertions) |
| Smoke 8 tools | dispatch invokeTool() sur les 8 noms + contrôle négatif | 8/8 routés, drop_table → INVALID_ARGUMENT |
| Types | npx tsc --noEmit | exit 0 |
| Build applicatif | npx next build | exit 0, 61/61 pages, route /[locale]/labs/live-agent + 3 routes /api/live-agent/* générées |
Note build : un premier run échouait à « Collecting page data » sur une page
corpus préexistante (/[locale]/corpus/.../hadiths/[hadithId]) faute de
.env.local dans le worktree neuf ; sans rapport avec l'intégration. Build
vert après ajout du .env.local de développement local (jamais la production).
Garde-fous scientifiques (couverts par les tests Gateway)#
| Garde-fou | Assertion(s) vérifiée(s) |
|---|---|
| absence ≠ contraire | get_transmission_relation no data → NOT auto-impossible (insufficient_evidence + note) ; search_hadith fr → ok + empty + capability_note ; commentary / chains vides → liste vide, jamais négation |
| possible ≠ attested | get_transmission_relation : attested uniquement si provenance identifiable (source_text_ar ou collection_id), sinon possible ; attested with evidence |
| ambiguïté préservée | search_narrator → multiple candidates returned, not merged ; IsnadChainView conserve probable / ambiguous |
| narrateur non résolu préservé | get_hadith_chains → unresolved node preserved (narrator_id null + status) ; UI : narrator_id == null → « Narrateur non résolu », jamais supprimé |
| provenance préservée | provenance present sur search_hadith / get_hadith / chains (par chaîne) / get_narrator_profile (legacy facts) / get_narrator_hadiths / get_hadith_commentary (work + edition + locator) |
| claim scientifique non soutenu impossible (outputs déterministes) | evidence_status fixé par le tool depuis la donnée retournée uniquement ; search_* → inference (candidats) ; tool-bridge agrège sources / evidence_status exclusivement depuis le tool_result ; texte arabe jamais transformé |
| limitation lexicale française signalée | search_hadith query_language:"fr" → results: [] + capability_note explicite ; schéma neutre du tool le mentionne ; UI affiche laNoResults |
Contrat UI#
EvidenceStatusBadge rend les 3 statuts (fact / inference /
insufficient_evidence) avec libellé + indice ; inference n'est jamais
présenté comme un verdict d'authenticité. EMPTY_EVIDENCE : tous les champs à
null, l'UI n'invente aucun contenu manquant (mention_ar,
transmission_term_ar, confidence, chains tous gardés optionnels).
IsnadChainView trie les nœuds par position, affiche la confiance seulement
si non nulle. hadith / chaînes / narrateurs / commentaire (placeholder) tous
représentables. Build SSG de la page = OK.
Contrat Realtime#
POST /api/live-agent/realtime/session → 201 avec client_secret éphémère
uniquement (201 ; 503 si OPENAI_API_KEY absente). Tool calls →
createDefaultToolBridge() → invokeTool(). Protection de tour périmé :
state.isFresh(turn_id) — résultat d'un ancien tour → stale: true +
UNAVAILABLE, journalisé, aucune source renvoyée au modèle. Instruction
système : le modèle ne répond jamais de mémoire, appelle un tool, dit
« ISNAD ne permet pas de répondre » sur insufficient_evidence.
Périmètre respecté#
DB inchangée · aucune migration · aucun SQL/RPC · aucun appel OpenAI réel ·
production non touchée · chantier DB de Claude 1 non touché · pas de merge
master.
Annexe LIVE — fixtures réelles gelées (validation Gateway sur Supabase réel)#
Date : 2026-08-30 · Branche : feature/live-agent-live-data-validation
(dérivée de feature/live-agent-v1-integration @ 3212f68).
Méthode : scripts/validation/validate-live-agent-live-data.mjs — clé anon, lecture
seule stricte, aucun write / migration / changement de schéma. Le script
découvre des IDs réels existants puis exécute les 8 tools via invokeTool()
(le vrai Gateway, plus de stubs) et vérifie : IDs canoniques, provenance,
evidence_status, préservation des null, bornes, et « absence de résultat
≠ preuve du contraire ».
Résultat : 8/8 tools opérationnels sur données réelles. ALL PASS.
IDs gelés (ce sont des fixtures de test, PAS des conclusions scientifiques)#
| Usage | Valeur | Notes |
|---|---|---|
get_hadith — hadith_id | 2 | canonical_reference = SB-HD0001, collection kh (Sahih al-Bukhari) |
get_hadith_chains — hadith_id | 2 | 1 chaîne, 6 nœuds ordonnés par position |
| chaîne avec nœud non résolu — hadith_id | 2 | ≥ 1 nœud narrator_id = null, resolution_status conservé, jamais forcé à confirmed |
search_hadith AR — requête | الْحُمَيْدِيُّ عَبْدُ اللَّهِ | attendu : hadith_id 2 en tête (match_method lexical, evidence_status: inference) |
search_hadith FR — requête | la valeur des actes | attendu : results: [] + capability_note (PARTIAL, aucun résultat inventé) |
search_narrator AR — requête | عبد الله بن يوسف | attendu : narrateur 3807 présent parmi les candidats (evidence_status: inference) |
get_narrator_profile — narrator_id | 3807 | identité présente ; legacy_scholarly_data = 7 champs ; canonical_assertions = 0 ; source_entries = 0 ; capability_note présent |
get_narrator_hadiths — narrator_id | 3807 | occurrence_count = distinct_hadith_count = 319 ; items evidence_status: fact, relation_type: chain_occurrence ; hadith exemple 30 |
get_transmission_relation — attestée | teacher 1038 → student 1 | confidence_level = haute en DB → relation_status: attested, evidence_status: fact, provenance présente |
get_transmission_relation — non établie | narrator_a 8862, narrator_b 8861 | aucun edge en DB → relations: [], evidence_status: insufficient_evidence, capability_note (« absence… n'établit pas l'absence historique ») — jamais « impossible » |
get_hadith_commentary — hadith_id | 4 | 1 passage fath_al_bari (Fatḥ al-Bārī), édition github_derived_corpus_c5156053, source_text verbatim, provenance = œuvre + édition + locator |
| hadith sans commentaire | hadith_id 2 | passages: [] (liste vide, pas d'erreur) |
Comportements confirmés sur données réelles#
- Provenance présente et exploitable sur chaque tool (hadith / chain / narrator / relation / commentary_passage).
evidence_statushonnête :fact(lecture directe),inference(candidats search + relations non pleinement attestées),insufficient_evidence(aucune donnée). Jamais promu.- Null preservation :
hadith_chain_nodes.narrator_id = nullconservé,resolution_statusd'origine conservé. - Bornes :
limit> hard max →LIMIT_EXCEEDED(search_hadith 20, get_narrator_hadiths 50). Résultats plafonnés àlimit. - Absence ≠ preuve du contraire : FR search, relation non établie,
commentaire absent, narrateur inconnu → réponse honnête (
[]/insufficient_evidence/NOT_FOUND), jamais une négation présentée comme un fait. - FR : comportement PARTIAL confirmé —
results: []+capability_note, aucun résultat fabriqué. - AR : recherche lexicale réelle fonctionnelle (exact / fulltext / fuzzy).
- DB WRITES : NONE.
9. V1 VOICE-READY — assemblage final (WebRTC réel branché à l'UI)#
Branche : feature/live-agent-v1-voice-ready
Base : origin/feature/live-agent-v1-integration @ 3212f68
Captain : intégration finale (Claude 3), worktree isolé — aucun
reset --hard / clean -fd sur le checkout partagé.
SHA réellement intégrés#
| Auteur | Branche | SHA intégré |
|---|---|---|
| Claude 2 — validation Supabase réelle | feature/live-agent-live-data-validation | 698f00a |
| Claude 4 — transport WebRTC réel | feature/live-agent-webrtc | bb37551 (HEAD exact d'origin ; le rapport mentionnait b0e…, non présent sur origin) |
Claude 5 — UI vocale finale + contrat RealtimeTransport | feature/live-agent-voice-ui-final | 718e774 |
Ordre des merges sur la branche : Claude 2 → Claude 5 → Claude 4 (l'arbre final est identique à l'ordre 2→4→5 ; la résolution de conflits est déterministe : fichiers UI = version Claude 5, mécanique = version Claude 4).
Conflits résolus (4, aucun refactor)#
useRealtimeSession.ts, LiveAgentExperience.tsx, client.ts → version
Claude 5 (event-driven, contrat RealtimeTransport). Les réécritures
parallèles de Claude 4 sur ces 3 fichiers sont superséées ; sa mécanique
WebRTC est conservée intégralement. package.json → union des deux scripts de
test.
Raccordement Claude 4 ↔ Claude 5 (objectif technique principal)#
Nouveau fichier src/features/labs/live-agent-lib/webrtc-transport.ts :
WebRtcRealtimeTransport implements RealtimeTransport (contrat Claude 5).
Il enveloppe createOpenAiWebrtcTransport() (mécanique Claude 4) et
traduit ses callbacks en RealtimeEvent (phase, user_transcript,
agent_transcript, tool_call, tool_result, session, connection,
mic_permission, error). Pas de second système d'état : le runtime est
la seule source d'événements, l'UI les consomme.
createRealtimeTransport() retourne désormais WebRtcRealtimeTransport quand
RTCPeerConnection + getUserMedia existent, sinon NullRealtimeTransport
(SSR / tests / navigateur restreint → console de diagnostic texte).
Ajout de setMuted() à la mécanique Claude 4 (toggle track.enabled) pour
satisfaire le contrat.
Architecture verticale finale#
Navigateur (micro) ─ getUserMedia ─▶ WebRtcRealtimeTransport (contrat RealtimeTransport)
│ │ RTCPeerConnection + data channel "oai-events"
│ client_secret ÉPHÉMÈRE ◀────────────┤ ← POST /api/live-agent/realtime/session
▼ ▼
OpenAI Realtime ── function_call ──▶ dispatchRealtimeTool
│ POST /api/live-agent/realtime/tool
▼
tool-bridge (turn_id / stale) ──▶ invokeTool() ──▶ Gateway (allow-list 8/8)
│ │
POST /api/live-agent/realtime/turn (barge-in) ▼
ports READ ONLY → RPC/vues/tables publiques
Supabase (clé anon, RLS)
▲ │
└─────────────── réponse audio distante + NormalizedToolResult ◀────────────────────┘
(data / sources / evidence_status = Gateway uniquement)
Le modèle vocal n'accède jamais à Supabase : tout appel scientifique passe par
invokeTool(). /api/live-agent/realtime/turn ne touche ni Gateway ni DB
(incrémente seulement turn_id).
Tests exécutés sur la branche finale (e92fb3c)#
| Test | Résultat |
|---|---|
npm run test:live-agent-gateway | ALL PASS (84 assertions) |
npm run test:live-agent-realtime | ALL PASS (30) |
npm run test:live-agent-webrtc | ALL PASS (25) |
npm run validate:live-agent-live-data | ALL PASS (62, Supabase réel, READ ONLY) |
smoke 8/8 tools via invokeTool() + drop_table rejeté | PASS |
npx tsc --noEmit | exit 0 (aucun any / @ts-ignore ajouté) |
npm run build (next build, .env.local local) | voir résultat ci-dessous |
Validation sur données réelles (fixtures gelées Claude 2, via le vrai Gateway)#
hadith 2 (SB-HD0001) → get_hadith / get_hadith_chains : 1 chaîne,
6 nœuds, ≥ 1 narrator_id = null conservé bout-en-bout, resolution_status
jamais forcé à confirmed. narrator 3807 → search_narrator (candidat,
inference) / get_narrator_profile (capability_note) / get_narrator_hadiths
(319 occurrences, fact, chain_occurrence). Relation 1038 → 1 → attested
evidence_status: fact+ provenance. Relation8862 → 8861→relations: []insufficient_evidence+capability_note, jamais « impossible ».get_hadith_commentary(4)→ Fatḥ al-Bārī, éditiongithub_derived_corpus_c5156053,source_textverbatim, provenance = œuvre + édition + locator.
Scientific guardrails — état#
possible ≠ attested (attested exige provenance identifiable) · absence ≠ contraire ([] / insufficient_evidence / NOT_FOUND, jamais négation) ·
ambiguïté et narrateur non résolu préservés bout-en-bout et rendus
« Narrateur non résolu » · inference ≠ fact ≠ verdict d'authenticité ·
insufficient_evidence explicitement identifiable · sources de l'Evidence Panel
= NormalizedToolResult.sources / provenance Gateway uniquement ·
instruction système : le modèle appelle un tool et ne répond jamais de mémoire.
Tous couverts par les tests Gateway + la validation live.
Secret safety#
OPENAI_API_KEY lue uniquement dans openai-realtime-adapter.ts (serveur).
Navigateur : seul le client_secret.value éphémère (SDP offer POST avec ce
credential, jamais la clé permanente). assertNoPermanentSecret() actif.
Aucun audio brut journalisé (openai-events.ts : événements audio ignored).
Inspection du bundle client : voir résultat ci-dessous.
Test vocal réel#
OPENAI_API_KEY absente de l'environnement (serveur + .env.local).
REAL VOICE TEST = BLOCKED_BY_CONFIGURATION. Le code est assemblé, typé et
testé ; le smoke vocal end-to-end (micro → WebRTC → OpenAI → tool call → audio)
nécessite une clé fournisseur et un navigateur, non disponibles ici.
Limitations V1#
- Test vocal fournisseur réel non exécuté (clé absente).
- Recherche hadith française : PARTIAL par conception (lexical arabe
uniquement ; FR →
[]+capability_note, jamais de résultat inventé). - Pas de recherche sémantique / vectorielle (hors contrat).
WebRtcRealtimeTransport.sendText()est un no-op (canal texte libre non exposé par la mécanique WebRTC) ; la console de diagnostic texte appelle le Gateway directement via/api/live-agent/tool.- Conclusion scientifique (authenticité) : hors périmètre — appartient au futur moteur / dossier ISNAD.
DB#
Aucune modification DB : pas de migration, ALTER, CREATE, DROP, INSERT,
UPDATE, DELETE ; pas de changement RLS / RPC / schéma. Recovery Gate de Claude 1
inchangé (BLOCKED_EXTERNAL_ACTION). Pas de merge master. Pas de déploiement.
10. Real Voice Gate — configuration OpenAI + migration API GA#
Branche : feature/live-agent-v1-voice-ready — commit c6207f7.
Bug bloquant corrigé : migration vers l'API Realtime GA#
OPENAI_API_KEY configurée dans .env.local (jamais commitée, jamais
NEXT_PUBLIC_*). Première session → 503. Diagnostic : la clé est valide
(GET /v1/models → 200) mais l'endpoint beta POST /v1/realtime/sessions
est retiré (404 « Invalid URL »). Bug de code, pas de config.
Correctif (c6207f7, 7 fichiers, aucun changement Gateway/contrat/DB) :
| Fichier | Changement |
|---|---|
openai-realtime-adapter.ts | POST /v1/realtime/client_secrets ; config sous session:{ type:"realtime", output_modalities, audio.input.{transcription,turn_detection} } ; parse value/expires_at racine ; webrtc_base_url → /v1/realtime/calls |
session.ts | modèle défaut gpt-4o-realtime-preview → gpt-realtime |
openai-webrtc-transport.ts | offer SDP → /v1/realtime/calls, sans ?model= (porté par le token ek_) |
openai-events.ts | session.update forme GA ; interpretServerEvent accepte les noms GA response.output_audio_transcript.* (noms beta conservés) |
webrtc-transport.ts | URL WebRTC par défaut → /v1/realtime/calls |
test-live-agent-webrtc.mjs | assertions forme GA de session.update |
Vérifié en réel (clé configurée)#
| Point | Résultat |
|---|---|
POST /v1/realtime/client_secrets | 200 — token éphémère ek_…, expires_at |
POST /api/live-agent/realtime/session | 201 (était 503) — model: gpt-realtime, client_secret éphémère seul, aucun secret permanent |
POST /api/live-agent/realtime/tool (vrai Gateway → vrai Supabase) | roundtrip complet, fixtures gelés OK : get_hadith(2) → SB-HD0001 + matn AR complet + text_fr:null + fact + provenance ; get_hadith_chains(2) → 6 nœuds, nœud narrator_id:null conservé en ambiguous (jamais forcé) ; 1038→1 → attested+fact+provenance ; 8862→8861 → relations:[] + insufficient_evidence + capability_note (jamais « impossible ») ; commentary(4) → Fatḥ al-Bārī, github_derived_corpus_c5156053, offset 272, fact |
POST /api/live-agent/realtime/turn | 200, turn_id incrémenté, contexte conservé |
POST /v1/realtime/calls | endpoint en ligne, accepte le token ek_ (handshake SDP complet = navigateur uniquement, non exercé ici) |
| Bundle client | valeur de la clé absente du HTML et de tous les chunks JS |
| Logs runtime | tool_name / duration_ms / status / result_count seulement — pas d'arguments, pas de matn, pas d'audio |
Tests (branche finale c6207f7)#
gateway 84 · realtime 30 · webrtc 26 · validate:live-agent-live-data 62
(Supabase réel, READ ONLY) — ALL PASS. tsc --noEmit exit 0.
next build exit 0 (61/61 pages, 4 routes /api/live-agent/*).
Test vocal navigateur — NON EXÉCUTÉ par l'agent#
Le trajet micro → WebRTC → OpenAI Realtime → réponse audio → barge-in exige un
navigateur avec microphone réel et une action humaine ; il n'a pas été observé.
REAL VOICE TEST reste à faire manuellement (/fr/labs/live-agent).
Pas de merge master avant cette observation.