输入一个问题,读取回答,这是如今大多数应用与语言模型对话的方式。实时 API 改变了这一点。OpenAI Realtime 和 Gemini Live 会在用户说话的同时接收语音,并用自己的语音作答,速度快到足以维持一场对话。要靠手工实现这一切,意味着要在两个方向上流式传输音频、判断用户何时说完、在被打断时停止代理,并在这一切进行的同时运行工具。
sgcWebSockets 2026.10 新增了 TsgcAIVoiceAgent,一个能完成所有这些工作的组件。它会监听、检测用户何时说完一个话轮、开口回应,并调用你的工具。它面向 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。切换只需要一个属性和相应的选项:
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;
话轮检测
话轮检测决定用户何时说完话,代理应该开始回答。共有四种模式。
- 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 schema,然后在 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 中的商品目录运行,并为代理提供了四个工具:find_products 用于搜索目录,get_product 用于读取某个商品,update_stock 用于修改库存,count_low_stock 用于列出需要补货的商品。问问它什么快没货了,然后让它给某个商品补货,看着表格在它回答的同时发生变化。
可用性
TsgcAIVoiceAgent 将随 sgcWebSockets 2026.10 一起发布,面向 Delphi 7 到 13 以及 .NET,包含在 Enterprise 版本中。本次发布的其他新增内容在 sgcWebSockets 2026.10 的文章中,其余的 AI 组件在 AI 产品页面上。
延伸阅读
- 面向 Delphi 和 .NET 的 AI 文档提取,同样是 2026.10 中的新特性
- 面向 Delphi 和 .NET 的 Server-Sent Events 客户端
有疑问或反馈?联系我们。回复你的会是真正编写这些代码的人。
