Zum Inhalt springen
Zurück zum Blog

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.

AzureKI
Ostern mit GPT Realtime. Mein kleines Voicebot Experiment in Azure

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

Azure Resource Group rg-voiceagent mit sechs Ressourcen in Germany West Central und Sweden Central

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.

  1. Jemand ruft die DE Nummer an. ACS empfängt den Call
  2. Event Grid feuert einen Webhook an meinen Endpoint POST /api/calls/incoming
  3. Mein FastAPI Backend nimmt den Anruf an und öffnet eine WebSocket Verbindung zurück zu ACS
  4. Parallel öffnet das Backend eine zweite WebSocket Verbindung zu Azure OpenAI Realtime
  5. Audio fließt zwischen diesen beiden WebSockets hin und her, 24 kHz PCM16
  6. Wenn der Bot eine Funktion aufruft wie Ticket anlegen, Anrufer nachschlagen oder weiterleiten, läuft das synchron über Microsoft Graph oder den ACS Transfer
  7. 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.

  1. Azure Login über einen Service Principal
  2. Docker Build und Push in die ACR mit Git SHA als Tag
  3. 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.

Hat dir der Artikel geholfen? Teile ihn mit deinem Netzwerk.

LinkedIn X