在语音中跟随用户
跟随模式使机器人能够跟踪所选用户,并在他们于 Discord 语音频道之间移动时跟随。
在语音中跟随用户¶
当你希望 Discord 语音机器人跟随一个或多个已知的 Discord 用户,而不是在启动时加入固定频道或等待 /vc join 时,请使用 voice.followUsers。
{
channels: {
discord: {
voice: {
enabled: true,
followUsersEnabled: true,
followUsers: ["discord:123456789012345678"],
allowedChannels: [
{
guildId: "123456789012345678",
channelId: "234567890123456789",
},
],
},
},
},
}
行为:
followUsers接受原始 Discord 用户 ID 和discord:<id>值。OpenClaw 会在匹配语音状态事件之前规范化这两种形式。followUsersEnabled在配置了followUsers时默认为true。将其设置为false可保留已保存的列表,但停止自动语音跟随。followUsers仅控制语音驻留。它不会授予说话者访问权限或所有者权限;请单独配置commands.ownerAllowFrom以及服务器或频道中的用户和角色。- 当被跟随用户加入允许的语音频道时,OpenClaw 会加入该频道。当用户移动时,OpenClaw 会跟随移动。当当前被跟随用户断开连接时,OpenClaw 会离开。
- 如果多个被跟随用户位于同一服务器中,且当前被跟随用户离开,OpenClaw 会先移动到另一个受跟踪的被跟随用户所在频道,然后再离开该服务器。如果多个被跟随用户同时移动,则以最新观察到的语音状态事件为准。
allowedChannels仍然适用。位于不允许频道中的被跟随用户会被忽略,且由跟随拥有的会话会移动到另一个被跟随用户或离开。- OpenClaw 会在启动时以及有界间隔内协调错过的语音状态事件。协调过程会采样已配置的服务器,并限制每次运行的 REST 查询次数,因此非常大的
followUsers列表可能需要多个间隔才能收敛。 - 如果 Discord 或管理员在机器人跟随用户期间移动了机器人,OpenClaw 会重建语音会话,并在目标位置被允许时保留跟随所有权。如果机器人被移动到
allowedChannels之外,OpenClaw 会离开,并在存在已配置目标时重新加入该目标。 - DAVE 接收恢复在反复解密失败后可能会离开并重新加入同一频道。跟随拥有的会话在该恢复路径中会保留其跟随所有权,因此之后被跟随用户断开连接时仍会离开频道。
在加入模式之间选择:
- 对于个人或操作员配置,如果希望机器人在你在线时自动处于语音中,请使用
followUsers。 - 对于固定房间,请使用
autoJoin。如果机器人只应在有人类位于该房间时存在,请添加whenOccupied: true;如需始终在线的语音存在,请省略它。 - 对于一次性加入,或自动语音存在会令人意外的房间,请使用
/vc join。
Discord 语音编解码器:
- 语音接收日志显示
discord voice: opus decoder: libopus-wasm。 - 实时播放使用相同的捆绑
libopus-wasm包将原始 48 kHz 立体声 PCM 编码为 Opus,然后将数据包交给@discordjs/voice。 - 文件和提供商流播放使用 ffmpeg 转码为原始 48 kHz 立体声 PCM,然后使用
libopus-wasm生成发送到 Discord 的 Opus 数据包流。
STT 加 TTS 流水线:
- Discord PCM 捕获会被转换为 WAV 临时文件。
tools.media.audio处理 STT,例如openai/gpt-4o-mini-transcribe。- 批量捕获会遵守最大的适用音频输入限制,包括已配置的模型和 CLI 回退方案。超大的捕获会停止并给出警告,提示说更短的片段;部分音频不会被转录。此限制不会缓冲或截断直接实时流。
- 转录文本会通过 Discord 入口和路由发送,同时响应 LLM 使用一种语音输出策略运行,该策略会隐藏 agent
tts工具并要求返回文本,因为 Discord 语音负责最终 TTS 播放。 - 设置
voice.model时,它仅覆盖此语音频道回合的响应 LLM。 voice.tts会合并覆盖tts;支持流式的提供商会直接馈送到播放器,否则生成的音频文件会在已加入的频道中播放。
默认 agent-proxy 语音频道会话示例:
{
channels: {
discord: {
voice: {
enabled: true,
model: "openai/gpt-6-astra",
followUsersEnabled: true,
followUsers: ["123456789012345678"],
realtime: {
provider: "openai",
model: "gpt-realtime-2.1",
speakerVoice: "cedar",
},
},
},
},
}
在没有 voice.agentSession 块的情况下,每个语音频道都会获得自己的路由 OpenClaw 会话。例如,/vc join channel:234567890123456789 会与对应 Discord 语音频道的会话对话。实时模型只是语音前端;实质性请求会交给已配置的 OpenClaw agent。如果实时模型在未调用 consult 工具的情况下生成了最终转录文本,OpenClaw 会强制将 consult 作为回退,使默认行为仍然像在与 agent 对话。
旧版 STT 加 TTS 示例:
{
channels: {
discord: {
voice: {
enabled: true,
mode: "stt-tts",
model: "openai/gpt-5.4-mini",
tts: {
provider: "openai",
providers: {
openai: {
model: "gpt-4o-mini-tts",
speakerVoice: "cedar",
},
},
},
},
},
},
}
实时双向示例:
{
channels: {
discord: {
voice: {
enabled: true,
mode: "bidi",
model: "openai/gpt-6-astra",
realtime: {
provider: "openai",
model: "gpt-realtime-2.1",
speakerVoice: "cedar",
toolPolicy: "safe-read-only",
consultPolicy: "always",
},
},
},
},
}
将语音作为现有 Discord 频道会话的扩展:
{
channels: {
discord: {
voice: {
enabled: true,
mode: "agent-proxy",
model: "openai/gpt-6-astra",
agentSession: {
mode: "target",
target: "channel:123456789012345678",
},
realtime: {
provider: "openai",
model: "gpt-realtime-2.1",
speakerVoice: "cedar",
},
},
},
},
}
在 agent-proxy 模式下,机器人会加入已配置的语音频道,但 OpenClaw agent 回合会使用目标频道的常规路由会话和 agent。实时语音会话会将返回的结果朗读回语音频道。监督 agent 仍可根据其工具策略使用常规消息工具,包括在合适时发送单独的 Discord 消息。
在实时语音通话期间,可以要求 agent 列出可用语音或切换到其中一个。talk_voice 工具会更改当前房间的语音,并重新连接其实时 provider 连接,同时保留 Discord 连接、对话以及正在进行的 agent 工作。替换就绪后,agent 会确认该更改。如果切换失败,OpenClaw 会保留或恢复之前的语音。如果恢复也失败,它会要求你重新加入语音;已接受的 agent 工作仍可以完成。
该选择适用于该房间中后续的说话者,并持续到房间断开连接为止;voice.realtime.speakerVoice 仍作为未来通话的默认值。这需要经过 owner 授权的语音回合、agent 委派以及对 talk_voice 工具的访问权限。
通话中的更改需要 provider 语音目录。Google Live 支持相同的 talk_voice 流程:OpenClaw 使用所选语音打开一个新连接,并将未完成的 agent 工作保留在 Discord 通话中。等待发送的回复会跟随替换后的连接。已提交到之前连接的回复可能会被切换中断,并且不会重放,以避免重复语音。
当委派的 OpenClaw 运行处于活动状态时,在开始另一个 agent 回合之前,经过命令授权的 Discord 对话转录会被视为实时运行控制。诸如“状态”、“取消那个”、“使用较小的修复”或“完成后也检查测试”之类的短语会被分类为活动会话的状态、取消、引导或后续输入。状态、取消、已接受的引导以及后续结果会被朗读回语音频道,以便呼叫者知道 OpenClaw 是否处理了该请求。
当 OpenClaw 取消委派的咨询时,Discord 会记录为取消而不是失败,并且不会播放通用错误回退。匹配的延迟 provider 工具调用会收到相同的终止取消,而不是重新开始工作。语音会话仍可用于下一个请求;超时和真实失败保留其常规错误处理。
有用的目标形式:
target: "channel:123456789012345678"通过 Discord 文本频道会话进行路由。target: "123456789012345678"被视为频道目标。target: "dm:123456789012345678"或target: "user:123456789012345678"通过该直接消息会话进行路由。
回声较多的 OpenAI Realtime 示例:
{
channels: {
discord: {
voice: {
enabled: true,
mode: "bidi",
model: "openai/gpt-6-astra",
realtime: {
provider: "openai",
model: "gpt-realtime-2.1",
speakerVoice: "cedar",
bargeIn: true,
minBargeInAudioEndMs: 500,
consultPolicy: "always",
providers: {
openai: {
interruptResponseOnInputAudio: false,
},
},
},
},
},
},
}
当模型通过打开的麦克风听到自己的 Discord 播放时,但仍希望通过说话来中断它时,请使用此配置。OpenClaw 会阻止 OpenAI 根据原始输入音频自动中断,而 bargeIn: true 可让 Discord 说话者开始事件和已激活的说话者音频在下一个捕获回合到达 OpenAI 之前取消活动的实时响应。audioEndMs 低于 minBargeInAudioEndMs 的非常早期的 barge-in 信号会被视为可能的回声/噪声并忽略,以免模型在第一个播放帧处被切断。
预期的语音日志:
- 加入时:
discord voice: joining ... voiceSession=... supervisorSession=... agentSessionMode=... voiceModel=... realtimeModel=... - 实时启动时:
discord voice: realtime bridge starting ... autoRespond=false interruptResponse=false bargeIn=false minBargeInAudioEndMs=... - 说话者音频时:
discord voice: realtime speaker turn opened ...、discord voice: realtime input audio started ... outputAudioMs=... outputActive=...,以及discord voice: realtime speaker turn closed ... chunks=... discordBytes=... realtimeBytes=... interruptedPlayback=... - 跳过过期语音时:
discord voice: realtime forced agent consult skipped reason=incomplete-transcript ...或reason=non-actionable-closing ... - 实时响应完成时:
discord voice: realtime audio playback finishing reason=completed ... audioMs=... chunks=...;缓冲音频在此行之后仍可能正在播放。 - 常规播放背压时:
discord voice: realtime audio playback buffering ... bufferedBytes=...;当 Discord 排空缓冲音频时,播放会继续。 - Discord 会在其 PCM 被消费后确认作用域内的 provider 播放标记。xAI 在开始后续响应之前会等待这些确认;清除已丢弃的音频不会将其报告为已播放。
- 播放停止/重置时:
discord voice: realtime audio playback stopped reason=... audioMs=... elapsedMs=... chunks=... - 实时咨询时:
discord voice: realtime consult requested ... voiceSession=... supervisorSession=... question=... - agent 回答时:
discord voice: agent turn answer ... - 排队精确语音时:
discord voice: realtime exact speech queued ... queued=... outputAudioMs=... outputActive=...,随后是discord voice: realtime exact speech dequeued reason=player-idle ... - 检测到 barge-in 时:
discord voice: realtime barge-in detected source=speaker-start ...或discord voice: realtime barge-in detected source=active-speaker-audio ...,随后是discord voice: realtime barge-in requested reason=... outputAudioMs=... outputActive=... - 实时中断时:
discord voice: realtime model interrupt requested client:response.cancel reason=barge-in,随后是discord voice: realtime model audio truncated client:conversation.item.truncate reason=barge-in audioEndMs=...或discord voice: realtime model interrupt confirmed server:response.done status=cancelled ... - 忽略回声/噪声时:
discord voice: realtime model interrupt ignored client:conversation.item.truncate.skipped reason=barge-in audioEndMs=0 minAudioEndMs=250 - 禁用 barge-in 时:
discord voice: capture ignored: ... reason=protected playback - 空闲播放时:
discord voice: realtime barge-in ignored reason=... outputActive=false ... playbackChunks=0
要调试被截断的音频,请将实时语音日志作为时间线阅读:
realtime audio playback started表示 Discord 已开始播放助手音频。桥接器从此时开始统计助手输出块、Discord PCM 字节、提供商实时字节和合成音频时长。realtime speaker turn opened标记一个 Discord 说话人变为活动。如果播放已经处于活动状态且启用了bargeIn,其后可能出现barge-in detected source=speaker-start。realtime input audio started标记为该说话人轮次接收到的第一个实际音频帧。此处outputActive=true或非零的outputAudioMs表示麦克风正在发送输入,而助手播放仍处于活动状态。barge-in detected source=active-speaker-audio表示 OpenClaw 在助手播放活动期间看到了实时说话人音频。这有助于区分真实打断和没有有用音频的 Discord 说话人开始事件。barge-in requested reason=...表示 OpenClaw 要求实时提供商取消或截断活动响应。outputAudioMs和playbackChunks描述已生成的音频;后续的audioEndMs截断字段记录每个原生条目已消费的音频。Discord 从 AudioPlayer 准备的 Opus 数据包测量本地消费,粒度为 20 ms;它无法测量远程收听者的播放。realtime audio playback stopped reason=...是本地 Discord 播放重置点。player-idle表示 Discord 已完成音频消费;仅提供商的response.done和编码器完成并不意味着播放已结束。其他原因包括barge-in、provider-clear-audio、forced-agent-consult、stream-close、output-audio-overflow和session-close。realtime speaker turn closed总结捕获的输入轮次。chunks=0或hasAudio=false表示说话人轮次已打开,但没有可用音频到达实时桥接器。interruptedPlayback=true表示该输入轮次与助手输出重叠,并触发了 barge-in 逻辑。
常用字段:
outputAudioMs:在该日志行之前,实时提供商生成的助手音频时长。audioMs:OpenClaw 在播放停止前统计的助手音频时长。elapsedMs:打开和关闭播放流或说话人轮次之间的实际经过时间。discordBytes:发送到或从 Discord 语音接收的 48 kHz 立体声 PCM 字节。realtimeBytes:发送到或从实时提供商接收的提供商格式 PCM 字节。playbackChunks:为活动响应转发到 Discord 的助手音频块。sinceLastAudioMs:最后捕获的说话人音频帧与说话人轮次关闭之间的间隔。
常见模式:
- 带有
source=active-speaker-audio、较小的outputAudioMs且附近同一用户的情况,通常表示扬声器回声进入麦克风。提高voice.realtime.minBargeInAudioEndMs,降低扬声器音量,使用耳机,或设置voice.realtime.providers.openai.interruptResponseOnInputAudio: false。 source=speaker-start后跟speaker turn closed ... hasAudio=false表示 Discord 报告了说话人开始,但没有音频到达 OpenClaw。这可能是瞬时的 Discord 语音事件、噪声门行为,或客户端短暂触发麦克风。audio playback stopped reason=output-audio-overflow表示持续的投递问题超出了有界的待处理音频队列。检查相关的Discord realtime audio playback overflow错误以及前面的提供商或 Discord 连接诊断;普通播放背压不应产生此错误。Discord voice receive backlog exceeded表示身份查找、解码或本地磁盘无法跟上传入音频。OpenClaw 将每个接收流限制为 1,000 个待处理数据包和 1 MiB 编码音频;瓶颈清除后再次说话。其他说话人和任何已注册的捕获保持活动,且不完整的语音不能成为新的对话命令。conversation audio backlog exceeded、conversation is busy或conversation transcript limit exceeded表示已达到对话限制。录音继续;等待待处理请求完成,或重复一个更短且完整的请求。Discord voice recording backlog exceeded表示待处理的 WAV 文件达到录音队列的块或字节限制。受影响的语句停止,并且已注册的捕获在待处理音频处理追上后仍可用。audio playback stopped reason=stream-close附近没有 barge-in 或provider-clear-audio时,表示本地 Discord 播放流意外结束。检查前面的提供商和 Discord 播放器日志。capture ignored: ... reason=protected playback表示 OpenClaw 在助手音频活动期间有意抑制对话输入。显式启动的捕获仍会记录该语音。如果希望语音打断播放,请启用voice.realtime.bargeIn。barge-in ignored ... outputActive=false表示 Discord 或提供商 VAD 报告了语音,但 OpenClaw 没有可打断的活动播放。这不应截断音频。
凭据按组件解析:voice.model 的 LLM 路由认证、tools.media.audio 的 STT 认证、tts/voice.tts 的 TTS 认证,以及 voice.realtime.providers 或提供商常规认证配置的实时提供商认证。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw