Ostern mit GPT Realtime. Mein kleines Voicebot Experiment in Azure
Ein Osterwochenende, ein 1st Level Support Bot in Azure und ehrliche Learnings aus einer Dev Spielwiese.
Ostern war dieses Jahr anders. Kein Kurztrip, kein großes Projekt auf der Arbeit. Dafür eine einfache Frage, die mich seit Wochen nicht losließ. Wie weit kommt man mit GPT Realtime in Azure, wenn man einfach mal anfängt?
Am Ende standen sechs Azure Ressourcen, eine deutsche Rufnummer und ein kleiner Bot, der echte Anrufe entgegen nimmt. Sein Name im Prompt ist Chantal. Sie ist freundlich, geduldig und sie weiß wann sie einen Menschen holen muss.
Das ist die Geschichte dazu, ehrlich und ohne Hochglanz. Es ist eine Dev Spielwiese, keine Referenzarchitektur.
Was dabei raus gekommen ist
Ein 1st Level Support Bot, der drei Dinge kann.
- Anrufer über die Telefonnummer in Entra ID nachschlagen und persönlich begrüßen
- Ein Ticket in SharePoint anlegen wenn der Fall nicht sofort lösbar ist
- Den Anruf an eine echte Person weiterleiten wenn es eskaliert
Alles in Azure, alles pay per use und über eine spontan registrierte deutsche Nummer erreichbar.
Warum Azure
Ich hatte mehrere Gründe.
Erstens die Free Pläne. Für ein Experiment zahlt man in Azure fast nichts, wenn man weiß was man tut. GPT Realtime rechnet nach Audio Sekunden ab, ACS nach Anrufminuten, Container Apps haben ein Free Tier. Das Risiko einer teuren Fehleinstellung ist überschaubar.
Zweitens die Nähe zu M365. Mein Bot spricht mit Microsoft Graph, legt Tickets in SharePoint an und identifiziert Anrufer über Entra ID. Das würde in anderen Clouds unnötig kompliziert.
Drittens das schnelle Setup. Deutsche Rufnummer in ACS, Container App Revision deployt, Event Grid Webhook konfiguriert. An einem Abend steht die Grund Infrastruktur.
Der Stack auf einen Blick

Sechs Ressourcen in einer Resource Group, verteilt auf zwei Regionen.
- Azure Communication Services für Telefonie und Audio Streaming. Die DE Nummer kostet ein paar Cent pro Monat
- Azure OpenAI in Sweden Central weil dort GPT Realtime verfügbar ist. Der Rest liegt in Germany West Central
- Azure Container Registry als Ablage für das Docker Image
- Azure Container App und Container Apps Environment als Laufzeit für das Python Backend
- Azure Storage Account mit Table Storage für Call Logs
- Log Analytics Workspace für Container Metrics und Diagnosen
Wie ein Anruf durch das System läuft
Der Flow ist einfacher als es klingt.
- Jemand ruft die DE Nummer an. ACS empfängt den Call
- Event Grid feuert einen Webhook an meinen Endpoint
POST /api/calls/incoming - Mein FastAPI Backend nimmt den Anruf an und öffnet eine WebSocket Verbindung zurück zu ACS
- Parallel öffnet das Backend eine zweite WebSocket Verbindung zu Azure OpenAI Realtime
- Audio fließt zwischen diesen beiden WebSockets hin und her, 24 kHz PCM16
- Wenn der Bot eine Funktion aufruft wie Ticket anlegen, Anrufer nachschlagen oder weiterleiten, läuft das synchron über Microsoft Graph oder den ACS Transfer
- Am Ende landet alles in Azure Table Storage als Call Log, strukturiert nach Monat
flowchart TD
A(["Anrufer
DE Nummer"]):::user
subgraph AZ["Azure Resource Group"]
direction TB
ACS["ACS
Communication Services"]:::azure
API["FastAPI Backend
Container App"]:::app
GPT["Azure OpenAI
GPT Realtime 1.5"]:::ai
TS[("Table Storage
Call Logs")]:::data
end
subgraph M["Microsoft 365"]
direction TB
MG{{"Microsoft Graph"}}:::m365
ENTRA["Entra ID
Caller Lookup"]:::m365
SP["SharePoint
Ticket Liste"]:::m365
end
A ==>|"PSTN Anruf"| ACS
ACS -->|"Event Grid Webhook"| API
API <==>|"WebSocket Audio 24kHz"| ACS
API <==>|"WebSocket Realtime API"| GPT
API -->|"Function Call"| MG
MG --> ENTRA
MG --> SP
API -->|"Call Log pro Monat"| TS
classDef azure fill:#0078d4,stroke:#106ebe,stroke-width:2px,color:#fff
classDef m365 fill:#0364b8,stroke:#032f5e,stroke-width:2px,color:#fff
classDef app fill:#e67e22,stroke:#d35400,stroke-width:2px,color:#fff
classDef ai fill:#8b5cf6,stroke:#6d28d9,stroke-width:2px,color:#fff
classDef data fill:#475569,stroke:#1e293b,stroke-width:2px,color:#fff
classDef user fill:#10b981,stroke:#047857,stroke-width:2px,color:#fff
Der Trick liegt in dem dualen WebSocket Setup. Mit asyncio.wait(..., return_when=FIRST_COMPLETED) schließt die OpenAI Session sofort wenn der Anrufer auflegt. Keine offenen Sessions die stille Sekunden abrechnen.
Das Framework in Code
Das Backend ist ein schlankes FastAPI Projekt mit klarer Trennung.
- main.py definiert die HTTP und WebSocket Endpoints
- call_handler.py spricht mit dem ACS Call Automation SDK
- audio_stream.py ist die Brücke zwischen ACS und OpenAI
- realtime_client.py kapselt die GPT Realtime Session
- services/ enthält Graph Lookup, Ticket Erstellung und Call Logger
- prompts/ hält den System Prompt und eine kleine Wissensbasis als Textdateien
Insgesamt deutlich unter 2000 Zeilen Python. Das war bewusst so. Ich wollte verstehen was passiert und nicht eine Library Magic Show.
GitHub Actions als Deployment Pfad
Push auf main, das war die Messlatte.
Die Pipeline ist in wenigen Zeilen beschrieben.
- Azure Login über einen Service Principal
- Docker Build und Push in die ACR mit Git SHA als Tag
- Container App Update auf das neue Image
Keine manuelle Freigabe, keine Slots, kein Kubernetes. Für ein Lab genau richtig. Der erste Cold Start dauert ein paar Sekunden, danach fühlt sich der Bot schnell genug an.
Wo es richtig weh tat
Das schwierigste am gesamten Projekt war nicht Azure. Es war der System Prompt.
GPT Realtime ist schnell. Richtig schnell. Der Bot nimmt dich nach 300 Millisekunden ins Wort wenn du falsch priorisierst. Bis die Persona sitzt, bis das Modell die richtigen Funktionen zum richtigen Zeitpunkt wählt, bis es nicht zu früh eskaliert und nicht zu lange plaudert, habe ich viele Abende gebraucht.
Typische Fehler die ich immer wieder aus dem Prompt heraus feilen musste.
- Zu früh ticketfähig. Der Bot legt ein Ticket an bevor der Nutzer sein Problem fertig erklärt hat
- Zu spät eskaliert. Der Bot versucht technische Magie statt einfach weiterzuleiten
- Funktion vergessen. Der Bot fragt drei Mal nach dem Namen obwohl er die Telefonnummer hat mit der er nachschlagen könnte
- Zu förmlich oder zu locker. Das Du Sie Verhältnis ist in echten Gesprächen eine Kunst
Ich habe irgendwann angefangen, die Gespräche mitzulesen und aus den Protokollen die typischen Ausrutscher zurück in den Prompt zu bauen. Das hat mehr geholfen als jede Theorie.
Kleine technische Perlen
Drei Sachen die mich beeindruckt haben.
Barge in funktioniert. Wenn der Anrufer dem Bot ins Wort fällt, hört der Bot sofort auf zu sprechen. Das basiert auf dem OpenAI Event input_audio_buffer.speech_started, das einen StopAudio an ACS triggert. Keine Zeile mehr Code, aber gefühlt 50 Prozent mehr Natürlichkeit.
Caller Identification passiert vor dem Hallo. Microsoft Graph liefert aus der Rufnummer den Namen, die Abteilung und die Sprachpräferenz. Der Prompt wird damit personalisiert. Wenn du den Bot anrufst und er dich mit deinem Namen begrüßt, ist das keine Magie, sondern ein simpler Graph Lookup bevor die OpenAI Session startet.
Call Transfer mit Source Caller ID. ACS verlangt bei PSTN zu PSTN Transfer die Source Nummer, sonst verweigert der Carrier die Vermittlung. Drei Zeilen Code, einen Abend Fehlersuche.
Dev Spielwiese versus Production
Jetzt der ehrliche Teil. Was wäre nötig bevor dieser Bot in einem echten Unternehmen Kunden bedient?
| Thema | Azure Lösung |
|---|---|
| API Endpoint absichern | Azure AD Auth oder API Key für /api/calls/logs |
| Secrets Management | Azure Key Vault statt Env Vars für Connection Strings und API Keys |
| Netzwerk | VNet Integration für Container App, Private Endpoints für Storage und OpenAI |
| Backup Call Logs | Storage Account Soft Delete, Versionierung, Geo Redundanz (GRS) |
| Backup Config | Git (schon erledigt), Key Vault Soft Delete |
| Zugriffssteuerung | Managed Identity statt Client Secrets für Graph und Storage |
| Monitoring | Azure Monitor und Alerts auf Fehlerrate, Latenz, Kosten Limit |
| Audit Log | Azure Activity Log und Diagnostic Settings |
| Secret Rotation | Key Vault mit automatischer Rotation für Graph Client Secret |
| DDoS und Rate Limiting | Azure Front Door oder Container App Auth |
Keines dieser Themen ist hart. Aber jedes ist ein eigener kleiner Tag Arbeit. Genau das ist der Unterschied zwischen funktioniert und ist produktionsreif.
Was ich mitnehme
Drei Erkenntnisse aus dem Oster Experiment.
Erstens, der schwierigste Teil war nicht das Modell, sondern die Funktionsauswahl. GPT Realtime kann technisch fast alles. Die eigentliche Arbeit steckt darin, dem Bot beizubringen wann er welche Funktion wirklich ausführt. Ticket anlegen, Anrufer nachschlagen, Weiterleitung. Jede Aktion braucht im Prompt einen klaren Auslöser und eine klare Abschlussbedingung, sonst ruft der Bot Funktionen zu früh, zu spät oder gar nicht auf.
Zweitens, Azure ist für dieses Szenario eine erstaunlich gute Plattform. ACS macht PSTN zu WebSocket trivial. Graph und SharePoint machen die M365 Integration schnell. Container Apps sind schlank genug für Dev und skaliert genug für Pilot.
Drittens, der System Prompt ist die eigentliche Arbeit. Die Infrastruktur hatte ich an einem Wochenende. Den Prompt an den Punkt, an dem ich ihm auch eine echte Eskalation zumuten würde, habe ich fast zwei Wochen lang gefeilt. Jeden Abend wenn die Familie schlief, ein Protokoll aufgemacht, eine Formulierung verschärft, neue Testanrufe gemacht.
Das ist aktuell der Stand. Ob ich daraus mehr mache weiß ich noch nicht. Als Experiment reicht es mir erstmal.
Train Hard, Work Smart.