درس 8 — Projet final : compte rendu d'appel

Le pipeline « compte rendu d'appel »

6 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Le pipeline « compte rendu d'appel »

Le cahier des charges

La boutique fictive Atelier veut qu'à la fin de chaque appel :

  1. la transcription soit archivée, locuteurs séparés ;
  2. un compte rendu structuré alimente le CRM : motif, numéro de commande, sentiment, engagements pris, résumé ;
  3. le client reçoive un message vocal qui confirme ce qui a été convenu ;
  4. rien ne parte si le numéro de commande est douteux.

Pour tester sans vrais clients (et sans données personnelles), l'appel lui-même est synthétisé à partir d'un script : on connaît donc le texte exact prononcé, ce qui permet de mesurer le WER.

flowchart TB
  J["appel_fictif.json : script + commandes connues"] --> E1["1. tts_dialogue : appel.wav"]
  E1 --> E2["2. transcribe : transcription.json"]
  E2 --> W["WER contre le script"]
  E2 --> E3["3. summarize : compte_rendu.json (schéma Pydantic)"]
  E3 --> G{"numero_commande dans commandes_connues ?"}
  G -->|"non"| A["ATTENTION : à confirmer par un conseiller"]
  G --> E4["4. tts : reponse.wav"]

Les fichiers

FichierRôle
voix.pyles briques : tts, tts_dialogue, transcribe, summarize, wer, save_wav, with_retry
compte_rendu.pyle pipeline, en 4 étapes chronométrées
appel_fictif.jsonle scénario : voix, répliques, styles, commandes connues
live_demo.pyla conversation temps réel (module 3)
test_voix.py7 tests sans clé ni réseau
.env.exampleles variables : clé et noms de modèles

Étape 3 : un compte rendu structuré

Le compte rendu doit être lisible par un programme (le CRM). On impose donc un schéma avec Pydantic : l'API renvoie un JSON qui le respecte, et model_validate_json le vérifie à l'arrivée.

files/voix.py
class CompteRendu(BaseModel):
    role_agent: str                 # étiquette de diarisation de l'agent, ex. "spk:0"
    motif: str
    numero_commande: str | None
    sentiment_client: Literal["satisfait", "neutre", "mécontent"]  # une enum dans le schéma JSON
    actions: list[str]              # engagements pris par l'agent
    resume: str                     # 2 à 3 phrases
    reponse_vocale: str             # message de rappel lu au client (TTS), < 60 mots


def summarize(c, segments: list[dict]) -> CompteRendu:
    cfg = {"response_mime_type": "application/json", "response_schema": CompteRendu}
    r = with_retry(lambda: c.models.generate_content(
        model=MODEL, contents=PROMPT.format(transcript=as_text(segments)), config=cfg))
    return CompteRendu.model_validate_json(r.text)

Le champ role_agent règle la question laissée ouverte par la diarisation : c'est le LLM, qui lit le contenu, qui déduit que spk:0 est l'agent.

Pourquoi demander le compte rendu avec un response_schema plutôt qu'en texte libre ?

Étapes 3 bis et 4 : le garde-fou, puis la voix

files/compte_rendu.py
known = script.get("commandes_connues", [])
to_confirm = cr.numero_commande not in known
if to_confirm:
    print(f"    ATTENTION : commande {cr.numero_commande!r} inconnue du SI -> à confirmer par un conseiller")

dur = voix.tts(c, cr.reponse_vocale, f"{out}/reponse.wav", voice="Kore", style="chaleureuse, posée")

Ici, commandes_connues simule le SI. En production, ce serait une requête à votre base de commandes, filtrée sur le client qui appelle.

Résultat obtenu

Exécution réelle du 2026-10-15 (python compte_rendu.py appel_fictif.json sortie), compte rendu produit :

{
  "role_agent": "spk:0",
  "motif": "Retard de livraison de la commande de lampe de bureau",
  "numero_commande": "4521",
  "sentiment_client": "satisfait",
  "actions": [
    "Vérification du statut du colis",
    "Expédition d'une nouvelle lampe en livraison express offerte",
    "Envoi du numéro de suivi par email le soir même"
  ],
  "resume": "Le client appelle pour signaler le retard de la commande 4521. L'agent constate que le colis est bloqué à Lyon et propose de réexpédier une nouvelle lampe en livraison express gratuite aujourd'hui même avec suivi par email, afin qu'elle arrive avant vendredi. Le client accepte.",
  "reponse_vocale": "Bonjour Monsieur Martin. Comme convenu, nous vous renvoyons dès aujourd'hui une nouvelle lampe en livraison express et sans frais. Je vous enverrai votre numéro de suivi par email ce soir. Très bonne journée à vous."
}

Le fichier est aussi dans exemple_compte_rendu.json. Remarquez "sentiment_client": "satisfait" : le client était agacé au début et soulagé à la fin. Le modèle a jugé la fin de l'appel. Si vous voulez les deux, demandez deux champs (sentiment_debut, sentiment_fin).

La transcription donne « commande 451 » alors que le SI ne connaît que 4521, 3310 et 7788. Que fait compte_rendu.py ?

Exercice : un champ « à rappeler avant »

Ajoutez au compte rendu un champ echeance : la date limite promise au client (« avant vendredi »), ou null s'il n'y en a pas. Où le modifiez-vous, et comment le testez-vous sans clé ?

Voir la solution
  1. Dans CompteRendu (voix.py) : echeance: str | None.
  2. Dans PROMPT, précisez : « echeance : la date limite promise au client, telle qu'énoncée, ou null ».
  3. Dans FakeModels de test_voix.py, ajoutez "echeance": "avant vendredi" au JSON renvoyé, puis une assertion assert res["compte_rendu"].echeance == "avant vendredi".

Sans l'étape 3, le test échoue : Pydantic signale le champ manquant. C'est précisément ce qui garantit que le schéma, la consigne et les tests restent alignés.

Je sais faire

  • Enchaîner TTS, STT, LLM et TTS dans un pipeline chronométré
  • Imposer un schéma JSON avec Pydantic, y compris une liste fermée de valeurs
  • Faire déduire les rôles des locuteurs à partir de la diarisation
  • Bloquer une action quand une donnée entendue n'est pas vérifiée
التالي