ISNAD Scientific Platform

Documentation Center
V1 · Référence officielle

Architecture

Live AI Vocal Hadith Agent — Scientific Evaluation Dataset (V1 specification)

19 min de lectureMis à jour le 29 أكتوبر 2026
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 :

  1. de valider le Gateway déterministe (LEVEL A, sans LLM) ;
  2. 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)#

valeursignification
factdirectement soutenu par les données ISNAD retournées dans la même réponse.
inferenceconclusion explicitement dérivée de ces données, traçable jusqu'à elles.
insufficient_evidenceles 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 de provenance, 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_id du 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étriquedéfinitionniveaumesure V1
tool_selection_accuracypart 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.Bmesurée, pas de seuil
retrieval_accuracypart 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é.Amesurée, pas de seuil
source_fidelitypart 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.Bmesurée, pas de seuil
unsupported_claim_ratenombre 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_ratepart 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+Bmesurée, cible implicite 100 %
insufficient_evidence_accuracypart 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+Bmesurée, pas de seuil
context_carryover_accuracypart 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é.Bmesurée, pas de seuil
latencytemps 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+Bmesuré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é :

  1. Résoudre chaque placeholder via le tool prévu (search_hadith pour H_INTENTION, search_narrator pour N_MALIK / N_NAFI / noms ambigus de SCN-09).
  2. 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.
  3. Pour SCN-06 : vérifier l'existence réelle d'une arête narrator_transmissions Mālik→Nāfiʿ avec evidence. Si absente → convertir SCN-06 en cas not_established et documenter le fait.
  4. Pour SCN-07 : choisir une paire de narrateurs réels sans arête et le consigner.
  5. 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.
  6. Aucun ID n'entre dans le dataset sans cette trace.

7. Rapport de conformité de ce document#

ÉlémentÉtat
BRANCHfeature/live-agent-scientific-eval (depuis 690db71, état TASK 2)
SCENARIOS10 familles (SCN-01..SCN-10), + SCN-01b
ARABIC EXACTPASS (spécifié : SCN-01 / SCN-01b)
FRENCH PARAPHRASESPECIFIED / BLOCKED (SCN-02 — capacité FR absente, blocage honnête testé)
CHAINPASS (SCN-04 — conservation des nœuds non résolus exigée)
NARRATORPASS (SCN-03 — séquence search → profile, sections typées)
RELATIONPASS (SCN-06 attested conditionnel + SCN-07 not_established)
COMMENTARYPASS (SCN-08 — ouvrage / locator / provenance / anti-citation inventée)
AMBIGUITYPASS (SCN-09 — >= 2 candidats, aucune sélection arbitraire)
INSUFFICIENT EVIDENCEPASS (SCN-10 — oracle anti-hallucination, seuil 0)
LEVEL A/B SEPARATIONPASS (§2, chaque scénario porte A et/ou B)
ANTI-HALLUCINATION CRITERIAPASS (§5.1 — unsupported_claim_rate = 0 exigé)
DB CHANGESNONE
RUNTIME CODE CHANGESNONE
FILES CHANGEDdocs/scientific/live-agent-scientific-evaluation.md
CONCRETE IDS FIXEDNONE (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ôleBrancheCommit source
Gateway (8/8, TASK 3A→3C) + UI shellorigin/feature/live-agent-db-auditc5a0ac7 (contient db55b5a TASK 3C)
Realtime voice runtime (TASK 6A+6B)origin/feature/live-agent-realtime-implba04df6
Voice UI shell (labs/live-agent)origin/feature/live-agent-voice-uidb0dea1
Scientific eval (TASK 8A)feature/live-agent-scientific-evalbd18bc9

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 / createClient dans src/features/labs/components/live-agent/** ni src/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 ni contract.ts, ni gateway.ts, ni ports.ts, ni supabase-read-adapter.ts : le provider Realtime n'a aucun accès DB direct, uniquement GatewayInvoke.
  • Clé fournisseur (OPENAI_API_KEY) lue uniquement dans provider/openai-realtime-adapter.ts (serveur). assertNoPermanentSecret refuse toute réponse de session contenant service_role / clé permanente.

Résultats de tests (déterministes, stubs — aucune connexion Supabase)#

SuiteCommandeRésultat
Gateway 8/8npm run test:live-agent-gatewayALL PASS (≈110 assertions)
Realtime runtimenpm run test:live-agent-realtimeALL PASS (31 assertions)
Smoke 8 toolsdispatch invokeTool() sur les 8 noms + contrôle négatif8/8 routés, drop_table → INVALID_ARGUMENT
Typesnpx tsc --noEmitexit 0
Build applicatifnpx next buildexit 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-fouAssertion(s) vérifiée(s)
absence ≠ contraireget_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 ≠ attestedget_transmission_relation : attested uniquement si provenance identifiable (source_text_ar ou collection_id), sinon possible ; attested with evidence
ambiguïté préservéesearch_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éeprovenance 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éesearch_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)#

UsageValeurNotes
get_hadith — hadith_id2canonical_reference = SB-HD0001, collection kh (Sahih al-Bukhari)
get_hadith_chains — hadith_id21 chaîne, 6 nœuds ordonnés par position
chaîne avec nœud non résolu — hadith_id2≥ 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êtela valeur des actesattendu : 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_id3807identité présente ; legacy_scholarly_data = 7 champs ; canonical_assertions = 0 ; source_entries = 0 ; capability_note présent
get_narrator_hadiths — narrator_id3807occurrence_count = distinct_hadith_count = 319 ; items evidence_status: fact, relation_type: chain_occurrence ; hadith exemple 30
get_transmission_relation — attestéeteacher 1038 → student 1confidence_level = haute en DB → relation_status: attested, evidence_status: fact, provenance présente
get_transmission_relation — non établienarrator_a 8862, narrator_b 8861aucun edge en DB → relations: [], evidence_status: insufficient_evidence, capability_note (« absence… n'établit pas l'absence historique ») — jamais « impossible »
get_hadith_commentary — hadith_id41 passage fath_al_bari (Fatḥ al-Bārī), édition github_derived_corpus_c5156053, source_text verbatim, provenance = œuvre + édition + locator
hadith sans commentairehadith_id 2passages: [] (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_status honnê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 = null conservé, resolution_status d'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#

AuteurBrancheSHA intégré
Claude 2 — validation Supabase réellefeature/live-agent-live-data-validation698f00a
Claude 4 — transport WebRTC réelfeature/live-agent-webrtcbb37551 (HEAD exact d'origin ; le rapport mentionnait b0e…, non présent sur origin)
Claude 5 — UI vocale finale + contrat RealtimeTransportfeature/live-agent-voice-ui-final718e774

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

TestRésultat
npm run test:live-agent-gatewayALL PASS (84 assertions)
npm run test:live-agent-realtimeALL PASS (30)
npm run test:live-agent-webrtcALL PASS (25)
npm run validate:live-agent-live-dataALL PASS (62, Supabase réel, READ ONLY)
smoke 8/8 tools via invokeTool() + drop_table rejetéPASS
npx tsc --noEmitexit 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. Relation 8862 → 8861 → relations: []
  • insufficient_evidence + capability_note, jamais « impossible ». get_hadith_commentary(4) → Fatḥ al-Bārī, édition github_derived_corpus_c5156053, source_text verbatim, 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) :

FichierChangement
openai-realtime-adapter.tsPOST /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.tsmodèle défaut gpt-4o-realtime-preview → gpt-realtime
openai-webrtc-transport.tsoffer SDP → /v1/realtime/calls, sans ?model= (porté par le token ek_)
openai-events.tssession.update forme GA ; interpretServerEvent accepte les noms GA response.output_audio_transcript.* (noms beta conservés)
webrtc-transport.tsURL WebRTC par défaut → /v1/realtime/calls
test-live-agent-webrtc.mjsassertions forme GA de session.update

Vérifié en réel (clé configurée)#

PointRésultat
POST /v1/realtime/client_secrets200 — token éphémère ek_…, expires_at
POST /api/live-agent/realtime/session201 (é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/turn200, turn_id incrémenté, contexte conservé
POST /v1/realtime/callsendpoint en ligne, accepte le token ek_ (handshake SDP complet = navigateur uniquement, non exercé ici)
Bundle clientvaleur de la clé absente du HTML et de tous les chunks JS
Logs runtimetool_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.