質問を入力して答えを読む。これが今日、多くのアプリケーションが言語モデルと対話する方法です。リアルタイムAPIはこれを変えます。OpenAI RealtimeとGemini Liveは、ユーザーの声を話されたそのままに受け取り、会話を成立させられるほど速く、自分自身の声で応答します。これを自前で実現するには、双方向にオーディオをストリーミングし、ユーザーが話し終えたタイミングを判断し、割り込まれたときにエージェントを止め、その最中にツールを実行する必要があります。
sgcWebSockets 2026.10は、そのすべてを行う単一のコンポーネントTsgcAIVoiceAgentを追加します。聞き取り、ユーザーが1つのターンを終えたことを検出し、応答し、あなたのツールを呼び出します。Delphi 7から13まで、そして.NET向けに提供されます。
TsgcAIVoiceAgentが動作する様子です。ターン検出、barge-in、ライブ文字起こし、ツール呼び出しまで。YouTubeでも公開中。
クイックスタート
プロバイダーを選び、APIキー、声、指示を設定し、OnTranscriptを処理してStartを呼び出します。それだけで動作する音声アシスタントになります。
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は会話の両側、つまりユーザーとエージェントのライブ文字起こしを届けるので、画面は話されている内容を追うことができます。
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かGemini Live、WebSocketかWebRTC
OpenAI RealtimeとGemini Liveの2つのプロバイダーに対応しています。切り替えはプロパティ1つと対応するオプションだけで済みます。
oVoice.Provider := vapGemini;
oVoice.GeminiOptions.ApiKey := 'GEMINI_API_KEY';
oVoice.Start;
セッションはWebSocket上で動作します。OpenAIを使う場合はWebRTC上でも動作できます。
oVoice.Provider := vapOpenAI;
oVoice.Transport := vatWebRTC;
oVoice.OpenAIOptions.ApiKey := 'API_KEY';
oVoice.Start;
内蔵オーディオ、または独自のオーディオ
Windows、Android、iOSではマイクとスピーカーが内蔵されているので、上のクイックスタートだけで十分です。それ以外のプラットフォームや、オーディオが別の場所から来る場合は、デバイスをオフにしてください。アプリケーションはSendAudioでオーディオを渡し、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);
電話に出る
電話回線はG.711で話し、音声エージェントもそうです。オーディオ形式を8 kHzのmu-lawまたはA-lawに設定すれば、通話のバイト列はコードの中で何の変換もなく、そのまま入り、そのまま出ていきます。
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;
ターン検出
ターン検出は、ユーザーが話し終えてエージェントが応答すべきタイミングを判断します。4つのモードがあります。
- Server VADは、音声アクティビティ検出でターンの終わりをプロバイダーに検出させます。
- Semantic VADは沈黙だけでなく話されている内容を聞くので、文の途中の間がユーザーの発話を打ち切ることはありません。
- Client VADはクライアント側でターンを検出します。
- Manualはプッシュトゥトークです。アプリケーションがターンの終わりを決めます。
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';
プッシュトゥトークでは、ユーザーがボタンを離したときにオーディオをコミットし、応答を要求します。
procedure TForm1.btnTalkMouseUp(Sender: TObject);
begin
// ... TurnDetection.Mode is vtdManual
oVoice.CommitAudio;
oVoice.CreateResponse;
end;
Barge-in
人は話に割り込むものであり、優れた音声エージェントはそれを許します。barge-inを使うと、ユーザーが話し始めたときにエージェントは発話を止め、応答はキャンセルされます。応答はユーザーが実際に聞いた部分だけに切り詰められるので、会話履歴に、実際には再生されなかった言葉をエージェントが言ったことになってしまうことはありません。
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;
ツール
音声エージェントは何かを実行できるようになって初めて役立ちます。名前、説明、パラメーター用のJSONスキーマでツールを宣言し、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;
ツールはMCPサーバーから提供することもできます。MCPクライアントを割り当てると、そのツールが自動的にエージェントに提供されます。
oMCP := TsgcWSAPIClient_MCP.Create(nil);
oMCP.MCPOptions.HttpOptions.URL := 'https://localhost:5001/mcp';
oVoice.MCPClient := oMCP;
oVoice.Start;
デモ: 音声ショップアシスタント
Demos\15.AI\02.Applications\09.VoiceAgentにあるDelphiデモは、話しかけて使うショップアシスタントです。TDataSetに保持された商品カタログの上で動作し、エージェントに4つのツールを与えます。カタログを検索するfind_products、1つの商品を読み取るget_product、在庫を変更するupdate_stock、補充が必要な商品を一覧するcount_low_stockです。何が少なくなっているか尋ねてから、ある商品を補充するように伝えて、応答している間にグリッドが変わる様子を見てください。
提供時期
TsgcAIVoiceAgentは、Delphi 7から13まで、そして.NET向けに、sgcWebSockets 2026.10のEnterpriseエディションで登場します。このリリースのその他の新機能はsgcWebSockets 2026.10の記事に、その他のAIコンポーネントはAI製品ページにあります。
次に読む
- Delphiと.NET向けのAIドキュメント抽出、これも2026.10の新機能です
- Delphiと.NET向けのServer-Sent Eventsクライアント
ご質問やご意見はありますか。お問い合わせください。コードを書いた本人から返信いたします。
