Application de chat vocal en temps réel utilisant l'API OpenAI Realtime pour des conversations audio bidirectionnelles.
- 🎤 Capture audio en temps réel depuis le microphone
- 🤖 Intégration OpenAI Realtime API avec le modèle
gpt-realtime-mini-2025-10-06 - 💬 Transcription automatique des conversations (utilisateur et IA)
- 🔊 Réponses audio jouées directement dans le navigateur
- 🎨 Interface moderne et responsive avec animations
- ⚡ Communication WebSocket bidirectionnelle (Client ↔ Serveur C# ↔ OpenAI)
Client Web (Browser)
↓ WebSocket
Serveur ASP.NET Core 9.0
↓ WebSocket
OpenAI Realtime API
- Audio utilisateur : Microphone → Client → WebSocket → Serveur C# → OpenAI
- Réponse IA : OpenAI → Serveur C# → WebSocket → Client → Haut-parleurs
- .NET 9.0 SDK ou supérieur
- Clé API OpenAI avec accès à l'API Realtime
- Navigateur moderne supportant WebSocket et Web Audio API (Chrome, Edge recommandés)
- Microphone fonctionnel
cd /chemin/vers/chatrealtimeOuvrez le fichier appsettings.json et remplacez YOUR_OPENAI_API_KEY_HERE par votre clé API :
{
"OpenAI": {
"ApiKey": "sk-proj-xxxxxxxxxxxxxxxxxxxxx",
"Model": "gpt-realtime-mini-2025-10-06",
...
}
}dotnet restoredotnet runL'application sera disponible à :
- HTTP : http://localhost:5000
- HTTPS : https://localhost:5001
Toutes les configurations se trouvent dans appsettings.json :
| Paramètre | Description | Valeur par défaut |
|---|---|---|
ApiKey |
Votre clé API OpenAI | À configurer |
Model |
Modèle OpenAI à utiliser | gpt-realtime-mini-2025-10-06 |
Voice |
Voix de l'IA (alloy, echo, fable, onyx, nova, shimmer) | echo |
TranscriptionModel |
Modèle de transcription | gpt-4o-transcribe |
SystemPromptFile |
Fichier contenant le prompt système | Prompts/Marvin.md |
Temperature |
Créativité des réponses (0.0 - 2.0) | 0.8 |
MaxResponseOutputTokens |
Nombre maximum de tokens en sortie | 4096 |
Instructions |
Instructions système inline (si pas de fichier) | Texte personnalisable |
TurnDetection.Type |
Type de détection de tour de parole | server_vad |
TurnDetection.Threshold |
Seuil de détection vocale (0.0 - 1.0) | 0.5 |
TurnDetection.SilenceDurationMs |
Durée de silence pour fin de phrase | 500 ms |
Tools |
Liste d'outils MCP (voir ci-dessous) | [] |
L'application supporte des prompts système depuis des fichiers Markdown :
{
"OpenAI": {
"SystemPromptFile": "Prompts/Marvin.md"
}
}Exemple fourni : Prompts/Marvin.md - Personnalité de Marvin, le robot déprimé de H2G2
Pour créer votre propre personnalité :
- Créez un fichier
.mddans le dossierPrompts/ - Décrivez la personnalité, le style et les consignes
- Mettez à jour
SystemPromptFiledansappsettings.json
Vous pouvez configurer des outils externes que l'IA peut appeler pendant la conversation :
{
"OpenAI": {
"Tools": [
{
"Name": "get_weather",
"Description": "Obtenir la météo actuelle pour une ville donnée",
"Parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "Le nom de la ville (ex: Paris, Londres)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "L'unité de température"
}
},
"required": ["location"]
}
}
]
}
}| Champ | Type | Description |
|---|---|---|
Name |
string | Nom unique de l'outil (snake_case) |
Description |
string | Description de ce que fait l'outil |
Parameters |
object | Schéma JSON des paramètres (format OpenAPI) |
1. Météo
{
"Name": "get_weather",
"Description": "Obtenir la météo actuelle",
"Parameters": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "Ville" }
},
"required": ["location"]
}
}2. Heure actuelle
{
"Name": "get_time",
"Description": "Obtenir l'heure actuelle dans un fuseau horaire",
"Parameters": {
"type": "object",
"properties": {
"timezone": { "type": "string", "description": "Fuseau horaire (ex: Europe/Paris)" }
},
"required": ["timezone"]
}
}3. Calculatrice
{
"Name": "calculate",
"Description": "Effectuer un calcul mathématique",
"Parameters": {
"type": "object",
"properties": {
"expression": { "type": "string", "description": "Expression mathématique" }
},
"required": ["expression"]
}
}Les outils configurés sont déclarés à l'API OpenAI, mais vous devez implémenter la logique d'exécution dans votre code.
L'API vous enverra des événements function_call que vous devrez intercepter et traiter dans OpenAIRealtimeService.cs.
L'application utilise soundtouch-js pour modifier la vitesse audio sans changer la hauteur de la voix :
- Slider dans l'interface : 0.5x à 2.0x
- Buffer de démarrage : 8 chunks (configurable dans
app.jsligne 29)
Pour ajuster le buffer si vous avez des coupures :
// Dans wwwroot/app.js
this.minBufferChunks = 10; // Augmenter pour plus de stabilitéPour changer la voix :
"Voice": "nova"Voix disponibles : alloy, echo, fable, onyx, nova, shimmer
En plus de l'interface vocale, tous les outils MCP sont accessibles via HTTP REST !
GET /api/toolsRéponse :
{
"count": 2,
"tools": [
{
"name": "get_weather",
"description": "Obtenir la météo actuelle pour une ville donnée",
"parameters": { ... }
},
{
"name": "get_time",
"description": "Obtenir l'heure actuelle dans un fuseau horaire donné",
"parameters": { ... }
}
]
}POST /api/tools/{toolName}
Content-Type: application/json
{
"location": "Paris",
"unit": "celsius"
}Réponse :
{
"tool": "get_weather",
"success": true,
"result": {
"location": "Paris",
"temperature": 22,
"unit": "°C",
"condition": "Ensoleillé",
"description": "Il fait actuellement 22°C à Paris avec un temps ensoleillé."
},
"executedAt": "2025-10-08T14:30:00Z"
}Météo :
GET /api/tools/weather/Paris?unit=celsiusHeure :
GET /api/tools/time/Europe_Paris# Liste des outils
curl http://localhost:5166/api/tools
# Météo à Paris
curl http://localhost:5166/api/tools/weather/Paris
# Météo en Fahrenheit
curl http://localhost:5166/api/tools/weather/London?unit=fahrenheit
# Heure à New York
curl http://localhost:5166/api/tools/time/America_New_York
# Exécution générique
curl -X POST http://localhost:5166/api/tools/get_weather \
-H "Content-Type: application/json" \
-d '{"location": "Tokyo", "unit": "celsius"}'Obtenir la météo d'une ville (données simulées)
Paramètres :
location(string, requis) : Nom de la villeunit(string, optionnel) :celsiusoufahrenheit
Exemple :
curl http://localhost:5166/api/tools/weather/ParisObtenir l'heure actuelle dans un fuseau horaire
Paramètres :
timezone(string, requis) : Fuseau horaire (ex:Europe/Paris,America/New_York)
Exemple :
curl http://localhost:5166/api/tools/time/Europe_ParisEffectuer un calcul mathématique simple
Paramètres :
expression(string, requis) : Expression mathématique (ex:2 + 2,10 * 5)
Exemple :
curl -X POST http://localhost:5166/api/tools/calculate \
-H "Content-Type: application/json" \
-d '{"expression": "2 + 2"}'Parfait pour appeler vos propres APIs sans modifier le code C# !
{
"Tools": [
{
"Name": "mon_outil_local",
"Description": "Appelle mon API locale",
"Type": "http",
"Parameters": {
"type": "object",
"properties": {
"param1": { "type": "string", "description": "Premier paramètre" }
},
"required": ["param1"]
},
"Http": {
"Url": "http://localhost:3000/api/mon-endpoint",
"Method": "POST",
"Headers": {
"Authorization": "Bearer mon-token",
"X-Custom-Header": "valeur"
}
}
}
]
}Exemples d'outils HTTP :
1. Appel GET avec paramètres dans l'URL :
{
"Name": "get_user",
"Type": "http",
"Parameters": { ... },
"Http": {
"Url": "http://localhost:3000/users/{user_id}",
"Method": "GET"
}
}2. Appel POST avec body JSON :
{
"Name": "create_task",
"Type": "http",
"Parameters": { ... },
"Http": {
"Url": "http://localhost:5000/tasks",
"Method": "POST",
"Headers": {
"Content-Type": "application/json"
}
}
}3. Appel vers une API externe :
{
"Name": "check_stock",
"Type": "http",
"Parameters": { ... },
"Http": {
"Url": "https://api.example.com/stock/{symbol}",
"Method": "GET",
"Headers": {
"X-API-Key": "votre-clé-api"
}
}
}Pour des outils avec logique personnalisée en C# :
- Déclarez l'outil dans
appsettings.jsonavec"Type": "builtin" - Implémentez la logique dans
Services/Tools/ToolExecutorService.cs - L'outil sera automatiquement disponible :
- Via l'API REST (
POST /api/tools/{nom}) - Via l'IA vocale (Marvin peut l'appeler)
- Via l'API REST (
Quand vous parlez à Marvin, il peut automatiquement :
- Appeler vos APIs locales (localhost)
- Utiliser les outils intégrés (météo, heure, calcul)
- Vous communiquer les résultats vocalement
Exemple de conversation :
- Vous : "Marvin, quelle heure est-il à Paris ?"
- Marvin : Appelle automatiquement
get_timeavectimezone: "Europe/Paris" - Marvin : "Il est actuellement 14h30 à Paris... Encore une question futile pour mon cerveau gigantesque..."
L'application utilise Polly, la bibliothèque de résilience .NET de référence, pour gérer les appels HTTP des outils avec des politiques de retry et circuit breaker.
{
"OpenAI": {
"Resilience": {
"Retry": {
"Enabled": true,
"MaxRetryAttempts": 3,
"InitialDelayMs": 100,
"MaxDelayMs": 5000
},
"CircuitBreaker": {
"Enabled": true,
"FailureThreshold": 5,
"BreakDurationSeconds": 30,
"SamplingDurationSeconds": 60
},
"Timeout": {
"Enabled": true,
"TimeoutSeconds": 30
}
}
}
}1. Retry (Politique de réessai) 🔄
- Exponential backoff : délai initial de 100ms, doublé à chaque tentative, jusqu'à 5 secondes max
- 3 tentatives par défaut
- Déclenché automatiquement pour :
- Erreurs HTTP transitoires (500, 502, 503, 504, 408)
- Timeouts
- Erreurs réseau
2. Circuit Breaker (Disjoncteur) ⚡
- Protège vos APIs contre les surcharges
- S'ouvre après 5 échecs dans une fenêtre de 60 secondes
- Reste ouvert pendant 30 secondes (aucun appel n'est effectué)
- Passe en mode "half-open" pour tester si l'API est revenue
- États :
- 🟢 Closed (fermé) : Fonctionnement normal
- 🔴 Open (ouvert) : Toutes les requêtes échouent immédiatement
- 🟡 Half-Open (semi-ouvert) : Test si le service est revenu
3. Timeout (Délai d'expiration) ⏱️
- 30 secondes par défaut par requête HTTP
- Évite les appels qui bloquent indéfiniment
Circuit Breaker (outermost)
↓
Retry Policy (middle)
↓
Timeout Policy (innermost)
↓
HTTP Request
- Le Circuit Breaker vérifie s'il doit laisser passer la requête
- La Retry Policy gère les échecs et réessaye si nécessaire
- Le Timeout limite la durée de chaque tentative
- La requête HTTP est finalement exécutée
Les politiques génèrent des logs détaillés :
[Polly Retry] Retry 1/3 after 100ms. Reason: 503 Service Unavailable
[Polly Retry] Retry 2/3 after 200ms. Reason: Timeout
[Polly Circuit Breaker] Circuit opened for 30s. Reason: 500 Internal Server Error
[Polly Circuit Breaker] Circuit half-open (testing)
[Polly Circuit Breaker] Circuit reset (closed)
[Polly Timeout] Request timed out after 30s
Vous pouvez désactiver ou ajuster chaque politique individuellement :
Désactiver le retry :
{
"Resilience": {
"Retry": {
"Enabled": false
}
}
}Augmenter le nombre de tentatives :
{
"Resilience": {
"Retry": {
"Enabled": true,
"MaxRetryAttempts": 5,
"InitialDelayMs": 200,
"MaxDelayMs": 10000
}
}
}Circuit Breaker plus agressif :
{
"Resilience": {
"CircuitBreaker": {
"Enabled": true,
"FailureThreshold": 3,
"BreakDurationSeconds": 60,
"SamplingDurationSeconds": 30
}
}
}APIs externes instables :
{
"Retry": { "MaxRetryAttempts": 5 },
"CircuitBreaker": { "FailureThreshold": 10 }
}Appels locaux rapides :
{
"Retry": { "MaxRetryAttempts": 2, "MaxDelayMs": 1000 },
"Timeout": { "TimeoutSeconds": 5 }
}Pas de retry (tests uniquement) :
{
"Retry": { "Enabled": false },
"CircuitBreaker": { "Enabled": false }
}- ✅ Fiabilité accrue : Gère automatiquement les erreurs transitoires
- ✅ Protection contre les surcharges : Le circuit breaker protège vos APIs
- ✅ Logs détaillés : Visibilité complète sur les échecs et réessais
- ✅ Configuration sans code : Tout se configure dans
appsettings.json - ✅ Standard .NET : Polly est la bibliothèque de résilience de référence
Le projet inclut plusieurs fichiers d'exemple pour différents cas d'usage :
1. APIs externes instables (appsettings.example-external-unstable.json)
{
"Resilience": {
"Retry": { "MaxRetryAttempts": 5, "InitialDelayMs": 500, "MaxDelayMs": 15000 },
"CircuitBreaker": { "FailureThreshold": 10, "BreakDurationSeconds": 60 },
"Timeout": { "TimeoutSeconds": 60 }
}
}2. Appels locaux rapides (appsettings.example-local-fast.json)
{
"Resilience": {
"Retry": { "MaxRetryAttempts": 2, "InitialDelayMs": 50, "MaxDelayMs": 500 },
"CircuitBreaker": { "FailureThreshold": 3, "BreakDurationSeconds": 10 },
"Timeout": { "TimeoutSeconds": 5 }
}
}3. Tests sans résilience (appsettings.example-no-resilience.json)
{
"Resilience": {
"Retry": { "Enabled": false },
"CircuitBreaker": { "Enabled": false },
"Timeout": { "Enabled": false }
}
}4. Production équilibrée (appsettings.example-production.json)
{
"Resilience": {
"Retry": { "MaxRetryAttempts": 4, "InitialDelayMs": 200, "MaxDelayMs": 10000 },
"CircuitBreaker": { "FailureThreshold": 7, "BreakDurationSeconds": 45 },
"Timeout": { "TimeoutSeconds": 30 }
}
}Utilisation :
# Copier un exemple
cp appsettings.example-production.json appsettings.json
# Ou fusionner la section "Resilience" dans votre config existante📖 Guide complet : Consultez Prompts/ResilienceGuide.md pour :
- Comprendre chaque paramètre
- Choisir la bonne configuration
- Éviter les pièges courants
- Tableaux de décision rapide
- Ouvrez l'application dans votre navigateur
- Sélectionnez un microphone dans la liste déroulante
- Cliquez sur "Démarrer l'écoute" (le bouton devient rouge)
- Parlez naturellement - l'IA vous répondra automatiquement
- Les transcriptions apparaissent en temps réel dans le chat
- Cliquez sur "Arrêter l'écoute" pour terminer
- 🟢 Vert : Prêt à écouter
- 🔴 Rouge : En écoute active
- 💬 Messages bleus : Vos paroles
- 💬 Messages gris : Réponses de l'IA
chatrealtime/
├── Configuration/
│ └── OpenAISettings.cs # Configuration OpenAI
├── Models/
│ ├── RealtimeEvents.cs # Événements API Realtime
│ └── ClientMessage.cs # Messages WebSocket
├── Services/
│ ├── OpenAIRealtimeService.cs # Service connexion OpenAI
│ └── RealtimeWebSocketHandler.cs # Gestion WebSocket client
├── wwwroot/
│ ├── index.html # Interface utilisateur
│ ├── styles.css # Styles CSS
│ └── app.js # Logique JavaScript
├── Program.cs # Point d'entrée
├── appsettings.json # Configuration
└── chatrealtime.csproj # Fichier projet
- Format : PCM16 (16-bit linear PCM)
- Sample Rate : 24 000 Hz
- Canaux : Mono (1 canal)
- Taille buffer : 4096 samples
- Endpoint client :
ws(s)://host/ws/realtime - Keep-alive : 120 secondes
- Format messages : JSON
{
"type": "audio",
"audio": "base64_encoded_pcm16_data"
}{
"type": "audio|transcript|status|error",
"audio": "base64_audio",
"transcript": "texte transcrit",
"role": "user|assistant",
"status": "message de statut"
}➡️ Vérifiez que vous avez bien configuré votre clé API dans appsettings.json
➡️ Autorisez l'accès au microphone dans les paramètres de votre navigateur
➡️ Vérifiez :
- Votre clé API est valide
- Vous avez accès à l'API Realtime
- Votre connexion Internet fonctionne
➡️ Vérifiez :
- Le volume de votre navigateur
- Les permissions audio du navigateur
- Que vous utilisez Chrome ou Edge
- Coût : L'API OpenAI Realtime est facturée à l'usage. Surveillez votre consommation.
- Navigateurs : Chrome et Edge recommandés pour une meilleure compatibilité
- Sécurité : Ne commitez JAMAIS votre clé API dans un repository public
- Production : Pour la production, utilisez des variables d'environnement pour stocker la clé API
Pour la production, utilisez des variables d'environnement :
export OpenAI__ApiKey="sk-proj-xxxxx"
dotnet runOu configurez dans appsettings.Development.json (non versionné) :
{
"OpenAI": {
"ApiKey": "votre-clé-ici"
}
}Ce projet est fourni à des fins éducatives et de démonstration.