Een realtime spraakagent voor Delphi en .NET

· Componenten
Een realtime spraakagent voor Delphi en .NET

Een vraag typen en het antwoord lezen is hoe de meeste applicaties vandaag met een taalmodel praten. De realtime API's veranderen dat. OpenAI Realtime en Gemini Live nemen de stem van de gebruiker op zoals die wordt uitgesproken en antwoorden met een eigen stem, snel genoeg om een gesprek te voeren. Dit met de hand bouwen betekent audio in beide richtingen streamen, bepalen wanneer de gebruiker klaar is, de agent stoppen wanneer die wordt onderbroken, en tools uitvoeren midden in dat alles.

sgcWebSockets 2026.10 voegt TsgcAIVoiceAgent toe, één component dat dat allemaal doet. Het luistert, detecteert wanneer de gebruiker een beurt heeft afgerond, praat terug en roept je tools aan. Het is beschikbaar voor Delphi 7 tot en met 13 en voor .NET.

TsgcAIVoiceAgent in actie: beurtdetectie, barge-in, live transcripties en tool calls. Ook op YouTube.

Aan de slag

Kies de provider, stel de API-sleutel in, een stem en de instructies, verwerk OnTranscript en roep Start aan. Dat is al een werkende spraakassistent.

oVoice := TsgcAIVoiceAgent.Create(nil);
oVoice.Provider := vapOpenAI;
oVoice.OpenAIOptions.ApiKey := 'API_KEY';
oVoice.OpenAIOptions.Voice := 'alloy';
oVoice.Instructions := 'You are a friendly assistant. Answer briefly.';
oVoice.OnTranscript := oVoiceTranscript;
oVoice.OnError := oVoiceError;
oVoice.Start;

OnTranscript levert live transcripties van beide kanten van het gesprek, de gebruiker en de agent, zodat het scherm kan volgen wat er gezegd wordt:

procedure TForm1.oVoiceTranscript(Sender: TObject;
  aRole: TsgcAIVoiceAgentRole; const aText: string; aFinal: Boolean);
begin
  if aFinal then
  begin
    if aRole = varUser then
      Memo1.Lines.Add('You: ' + aText)
    else
      Memo1.Lines.Add('Agent: ' + aText);
  end;
end;

OpenAI Realtime of Gemini Live, WebSocket of WebRTC

Er worden twee providers ondersteund, OpenAI Realtime en Gemini Live. Overschakelen is één property en de bijbehorende opties:

oVoice.Provider := vapGemini;
oVoice.GeminiOptions.ApiKey := 'GEMINI_API_KEY';
oVoice.Start;

De sessie loopt over WebSocket. Met OpenAI kan die ook over WebRTC lopen:

oVoice.Provider := vapOpenAI;
oVoice.Transport := vatWebRTC;
oVoice.OpenAIOptions.ApiKey := 'API_KEY';
oVoice.Start;

Ingebouwde audio, of je eigen

Op Windows, Android en iOS zijn de microfoon en de luidspreker ingebouwd, dus aan de slag hierboven is alles wat je nodig hebt. Op elk ander platform, of wanneer de audio ergens anders vandaan komt, schakel je het apparaat uit. Je applicatie levert audio aan met SendAudio en ontvangt het antwoord in OnAudioOutput.

oVoice.Audio.UseDevice := False;
oVoice.Audio.Format := vafPCM16;
oVoice.Audio.SampleRate := 16000;
oVoice.Start;

// ... 20 ms of PCM16 mono at 16 kHz = 640 bytes
SetLength(vChunk, 640);
FillChar(vChunk[0], Length(vChunk), 0);
oVoice.SendAudio(vChunk);

De telefoon beantwoorden

Telefonie spreekt G.711, en de spraakagent ook. Stel het audioformaat in op mu-law of A-law op 8 kHz, en de bytes van een gesprek gaan er rechtstreeks in en rechtstreeks uit, zonder enige conversie in je code.

oVoice.Provider := vapOpenAI;
oVoice.OpenAIOptions.ApiKey := 'API_KEY';
oVoice.Instructions := 'You answer the phone of a restaurant and take bookings.';
oVoice.Audio.UseDevice := False;
oVoice.Audio.Format := vafG711ULaw;
oVoice.OnAudioOutput := oVoicePhoneAudioOutput;
oVoice.Start;
procedure TForm1.DoPhoneAudioReceived(const aData: TBytes);
begin
  // ... G.711 mu-law, 8 kHz, as it arrives from the RTP stream of the call
  oVoice.SendAudio(aData);
end;

procedure TForm1.oVoicePhoneAudioOutput(Sender: TObject;
  const aData: TBytes);
begin
  // ... already G.711 mu-law at 8 kHz: send it back to the caller
  SendToPhone(aData);
end;

Beurtdetectie

Beurtdetectie bepaalt wanneer de gebruiker klaar is met spreken en de agent moet antwoorden. Er zijn vier modi.

oVoice.TurnDetection.Mode := vtdServerVAD;
oVoice.TurnDetection.Threshold := 0.6;
oVoice.TurnDetection.PrefixPaddingMs := 300;
oVoice.TurnDetection.SilenceDurationMs := 700;
oVoice.TurnDetection.Mode := vtdSemanticVAD;
oVoice.TurnDetection.Eagerness := 'low';

Bij push-to-talk bevestig je de audio en vraag je om een antwoord wanneer de gebruiker de knop loslaat:

procedure TForm1.btnTalkMouseUp(Sender: TObject);
begin
  // ... TurnDetection.Mode is vtdManual
  oVoice.CommitAudio;
  oVoice.CreateResponse;
end;

Barge-in

Mensen onderbreken, en een goede spraakagent laat dat toe. Met barge-in stopt de agent met praten en wordt het antwoord geannuleerd zodra de gebruiker begint te spreken. Het antwoord wordt ook afgekapt tot wat de gebruiker daadwerkelijk hoorde, zodat de gespreksgeschiedenis niet beweert dat de agent woorden zei die nooit zijn afgespeeld.

oVoice.BargeIn := True;
oVoice.OnTurnStarted := oVoiceTurnStarted;
oVoice.OnTurnEnded := oVoiceTurnEnded;
oVoice.OnBargeIn := oVoiceBargeIn;
procedure TForm1.oVoiceBargeIn(Sender: TObject);
begin
  Memo1.Lines.Add('The user interrupted the agent.');
end;

Tools

Een spraakagent wordt pas nuttig als hij dingen kan doen. Declareer een tool met een naam, een beschrijving en een JSON-schema voor de parameters, en beantwoord die in OnToolCall.

procedure TForm1.StartWeatherAssistant;
var
  oTool: TsgcAIVoiceAgentTool;
begin
  oVoice.Provider := vapOpenAI;
  oVoice.OpenAIOptions.ApiKey := 'API_KEY';
  oVoice.Instructions := 'You are a weather assistant. ' +
    'Call get_weather to know the weather of a city before answering.';

  oVoice.Tools.Clear;
  oTool := oVoice.Tools.Add;
  oTool.Name := 'get_weather';
  oTool.Description := 'Returns the current weather of a city.';
  oTool.Parameters := '{"type":"object","properties":' +
    '{"city":{"type":"string","description":"Name of the city"}},' +
    '"required":["city"]}';

  oVoice.OnToolCall := oVoiceWeatherToolCall;
  oVoice.OnTranscript := oVoiceTranscript;
  oVoice.Start;
end;
procedure TForm1.oVoiceWeatherToolCall(Sender: TObject;
  const aCallId, aName, aArguments: string; var aResult: string;
  var aHandled: Boolean);
var
  oJSON: TsgcJSON;
  vCity: string;
begin
  if aName <> 'get_weather' then
    Exit; // ... not mine: the MCP client, if any, gets the call

  oJSON := TsgcJSON.Create(nil);
  Try
    oJSON.Read(aArguments);
    vCity := '';
    if Assigned(oJSON.Node['city']) then
      vCity := oJSON.Node['city'].Value;
  Finally
    oJSON.Free;
  End;

  // ... replace with a call to your weather service
  aResult := '{"city":"' + vCity + '","temperature":21,"sky":"clear"}';
  aHandled := True;
end;

Tools kunnen ook van een MCP-server komen. Wijs een MCP-client toe en de tools ervan worden automatisch aan de agent aangeboden:

oMCP := TsgcWSAPIClient_MCP.Create(nil);
oMCP.MCPOptions.HttpOptions.URL := 'https://localhost:5001/mcp';

oVoice.MCPClient := oMCP;
oVoice.Start;

De demo: een spraakassistent voor een winkel

De Delphi-demo in Demos\15.AI\02.Applications\09.VoiceAgent is een winkelassistent waarmee je praat. Hij werkt op een productcatalogus die wordt bijgehouden in een TDataSet en geeft de agent vier tools: find_products om de catalogus te doorzoeken, get_product om één product te lezen, update_stock om de voorraad te wijzigen, en count_low_stock om de producten te tonen die moeten worden aangevuld. Vraag hem wat er bijna op is, zeg hem dan een product aan te vullen, en kijk hoe het raster verandert terwijl hij antwoordt.

Beschikbaarheid

TsgcAIVoiceAgent komt in sgcWebSockets 2026.10, voor Delphi 7 tot en met 13 en voor .NET, in de Enterprise-editie. Wat er verder nieuw is in deze release staat in de post over sgcWebSockets 2026.10, en de rest van de AI-componenten staat op de AI-productpagina.

Lees verder

Vragen of feedback? Neem contact op. Je krijgt een antwoord van de mensen die de code hebben geschreven.