Retour aux articles
20 MIN READ

Résumé Automatique d'Appels avec Claude : Guide Pratique

By Dorian Laurenceau

📅 Dernière révision : 24 avril 2026. Mise à jour avec les retours et observations d'avril 2026.

📚 Articles liés : Extended Thinking Guide | Prompt Chaining Pipelines | Processus de Prompt Engineering | Structured Outputs


Chaque jour, des millions d'appels sont passés dans les centres d'appels, les équipes commerciales et les réunions d'entreprise. La prise de notes manuelle est chronophage, incomplète et incohérente. Ce guide vous montre comment construire un pipeline de résumé automatique d'appels avec Claude, de la transcription brute au résumé structuré prêt pour votre CRM.


Pourquoi le Résumé Automatique d'Appels ?

Les cas d'usage sont massifs :

  • Centres d'appels : chaque agent traite 40-60 appels/jour. Le post-traitement manuel représente 20-30% du temps de travail.
  • Équipes commerciales : les notes de vente incomplètes font perdre du contexte entre les suivis.
  • Réunions d'entreprise : les décisions prises verbalement sont souvent oubliées sans trace écrite structurée.
Loading diagram…

Le résumé d'appels en production : ce qui échoue silencieusement

Le résumé automatique d'appels est un des cas d'usage LLM entreprise les plus communs, et aussi un où « ça marchait en démo » masque de vraies douleurs de production. Les threads sur r/MachineLearning, r/salesforce, r/SalesTechniques et r/dataengineering cartographient ce que les équipes heurtent vraiment.

Ce qui ship de façon fiable :

  • Résumés structurés plutôt que libres. Un schema fixe (résumé exécutif, participants, décisions, action items, sentiment) bat le « résume cet appel » ouvert pour les systèmes downstream.
  • Chunking + map-reduce pour les appels longs. Un appel de 90 minutes dépasse la plupart des budgets de prompt unique. Les patterns de résumé map-reduce et le résumé hiérarchique sont l'approche prouvée.
  • La qualité ASR fixe le plafond. AssemblyAI, Deepgram, OpenAI Whisper et Google Speech-to-Text ont tous des limites honnêtes sur l'audio bruité, les accents lourds et les locuteurs qui se chevauchent. Garbage-in garbage-out s'applique ici plus qu'ailleurs.
  • Les erreurs de diarisation se propagent. Si l'attribution de locuteur est fausse dans la transcription, le résumé attribuera les décisions à la mauvaise personne. Validez la diarisation sur votre profil audio.

Ce qui échoue silencieusement :

  • Action items hallucinés. Les modèles inventeront des action items concrets depuis une discussion vague. Les équipes de production ajoutent des étapes de vérification : cet action item apparaît-il comme citation littérale, paraphrase groundée, ou inférence pure ? Loggez et flaggez les inférences.
  • Consentement et compliance. GDPR, CCPA et règles spécifiques d'industrie (HIPAA pour la santé, FINRA pour les services financiers) contraignent ce qui peut être enregistré, stocké et traité. Otter.ai, Fireflies et Gong publient tous leurs frameworks de consentement ; lisez-les.
  • L'analyse de sentiment est fragile. « Le client était frustré » vs « le client avait des questions » peut être un retournement d'un seul mot qui change le routage CRM. Traitez les outputs de sentiment comme des signaux souples.
  • La gestion des PII n'est pas négociable. Numéros de carte, SSN, détails médicaux apparaissant dans les appels ont besoin de scrubbing avant logging. Presidio ou la rédaction basée sur regex est standard.
  • Le coût rampe avec le volume. Résumer chaque appel devient vite cher. Le message batching et le prompt caching sont significatifs ici.

Ce que les équipes de production font vraiment :

  • Deux passes : extraire puis résumer. Le premier pass tire les faits structurés (locuteurs, décisions, engagements) ; le second synthétise. Plus fiable que les résumés one-shot.
  • Évaluer contre des résumés de référence. ROUGE et BERTScore sont des proxies faibles ; les évals A/B notées par humain sont la vérité terrain.
  • Coupler avec le write-back CRM prudemment. Les écritures automatiques vers Salesforce ou HubSpot sont à haut risque tant que la qualité de résumé n'est pas mesurée à 95%+ de précision. Démarrez avec humain-dans-la-boucle.
  • Instrumenter le taux d'hallucination. Langfuse et Arize supportent le tracking output-vs-texte-source.

Le cadrage honnête : le résumé d'appels paraît facile en démos et est opérationnellement complexe en production. Les victoires viennent des outputs structurés, boucles de validation, hygiène PII et évaluation honnête contre vérité terrain. Sautez ça et la magie de démo devient un pipeline d'erreurs silencieuses qui empoisonnent les systèmes downstream.

Pré-traitement des Transcriptions

Avant d'envoyer une transcription à Claude, un nettoyage est indispensable. Les transcriptions brutes issues d'un ASR (Automatic Speech Recognition) contiennent du bruit : hésitations, répétitions, erreurs de diarisation.

Nettoyage et normalisation

import re
from dataclasses import dataclass

@dataclass
class TranscriptSegment:
    speaker: str
    text: str
    start_time: float
    end_time: float

def clean_transcript(segments: list[TranscriptSegment]) -> list[TranscriptSegment]:
    """Nettoie une transcription brute ASR."""
    cleaned = []
    for seg in segments:
        text = seg.text.strip()
        # Supprimer les hésitations courantes
        text = re.sub(r'\b(euh|hum|ah|oh|ben)\b', '', text, flags=re.IGNORECASE)
        # Supprimer les répétitions consécutives
        text = re.sub(r'\b(\w+)( \1\b)+', r'\1', text)
        # Normaliser les espaces
        text = re.sub(r'\s+', ' ', text).strip()
        if text:
            cleaned.append(TranscriptSegment(
                speaker=seg.speaker,
                text=text,
                start_time=seg.start_time,
                end_time=seg.end_time
            ))
    return cleaned

def merge_consecutive_speaker(segments: list[TranscriptSegment]) -> list[TranscriptSegment]:
    """Fusionne les segments consécutifs du même locuteur."""
    if not segments:
        return []
    merged = [segments[0]]
    for seg in segments[1:]:
        if seg.speaker == merged[-1].speaker:
            merged[-1].text += " " + seg.text
            merged[-1].end_time = seg.end_time
        else:
            merged.append(seg)
    return merged

Gestion de la diarisation multi-locuteurs

Quand la diarisation est incertaine (locuteurs mal identifiés), Claude peut aider à corriger :

import anthropic

client = anthropic.Anthropic()

def fix_diarization(transcript_text: str) -> str:
    """Utilise Claude pour corriger les erreurs de diarisation."""
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": f"""Analyse cette transcription et corrige les erreurs
de diarisation (mauvaise attribution de locuteur).

Indices pour identifier les erreurs :
- Un locuteur qui se répond à lui-même
- Un changement de ton/sujet incohérent pour un même locuteur
- Des formules de politesse attribuées au mauvais interlocuteur

Transcription :
{transcript_text}

Retourne la transcription corrigée au même format."""
        }]
    )
    return response.content[0].text

Prompt Design pour le Résumé Structuré

Le cœur du système est le prompt qui transforme une transcription en résumé structuré. La clé : un template de sortie explicite adapté au type d'appel.

Template générique (réunion / appel)

SUMMARIZATION_PROMPT = """Tu es un assistant spécialisé dans le résumé d'appels
professionnels. Produis un résumé structuré fidèle à la transcription.

RÈGLES :
- Ne jamais inventer d'informations absentes de la transcription
- Attribuer chaque décision/action au bon locuteur
- Distinguer les décisions fermes des discussions exploratoires
- Utiliser le format de sortie EXACTEMENT comme spécifié

FORMAT DE SORTIE :
## Résumé Exécutif
[2-3 phrases résumant l'objectif et le résultat principal de l'appel]

## Participants
- [Nom/Rôle] : [Contribution principale]

## Points Clés Discutés
1. [Sujet] — [Conclusion ou état]
2. ...

## Décisions Prises
- [Décision] (par [Locuteur], à [timestamp si disponible])

## Action Items
| Responsable | Action | Échéance | Priorité |
|-------------|--------|----------|----------|
| [Nom] | [Description] | [Date/Délai] | Haute/Moyenne/Basse |

## Prochaines Étapes
- [Étape] — [Responsable] — [Date prévue]

## Ton et Sentiment Général
[1 phrase sur l'ambiance générale de l'appel]

---
TRANSCRIPTION :
{transcript}"""

Template BANT pour appels commerciaux

SALES_CALL_PROMPT = """Tu es un analyste commercial IA. Résume cet appel de vente
au format BANT pour le CRM.

FORMAT DE SORTIE :
## Résumé de l'Appel Commercial

### Informations Client
- Entreprise : [nom]
- Contact : [nom, rôle]
- Secteur : [industrie]

### Qualification BANT
- **Budget** : [Montant évoqué ou "Non discuté"]
- **Authority** : [Décisionnaire identifié ? Qui ?]
- **Need** : [Besoin principal exprimé]
- **Timeline** : [Échéance mentionnée ou "Non définie"]

### Score de Qualification
[1-10 avec justification]

### Objections Soulevées
1. [Objection] → [Réponse apportée]

### Action Items Commerciaux
| Action | Responsable | Échéance |
|--------|-------------|----------|
| [Description] | [Vendeur/Client] | [Date] |

### Prochaine Étape Recommandée
[Recommandation basée sur l'analyse BANT]

---
TRANSCRIPTION :
{transcript}"""

Appel API et Extraction Structurée

Voici l'implémentation complète pour envoyer une transcription et récupérer un résumé structuré :

import anthropic
import json

client = anthropic.Anthropic()

def summarize_call(transcript: str, call_type: str = "generic") -> dict:
    """Résume un appel et retourne un résumé structuré."""
    
    prompts = {
        "generic": SUMMARIZATION_PROMPT,
        "sales": SALES_CALL_PROMPT,
    }
    prompt_template = prompts.get(call_type, SUMMARIZATION_PROMPT)
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": prompt_template.format(transcript=transcript)
        }]
    )
    
    summary_text = response.content[0].text
    
    # Extraction des action items en JSON structuré
    action_response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f"""Extrais les action items de ce résumé en JSON strict.

Résumé :
{summary_text}

Retourne UNIQUEMENT un JSON valide au format :
{{
  "action_items": [
    {{
      "responsible": "Nom",
      "action": "Description",
      "deadline": "Date ou null",
      "priority": "high|medium|low"
    }}
  ],
  "decisions": [
    {{
      "decision": "Description",
      "made_by": "Nom",
      "timestamp": "Moment dans l'appel ou null"
    }}
  ],
  "follow_ups": [
    {{
      "description": "Description",
      "responsible": "Nom",
      "target_date": "Date ou null"
    }}
  ]
}}"""
        }]
    )
    
    structured_data = json.loads(action_response.content[0].text)
    
    return {
        "summary": summary_text,
        "structured": structured_data,
        "model": "claude-sonnet-4-20250514",
        "call_type": call_type
    }

Pour aller plus loin sur l'extraction JSON fiable, consultez notre guide sur les Structured Outputs.


Gestion des Appels Longs : Chunking + Map-Reduce

Les appels de plus de 30 minutes dépassent souvent la fenêtre de contexte optimale. La solution : un pattern map-reduce inspiré des pipelines de prompt chaining.

Loading diagram…

Implémentation du chunking avec chevauchement

def chunk_transcript(
    segments: list[TranscriptSegment],
    chunk_duration_minutes: float = 15.0,
    overlap_minutes: float = 3.0
) -> list[list[TranscriptSegment]]:
    """Découpe une transcription en chunks avec chevauchement."""
    chunk_duration = chunk_duration_minutes * 60
    overlap = overlap_minutes * 60
    
    if not segments:
        return []
    
    total_duration = segments[-1].end_time - segments[0].start_time
    chunks = []
    start_time = segments[0].start_time
    
    while start_time < segments[-1].end_time:
        end_time = start_time + chunk_duration
        chunk = [s for s in segments 
                 if s.start_time >= start_time and s.start_time < end_time]
        if chunk:
            chunks.append(chunk)
        start_time += chunk_duration - overlap
    
    return chunks

def format_chunk(segments: list[TranscriptSegment]) -> str:
    """Formate un chunk pour l'envoi à Claude."""
    lines = []
    for seg in segments:
        minutes = int(seg.start_time // 60)
        seconds = int(seg.start_time % 60)
        lines.append(f"[{minutes:02d}:{seconds:02d}] {seg.speaker}: {seg.text}")
    return "\n".join(lines)

Map-Reduce : résumé parallèle puis fusion

import asyncio

async def summarize_chunk(client, chunk_text: str, chunk_index: int, 
                          total_chunks: int) -> str:
    """Résume un chunk individuel (phase MAP)."""
    response = await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f"""Résume ce segment d'appel (partie {chunk_index + 1}/{total_chunks}).

IMPORTANT : Ce n'est qu'une PARTIE de l'appel. Ne conclus pas
prématurément. Note les sujets en cours en fin de segment.

Extrais :
- Points clés discutés
- Décisions prises (avec locuteur)
- Action items identifiés
- Sujets en cours / non résolus

Segment :
{chunk_text}"""
        }]
    )
    return response.content[0].text

async def merge_summaries(client, partial_summaries: list[str], 
                          call_type: str = "generic") -> str:
    """Fusionne les résumés partiels en résumé final (phase REDUCE)."""
    summaries_text = "\n\n---\n\n".join(
        [f"### Partie {i+1}\n{s}" for i, s in enumerate(partial_summaries)]
    )
    
    response = await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": f"""Fusionne ces résumés partiels en UN résumé final cohérent.

RÈGLES DE FUSION :
- Dédoublonner les informations présentes dans les zones de chevauchement
- Maintenir l'ordre chronologique
- Consolider les action items (un item = une entrée, même s'il apparaît
  dans plusieurs parties)
- Résoudre les sujets "en cours" avec leur conclusion dans les parties
  suivantes

Résumés partiels :
{summaries_text}

Produis le résumé final au format structuré standard."""
        }]
    )
    return response.content[0].text

async def summarize_long_call(transcript_segments: list[TranscriptSegment],
                               call_type: str = "generic") -> dict:
    """Pipeline complet pour les appels longs."""
    client = anthropic.AsyncAnthropic()
    
    # Chunking
    chunks = chunk_transcript(transcript_segments)
    chunk_texts = [format_chunk(c) for c in chunks]
    
    # Phase MAP : résumés parallèles
    tasks = [
        summarize_chunk(client, text, i, len(chunk_texts))
        for i, text in enumerate(chunk_texts)
    ]
    partial_summaries = await asyncio.gather(*tasks)
    
    # Phase REDUCE : fusion
    final_summary = await merge_summaries(client, partial_summaries, call_type)
    
    return {
        "summary": final_summary,
        "chunks_processed": len(chunks),
        "method": "map-reduce"
    }

Évaluation de la Qualité des Résumés

Un bon résumé doit être fidèle, complet et concis. Voici un système d'évaluation automatique.

Loading diagram…

Évaluation automatisée avec Claude-as-a-Judge

def evaluate_summary(transcript: str, summary: str) -> dict:
    """Évalue un résumé sur 3 dimensions : fidélité, complétude, concision."""
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f"""Évalue ce résumé d'appel sur 3 dimensions.
Pour chaque dimension, donne un score de 0.0 à 1.0 et une justification.

## Dimensions

1. **Fidélité** : Chaque affirmation du résumé est-elle vérifiable dans
   la transcription ? (Pas d'hallucination)
2. **Complétude** : Tous les points clés, décisions et actions de la
   transcription sont-ils présents dans le résumé ?
3. **Concision** : Le résumé est-il suffisamment condensé sans
   redondance ni détails superflus ?

## Transcription (source de vérité)
{transcript[:3000]}

## Résumé à évaluer
{summary}

Retourne UNIQUEMENT un JSON valide :
{{
  "faithfulness": {{"score": 0.0, "issues": []}},
  "completeness": {{"score": 0.0, "missing": []}},
  "conciseness": {{"score": 0.0, "redundancies": []}},
  "overall_score": 0.0,
  "pass": true
}}"""
        }]
    )
    
    return json.loads(response.content[0].text)

Boucle de re-génération si qualité insuffisante

async def summarize_with_quality_check(
    transcript: str, 
    call_type: str = "generic",
    max_retries: int = 2,
    quality_threshold: float = 0.8
) -> dict:
    """Résume avec vérification qualité et re-génération si nécessaire."""
    
    for attempt in range(max_retries + 1):
        result = summarize_call(transcript, call_type)
        evaluation = evaluate_summary(transcript, result["summary"])
        
        if evaluation["overall_score"] >= quality_threshold:
            return {
                **result,
                "quality": evaluation,
                "attempts": attempt + 1
            }
        
        # Feedback pour améliorer la prochaine tentative
        if evaluation["completeness"]["missing"]:
            transcript = f"""[INSTRUCTION SUPPLÉMENTAIRE]
Points manqués lors de la tentative précédente :
{', '.join(evaluation['completeness']['missing'])}
Assure-toi d'inclure ces éléments.

{transcript}"""
    
    # Retourner le dernier résultat même si sous le seuil
    return {**result, "quality": evaluation, "attempts": max_retries + 1}

Étude de Cas : Appel Commercial → Résumé CRM


Pipeline Complet de Production

Voici l'assemblage final du pipeline, intégrant toutes les étapes :

import anthropic
import asyncio
from dataclasses import asdict

async def production_pipeline(
    raw_segments: list[TranscriptSegment],
    call_type: str = "generic",
    call_metadata: dict = None
) -> dict:
    """Pipeline de production pour résumé d'appels."""
    
    # 1. Pré-traitement
    cleaned = clean_transcript(raw_segments)
    merged = merge_consecutive_speaker(cleaned)
    
    # 2. Déterminer la stratégie (direct ou map-reduce)
    total_duration = merged[-1].end_time - merged[0].start_time
    
    if total_duration > 30 * 60:  # > 30 minutes
        result = await summarize_long_call(merged, call_type)
    else:
        transcript_text = format_chunk(merged)
        result = summarize_call(transcript_text, call_type)
    
    # 3. Évaluation qualité
    transcript_for_eval = format_chunk(merged[:50])  # Premiers segments
    evaluation = evaluate_summary(transcript_for_eval, result["summary"])
    
    return {
        "call_metadata": call_metadata,
        "summary": result["summary"],
        "structured_data": result.get("structured", {}),
        "quality": evaluation,
        "processing": {
            "method": result.get("method", "direct"),
            "segments_processed": len(merged),
            "duration_minutes": round(total_duration / 60, 1)
        }
    }

Pour des architectures de pipelines plus avancées, consultez notre guide sur le prompt chaining. Pour utiliser Extended Thinking sur les transcriptions complexes, voir le guide Extended Thinking.


FAQ

Comment Claude gère-t-il les transcriptions très longues (plus d'une heure) ?

Pour les transcriptions dépassant la fenêtre de contexte, on utilise un pattern map-reduce : la transcription est découpée en chunks de 10-15 minutes avec chevauchement, chaque chunk est résumé indépendamment, puis les résumés partiels sont fusionnés en un résumé final cohérent avec extraction des actions.

Quelle est la précision de l'extraction d'action items par Claude ?

Avec un prompt bien structuré et un format de sortie strict, Claude atteint 90-95% de rappel sur les action items explicites et 75-85% sur les engagements implicites. La précision augmente significativement avec des exemples few-shot dans le prompt.

Comment gérer les transcriptions avec erreurs de diarisation (mauvais locuteur attribué) ?

On ajoute une étape de pré-traitement qui corrige les incohérences de diarisation en analysant le contexte conversationnel. Claude peut identifier quand un locuteur est mal attribué en se basant sur le contenu sémantique et les transitions thématiques.

Peut-on personnaliser le format de résumé selon le type d'appel (vente, support, réunion) ?

Oui, on utilise des templates de sortie spécialisés : BANT pour les appels commerciaux, SOAP-like pour le support technique, et un format décision/action pour les réunions. Le system prompt sélectionne automatiquement le template via un classifieur initial.


D

Dorian Laurenceau

Full-Stack Developer & Learning Designer

Full-stack web developer and learning designer. I spent 4 years as a freelance full-stack developer and 4 years teaching React, JavaScript, HTML/CSS and WordPress to adult learners. Today I design learning paths in web development and AI, grounded in learning science. I founded learn-prompting.fr to make AI practical and accessible, and built the Bluff app to gamify political transparency.

Prompt EngineeringLLMsFull-Stack DevelopmentLearning DesignReact
Published: March 14, 2026Updated: April 24, 2026
Newsletter

Weekly AI Insights

Tools, techniques & news — curated for AI practitioners. Free, no spam.

Free, no spam. Unsubscribe anytime.

FAQ

Comment Claude gère-t-il les transcriptions très longues (plus d'une heure) ?+

Pour les transcriptions dépassant la fenêtre de contexte, on utilise un pattern map-reduce : la transcription est découpée en chunks de 10-15 minutes avec chevauchement, chaque chunk est résumé indépendamment, puis les résumés partiels sont fusionnés en un résumé final cohérent avec extraction des actions.

Quelle est la précision de l'extraction d'action items par Claude ?+

Avec un prompt bien structuré et un format de sortie strict, Claude atteint 90-95% de rappel sur les action items explicites et 75-85% sur les engagements implicites. La précision augmente significativement avec des exemples few-shot dans le prompt.

Comment gérer les transcriptions avec erreurs de diarisation (mauvais locuteur attribué) ?+

On ajoute une étape de pré-traitement qui corrige les incohérences de diarisation en analysant le contexte conversationnel. Claude peut identifier quand un locuteur est mal attribué en se basant sur le contenu sémantique et les transitions thématiques.

Peut-on personnaliser le format de résumé selon le type d'appel (vente, support, réunion) ?+

Oui, on utilise des templates de sortie spécialisés : BANT pour les appels commerciaux, SOAP-like pour le support technique, et un format décision/action pour les réunions. Le système prompt sélectionne automatiquement le template via un classifieur initial.