Raccorder Asterisk à OpenAI Whisper : transcription vocale automatique des appels
Author

admin

Raccorder Asterisk à OpenAI Whisper : transcription vocale automatique des appels

Guide technique pour connecter Asterisk à l'API Whisper d'OpenAI : enregistrement des appels, transcription automatique via AGI Python, intégration CRM et analyse de sentiment.

Raccorder Asterisk à OpenAI Whisper : transcription vocale automatique

Architecture Asterisk + OpenAI Whisper

La transcription automatique des appels est un cas d'usage de plus en plus demandé : qualité de service en centre d'appels, conformité réglementaire, analyse de sentiment, enrichissement CRM… Le modèle Whisper d'OpenAI offre une transcription multilingue de très haute qualité, directement utilisable via API.

Ce guide détaille comment raccorder Asterisk à Whisper pour transcrire automatiquement les appels téléphoniques.

Architecture

Le principe est simple :

  1. Asterisk enregistre l'appel via MixMonitor (fichier WAV)
  2. À la fin de l'appel (hangup), un script AGI/Python est déclenché
  3. Le script envoie le fichier audio à l'API Whisper d'OpenAI
  4. La transcription est stockée en base de données / envoyée au CRM

Prérequis

  • Asterisk 18+ installé et fonctionnel
  • Python 3.10+ avec pip
  • Clé API OpenAI (créer un compte sur platform.openai.com)
  • Espace disque pour les enregistrements audio temporaires

1. Configurer l'enregistrement dans Asterisk

extensions.conf — Dialplan

[from-internal]
; Appels sortants avec enregistrement
exten => _0XXXXXXXXX,1,NoOp(Appel sortant vers ${EXTEN})
 same => n,Set(RECORDING_FILE=/var/spool/asterisk/recordings/${UNIQUEID})
 same => n,Set(CDR(recording)=${RECORDING_FILE}.wav)
 same => n,MixMonitor(${RECORDING_FILE}.wav,b)
 same => n,Dial(PJSIP/${EXTEN}@trunk-operateur,60,tT)
 same => n,Hangup()

; Hook post-hangup : lancer la transcription
exten => h,1,NoOp(Appel terminé - lancement transcription)
 same => n,System(/usr/local/bin/whisper-transcribe.sh ${RECORDING_FILE}.wav ${UNIQUEID} &)

Options MixMonitor

Option Description
b Enregistrer les deux canaux (appelant + appelé) en un seul fichier
r(fichier) Enregistrer uniquement le canal entrant
t(fichier) Enregistrer uniquement le canal sortant

L'option b est recommandée pour la transcription car Whisper gère bien le mix des voix.

2. Script shell wrapper

#!/bin/bash
# /usr/local/bin/whisper-transcribe.sh
# Appelé par Asterisk après chaque appel

RECORDING="$1"
UNIQUEID="$2"

# Vérifier que le fichier existe et n'est pas vide
if [ ! -s "$RECORDING" ]; then
    echo "$(date) - Fichier vide ou inexistant: $RECORDING" >> /var/log/whisper-transcribe.log
    exit 1
fi

# Convertir en format optimal pour Whisper (16kHz mono)
OPTIMIZED="${RECORDING%.wav}_16k.wav"
sox "$RECORDING" -r 16000 -c 1 "$OPTIMIZED" 2>/dev/null || cp "$RECORDING" "$OPTIMIZED"

# Lancer le script Python de transcription
/usr/local/bin/whisper-transcribe.py "$OPTIMIZED" "$UNIQUEID"
RESULT=$?

# Nettoyer le fichier optimisé
rm -f "$OPTIMIZED"

# Optionnel : supprimer l'enregistrement original après transcription
# rm -f "$RECORDING"

exit $RESULT
chmod +x /usr/local/bin/whisper-transcribe.sh

3. Script Python de transcription

Installation des dépendances

pip install openai python-dotenv requests

Le script

#!/usr/bin/env python3
# /usr/local/bin/whisper-transcribe.py
# Envoie un fichier audio a l'API Whisper d'OpenAI et stocke la transcription.
import sys
import os
import json
import logging
from datetime import datetime
from pathlib import Path

from openai import OpenAI
from dotenv import load_dotenv

# Configuration
load_dotenv('/etc/asterisk/whisper.env')
OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
WEBHOOK_URL = os.environ.get('WEBHOOK_URL')  # Optionnel: CRM webhook
DB_PATH = os.environ.get('TRANSCRIPTION_DB', '/var/lib/asterisk/transcriptions.json')

# Logging
logging.basicConfig(
    filename='/var/log/whisper-transcribe.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
log = logging.getLogger(__name__)


def transcribe_audio(file_path: str, language: str = 'fr') -> dict:
    # Envoie le fichier audio a l'API Whisper et retourne la transcription.
    client = OpenAI(api_key=OPENAI_API_KEY)

    with open(file_path, 'rb') as audio_file:
        response = client.audio.transcriptions.create(
            model='whisper-1',
            file=audio_file,
            language=language,
            response_format='verbose_json',
            timestamp_granularities=['segment'],
        )

    return {
        'text': response.text,
        'language': response.language,
        'duration': response.duration,
        'segments': [
            {
                'start': seg.start,
                'end': seg.end,
                'text': seg.text,
            }
            for seg in (response.segments or [])
        ],
    }


def save_transcription(unique_id: str, transcription: dict):
    # Sauvegarde la transcription en local (JSON append).
    entry = {
        'unique_id': unique_id,
        'timestamp': datetime.now().isoformat(),
        'text': transcription['text'],
        'language': transcription['language'],
        'duration': transcription['duration'],
        'segments': transcription['segments'],
    }

    # Append au fichier JSON (un objet par ligne — JSON Lines)
    db_path = Path(DB_PATH)
    db_path.parent.mkdir(parents=True, exist_ok=True)
    with open(db_path, 'a') as f:
        f.write(json.dumps(entry, ensure_ascii=False) + '\n')

    log.info(f'Transcription saved for {unique_id}: {len(transcription["text"])} chars')


def send_to_webhook(unique_id: str, transcription: dict):
    # Envoie la transcription au CRM via webhook.
    if not WEBHOOK_URL:
        return

    import requests
    payload = {
        'unique_id': unique_id,
        'text': transcription['text'],
        'language': transcription['language'],
        'duration': transcription['duration'],
    }
    try:
        resp = requests.post(WEBHOOK_URL, json=payload, timeout=10)
        resp.raise_for_status()
        log.info(f'Webhook sent for {unique_id}: {resp.status_code}')
    except Exception as e:
        log.error(f'Webhook failed for {unique_id}: {e}')


def main():
    if len(sys.argv) < 3:
        print(f'Usage: {sys.argv[0]} <audio_file> <unique_id>')
        sys.exit(1)

    audio_file = sys.argv[1]
    unique_id = sys.argv[2]

    if not os.path.isfile(audio_file):
        log.error(f'File not found: {audio_file}')
        sys.exit(1)

    # Vérifier la taille (Whisper limite à 25 Mo)
    file_size = os.path.getsize(audio_file)
    if file_size > 25 * 1024 * 1024:
        log.error(f'File too large ({file_size} bytes): {audio_file}')
        sys.exit(1)

    if file_size < 1024:
        log.warning(f'File very small ({file_size} bytes), skipping: {audio_file}')
        sys.exit(0)

    log.info(f'Transcribing {audio_file} ({file_size} bytes) for call {unique_id}')

    try:
        transcription = transcribe_audio(audio_file)
        save_transcription(unique_id, transcription)
        send_to_webhook(unique_id, transcription)
        log.info(f'Done: {unique_id} — "{transcription["text"][:100]}..."')
    except Exception as e:
        log.error(f'Transcription failed for {unique_id}: {e}')
        sys.exit(1)


if __name__ == '__main__':
    main()
chmod +x /usr/local/bin/whisper-transcribe.py

4. Configuration environnement

# /etc/asterisk/whisper.env
OPENAI_API_KEY=sk-proj-votre-cle-api-openai
WEBHOOK_URL=https://crm.example.com/api/transcriptions
TRANSCRIPTION_DB=/var/lib/asterisk/transcriptions.jsonl
chmod 600 /etc/asterisk/whisper.env
chown asterisk:asterisk /etc/asterisk/whisper.env

5. Dépendances système

# sox pour la conversion audio
apt install -y sox libsox-fmt-all

# Créer le répertoire d'enregistrement
mkdir -p /var/spool/asterisk/recordings
chown asterisk:asterisk /var/spool/asterisk/recordings
mkdir -p /var/lib/asterisk

6. Transcription en temps réel (avancé)

Pour une transcription en temps réel pendant l'appel (pas seulement après), vous pouvez utiliser Asterisk External Media (ARI) ou un AudioSocket :

Méthode AudioSocket (Asterisk 18+)

; extensions.conf
exten => _0XXXXXXXXX,1,NoOp(Appel avec transcription live)
 same => n,Answer()
 same => n,AudioSocket(127.0.0.1:9092,${UNIQUEID})
 same => n,Dial(PJSIP/${EXTEN}@trunk-operateur,60)

Le serveur AudioSocket reçoit le flux audio brut en TCP et peut l'envoyer en streaming à l'API Whisper (ou à un modèle Whisper local).

Serveur AudioSocket Python (exemple simplifié)

#!/usr/bin/env python3
# Serveur AudioSocket pour transcription live.
import socket
import struct
import io
import wave
from openai import OpenAI

LISTEN_HOST = '127.0.0.1'
LISTEN_PORT = 9092
CHUNK_DURATION = 10  # secondes d'audio avant transcription

client = OpenAI()

def handle_connection(conn, addr):
    # Recoit l'audio d'Asterisk, accumule, transcrit par chunks.
    audio_buffer = bytearray()
    sample_rate = 8000  # Asterisk envoie en 8kHz par défaut
    bytes_per_chunk = sample_rate * 2 * CHUNK_DURATION  # 16-bit PCM

    try:
        while True:
            # Protocole AudioSocket : 1 byte type + 2 bytes length + payload
            header = conn.recv(3)
            if len(header) < 3:
                break

            msg_type = header[0]
            length = struct.unpack('>H', header[1:3])[0]

            if length > 0:
                payload = conn.recv(length)
            else:
                payload = b''

            # Type 0x10 = audio data
            if msg_type == 0x10 and payload:
                audio_buffer.extend(payload)

                if len(audio_buffer) >= bytes_per_chunk:
                    # Créer un WAV en mémoire
                    wav_io = io.BytesIO()
                    with wave.open(wav_io, 'wb') as wf:
                        wf.setnchannels(1)
                        wf.setsampwidth(2)
                        wf.setframerate(sample_rate)
                        wf.writeframes(bytes(audio_buffer))
                    wav_io.seek(0)
                    wav_io.name = 'chunk.wav'

                    # Transcrire
                    response = client.audio.transcriptions.create(
                        model='whisper-1',
                        file=wav_io,
                        language='fr',
                    )
                    if response.text.strip():
                        print(f'[LIVE] {response.text}')

                    audio_buffer.clear()

    except Exception as e:
        print(f'Error: {e}')
    finally:
        conn.close()


def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((LISTEN_HOST, LISTEN_PORT))
    server.listen(10)
    print(f'AudioSocket server listening on {LISTEN_HOST}:{LISTEN_PORT}')

    while True:
        conn, addr = server.accept()
        # En production : utiliser threading ou asyncio
        handle_connection(conn, addr)


if __name__ == '__main__':
    main()

7. Whisper local (auto-hébergé)

Si vous ne souhaitez pas envoyer l'audio à OpenAI (confidentialité), vous pouvez héberger Whisper localement :

# Installer whisper en local (nécessite GPU NVIDIA recommandé)
pip install openai-whisper

# Ou via Docker
docker run -d --gpus all -p 9000:9000 \
    onerahmet/openai-whisper-asr-webservice:latest

Puis modifier le script Python pour pointer vers votre instance locale :

# Pour whisper local via l'image Docker
client = OpenAI(
    api_key='not-needed',
    base_url='http://localhost:9000/v1',
)

8. Coûts API Whisper

Modèle Prix Limites
whisper-1 $0.006 / minute 25 Mo par fichier, ~99 langues

Pour un centre d'appels avec 1 000 appels/jour de 5 minutes en moyenne :
- 5 000 minutes/jour × $0.006 = $30/jour soit ~$900/mois
- Alternative : Whisper local (GPU) pour $0/appel après investissement initial

9. Cas d'usage avancés

Analyse de sentiment (avec GPT)

Après la transcription Whisper, vous pouvez chaîner avec GPT pour l'analyse :

def analyze_sentiment(transcription_text: str) -> dict:
    # Analyse le sentiment de la transcription avec GPT.
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[
            {'role': 'system', 'content': 'Analyse le sentiment de cette transcription d\'appel téléphonique. Retourne un JSON avec: sentiment (positif/neutre/negatif), score (0-10), resume (1 phrase), mots_cles (liste).'},
            {'role': 'user', 'content': transcription_text},
        ],
        response_format={'type': 'json_object'},
    )
    return json.loads(response.choices[0].message.content)

Résumé automatique des appels

def summarize_call(transcription_text: str) -> str:
    # Resume l'appel en quelques lignes.
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[
            {'role': 'system', 'content': 'Résume cet appel téléphonique en 3-5 bullet points. Identifie le sujet, les demandes du client et les actions à mener.'},
            {'role': 'user', 'content': transcription_text},
        ],
    )
    return response.choices[0].message.content

Conclusion

L'intégration d'Asterisk avec OpenAI Whisper ouvre des possibilités considérables : transcription automatique, analyse de qualité, conformité réglementaire, enrichissement CRM. Le modèle Whisper offre une précision remarquable en français et dans 99 langues.

Chez Technixis, nous intégrons l'IA dans les plateformes VoIP : transcription Whisper, analyse de sentiment GPT, SVI intelligent. Contactez-nous ou appelez le 0800 012 013.


Liens & sources :
- OpenAI Whisper — API
- Asterisk — Site officiel
- Whisper — GitHub (modèle open source)

Aucun commentaire pour le moment. Soyez le premier !

Laisser un commentaire