Un agente vocale in tempo reale per Delphi e .NET

· Componenti
Un agente vocale in tempo reale per Delphi e .NET

Digitare una domanda e leggere la risposta è il modo in cui oggi la maggior parte delle applicazioni parla con un modello linguistico. Le API in tempo reale cambiano questo scenario. OpenAI Realtime e Gemini Live prendono la voce dell'utente mentre viene pronunciata e rispondono con una voce propria, abbastanza veloce da sostenere una conversazione. Arrivarci a mano significa trasmettere l'audio in streaming in entrambe le direzioni, decidere quando l'utente ha finito, fermare l'agente quando viene interrotto ed eseguire strumenti nel mezzo di tutto questo.

sgcWebSockets 2026.10 aggiunge TsgcAIVoiceAgent, un componente che fa tutto questo. Ascolta, rileva quando l'utente ha finito un turno, risponde a voce e chiama i tuoi strumenti. È disponibile per Delphi da 7 a 13 e per .NET.

TsgcAIVoiceAgent in azione: rilevamento dei turni, barge-in, trascrizioni live e chiamate a strumenti. Anche su YouTube.

Avvio rapido

Scegli il provider, imposta la chiave API, una voce e le istruzioni, gestisci OnTranscript e chiama Start. Questo è già un assistente vocale funzionante.

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 consegna le trascrizioni live di entrambi i lati della conversazione, l'utente e l'agente, così lo schermo può seguire cosa viene detto:

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 o Gemini Live, WebSocket o WebRTC

Sono supportati due provider, OpenAI Realtime e Gemini Live. Passare dall'uno all'altro richiede una sola proprietà e le opzioni corrispondenti:

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

La sessione funziona su WebSocket. Con OpenAI può funzionare anche su WebRTC:

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

Audio integrato, o il tuo

Su Windows, Android e iOS il microfono e l'altoparlante sono integrati, quindi l'avvio rapido descritto sopra è tutto ciò che serve. Su qualsiasi altra piattaforma, o quando l'audio arriva da un'altra fonte, disattiva il device. La tua applicazione fornisce l'audio con SendAudio e riceve la risposta 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);

Rispondere al telefono

La telefonia parla G.711, e così fa l'agente vocale. Imposta il formato audio su mu-law o A-law a 8 kHz e i byte di una chiamata entrano ed escono direttamente, senza alcuna conversione nel tuo codice.

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;

Rilevamento dei turni

Il rilevamento dei turni decide quando l'utente ha finito di parlare e l'agente deve rispondere. Ci sono quattro modalità.

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';

Con il push to talk, esegui il commit dell'audio e richiedi una risposta quando l'utente rilascia il pulsante:

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

Barge-in

Le persone interrompono, e un buon agente vocale glielo permette. Con il barge-in, quando l'utente inizia a parlare l'agente smette di parlare e la risposta viene annullata. La risposta viene anche troncata a ciò che l'utente ha effettivamente sentito, così la cronologia della conversazione non afferma che l'agente ha detto parole che non sono mai state riprodotte.

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;

Strumenti

Un agente vocale diventa utile quando può fare delle cose. Dichiara uno strumento con un nome, una descrizione e uno schema JSON per i suoi parametri, e rispondi 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;

Gli strumenti possono arrivare anche da un server MCP. Assegna un client MCP e i suoi strumenti vengono offerti automaticamente all'agente:

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

oVoice.MCPClient := oMCP;
oVoice.Start;

La demo: un assistente vocale per un negozio

La demo Delphi in Demos\15.AI\02.Applications\09.VoiceAgent è un assistente per un negozio con cui parli. Funziona su un catalogo prodotti contenuto in un TDataSet e dà all'agente quattro strumenti: find_products per cercare nel catalogo, get_product per leggere un prodotto, update_stock per modificare lo stock, e count_low_stock per elencare i prodotti da riordinare. Chiedigli cosa sta scarseggiando, poi digli di riordinare un prodotto, e guarda la griglia cambiare mentre risponde.

Disponibilità

TsgcAIVoiceAgent arriva in sgcWebSockets 2026.10, per Delphi da 7 a 13 e per .NET, nell'edizione Enterprise. Tutto il resto di nuovo nella release è nel post sgcWebSockets 2026.10, e il resto dei componenti AI è nella pagina di prodotto AI.

Continua a leggere

Domande o feedback? Mettiti in contatto. Riceverai una risposta dalle persone che hanno scritto il codice.