Agent głosowy w czasie rzeczywistym dla Delphi i .NET

· Komponenty
Agent głosowy w czasie rzeczywistym dla Delphi i .NET

Wpisanie pytania i przeczytanie odpowiedzi to sposób, w jaki większość aplikacji dziś rozmawia z modelem językowym. API czasu rzeczywistego to zmieniają. OpenAI Realtime i Gemini Live przyjmują głos użytkownika w chwili, gdy jest wypowiadany, i odpowiadają własnym głosem, na tyle szybko, by podtrzymać rozmowę. Zbudowanie tego ręcznie oznacza przesyłanie strumieniowe audio w obu kierunkach, decydowanie, kiedy użytkownik skończył, zatrzymywanie agenta, gdy zostanie przerwany, oraz uruchamianie narzędzi w trakcie tego wszystkiego.

sgcWebSockets 2026.10 dodaje TsgcAIVoiceAgent, jeden komponent, który robi to wszystko. Słucha, wykrywa, kiedy użytkownik skończył turę, odpowiada głosem i wywołuje twoje narzędzia. Jest dostępny dla Delphi od 7 do 13 oraz dla .NET.

TsgcAIVoiceAgent w akcji: wykrywanie tur, barge-in, transkrypcje na żywo i wywołania narzędzi. Również w serwisie YouTube.

Szybki start

Wybierz dostawcę, ustaw klucz API, głos i instrukcje, obsłuż OnTranscript i wywołaj Start. To już działający asystent głosowy.

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 dostarcza transkrypcje na żywo obu stron rozmowy, użytkownika i agenta, dzięki czemu ekran może śledzić to, co jest mówione:

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

Obsługiwani są dwaj dostawcy, OpenAI Realtime i Gemini Live. Przełączenie to jedna właściwość i odpowiadające jej opcje:

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

Sesja działa przez WebSocket. Z OpenAI może też działać przez WebRTC:

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

Wbudowane audio lub własne

W systemach Windows, Android i iOS mikrofon i głośnik są wbudowane, więc powyższy szybki start to wszystko, czego potrzebujesz. Na każdej innej platformie, lub gdy audio pochodzi skądinąd, wyłącz urządzenie. Twoja aplikacja dostarcza audio za pomocą SendAudio i odbiera odpowiedź w 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);

Odbieranie telefonu

Telefonia mówi G.711, podobnie jak agent głosowy. Ustaw format audio na mu-law lub A-law przy 8 kHz, a bajty połączenia wchodzą i wychodzą bezpośrednio, bez żadnej konwersji w twoim kodzie.

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;

Wykrywanie tury

Wykrywanie tury decyduje, kiedy użytkownik skończył mówić i agent powinien odpowiedzieć. Są cztery tryby.

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

Przy push-to-talk zatwierdź audio i poproś o odpowiedź, gdy użytkownik zwolni przycisk:

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

Barge-in

Ludzie przerywają, a dobry agent głosowy im na to pozwala. Dzięki barge-in, gdy użytkownik zaczyna mówić, agent przestaje mówić, a odpowiedź zostaje anulowana. Odpowiedź jest też ucinana do tego, co użytkownik faktycznie usłyszał, więc historia rozmowy nie twierdzi, że agent powiedział słowa, które nigdy nie zostały odtworzone.

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;

Narzędzia

Agent głosowy staje się użyteczny, gdy potrafi coś zrobić. Zadeklaruj narzędzie z nazwą, opisem i schematem JSON dla jego parametrów, a następnie odpowiedz na nie w 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;

Narzędzia mogą też pochodzić z serwera MCP. Przypisz klienta MCP, a jego narzędzia zostaną automatycznie zaoferowane agentowi:

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

oVoice.MCPClient := oMCP;
oVoice.Start;

Demo: asystent głosowy sklepu

Demo Delphi w Demos\15.AI\02.Applications\09.VoiceAgent to asystent sklepu, z którym rozmawiasz. Działa na katalogu produktów przechowywanym w TDataSet i daje agentowi cztery narzędzia: find_products do przeszukiwania katalogu, get_product do odczytu jednego produktu, update_stock do zmiany stanu magazynowego oraz count_low_stock do wylistowania produktów wymagających uzupełnienia. Zapytaj go, czego zaczyna brakować, a potem każ mu uzupełnić dany produkt i obserwuj, jak siatka zmienia się w trakcie odpowiedzi.

Dostępność

TsgcAIVoiceAgent pojawia się w sgcWebSockets 2026.10, dla Delphi od 7 do 13 i dla .NET, w edycji Enterprise. Co jeszcze nowego jest w tym wydaniu, znajdziesz we wpisie o sgcWebSockets 2026.10, a reszta komponentów AI jest na stronie produktu AI.

Czytaj dalej

Masz pytania lub uwagi? Skontaktuj się z nami. Otrzymasz odpowiedź od osób, które napisały ten kod.