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.
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.
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.
É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.
Weekly AI Insights
Tools, techniques & news — curated for AI practitioners. Free, no spam.
Free, no spam. Unsubscribe anytime.
→Related Articles
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.