Retour aux articles
18 MIN READ

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

By Learnia AI Research Team

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

📅 Dernière mise à jour : 14 mars 2026 — Basé sur Claude 3.5 Sonnet et l'API Anthropic.

📚 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…

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.


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.