Um agente de voz em tempo real para Delphi e .NET

· Componentes
Um agente de voz em tempo real para Delphi e .NET

Digitar uma pergunta e ler a resposta é como a maioria das aplicações conversa hoje com um modelo de linguagem. As APIs em tempo real mudam isso. O OpenAI Realtime e o Gemini Live recebem a voz do usuário à medida que ela é falada e respondem com uma voz própria, rápido o suficiente para manter uma conversa. Construir isso à mão significa transmitir áudio nos dois sentidos, decidir quando o usuário terminou, parar o agente quando ele é interrompido e executar tools no meio de tudo isso.

O sgcWebSockets 2026.10 adiciona o TsgcAIVoiceAgent, um componente que faz tudo isso. Ele escuta, detecta quando o usuário terminou um turno, responde por voz e chama suas tools. Está disponível para Delphi 7 a 13 e para .NET.

O TsgcAIVoiceAgent em ação: detecção de turno, barge-in, transcrições ao vivo e chamadas de tools. Também no YouTube.

Início Rápido

Escolha o provedor, defina a chave de API, uma voz e as instruções, trate o OnTranscript e chame Start. Isso já é um assistente de voz funcionando.

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;

O OnTranscript entrega transcrições ao vivo dos dois lados da conversa, o usuário e o agente, para que a tela possa acompanhar o que está sendo dito:

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

Dois provedores são suportados, OpenAI Realtime e Gemini Live. Trocar de um para outro é uma property e as opções correspondentes:

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

A sessão roda sobre WebSocket. Com a OpenAI, também pode rodar sobre WebRTC:

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

Áudio integrado, ou o seu próprio

No Windows, Android e iOS, o microfone e o alto-falante já vêm integrados, então o início rápido acima é tudo o que você precisa. Em qualquer outra plataforma, ou quando o áudio vem de outro lugar, desligue o dispositivo. Sua aplicação envia áudio com SendAudio e recebe a resposta em 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);

Atendendo o telefone

A telefonia fala G.711, e o agente de voz também. Defina o formato de áudio como mu-law ou A-law a 8 kHz, e os bytes de uma chamada entram e saem direto, sem nenhuma conversão no seu código.

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;

Detecção de turno

A detecção de turno decide quando o usuário terminou de falar e o agente deve responder. Existem quatro modos.

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

Com push-to-talk, confirme o áudio e peça uma resposta quando o usuário soltar o botão:

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

Barge-in

As pessoas interrompem, e um bom agente de voz permite isso. Com o barge-in, quando o usuário começa a falar o agente para de falar e a resposta é cancelada. A resposta também é truncada até o ponto que o usuário realmente ouviu, para que o histórico da conversa não afirme que o agente disse palavras que nunca chegaram a ser reproduzidas.

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

Um agente de voz se torna útil quando consegue fazer coisas. Declare uma tool com um nome, uma descrição e um schema JSON para seus parâmetros, e responda a ela em 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;

As tools também podem vir de um servidor MCP. Atribua um cliente MCP e suas tools são oferecidas ao agente automaticamente:

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

oVoice.MCPClient := oMCP;
oVoice.Start;

A demo: um assistente de voz para uma loja

A demo em Delphi em Demos\15.AI\02.Applications\09.VoiceAgent é um assistente de loja com quem você conversa. Ela funciona sobre um catálogo de produtos guardado em um TDataSet e dá ao agente quatro tools: find_products para buscar no catálogo, get_product para ler um produto, update_stock para alterar o estoque, e count_low_stock para listar os produtos que precisam de reposição. Pergunte a ela o que está acabando, depois diga para repor um produto, e veja a grade mudar enquanto ela responde.

Disponibilidade

O TsgcAIVoiceAgent chega no sgcWebSockets 2026.10, para Delphi 7 a 13 e para .NET, na edição Enterprise. O restante das novidades da versão está no post do sgcWebSockets 2026.10, e o resto dos componentes de IA está na página de produto de IA.

Leia também

Dúvidas ou feedback? Entre em contato. Você receberá uma resposta das pessoas que escreveram o código.