跳转至

在语音中跟随用户

跟随模式使机器人能够跟踪所选用户,并在他们于 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

要调试被截断的音频,请将实时语音日志作为时间线阅读:

  1. realtime audio playback started 表示 Discord 已开始播放助手音频。桥接器从此时开始统计助手输出块、Discord PCM 字节、提供商实时字节和合成音频时长。
  2. realtime speaker turn opened 标记一个 Discord 说话人变为活动。如果播放已经处于活动状态且启用了 bargeIn,其后可能出现 barge-in detected source=speaker-start。
  3. realtime input audio started 标记为该说话人轮次接收到的第一个实际音频帧。此处 outputActive=true 或非零的 outputAudioMs 表示麦克风正在发送输入,而助手播放仍处于活动状态。
  4. barge-in detected source=active-speaker-audio 表示 OpenClaw 在助手播放活动期间看到了实时说话人音频。这有助于区分真实打断和没有有用音频的 Discord 说话人开始事件。
  5. barge-in requested reason=... 表示 OpenClaw 要求实时提供商取消或截断活动响应。outputAudioMs 和 playbackChunks 描述已生成的音频;后续的 audioEndMs 截断字段记录每个原生条目已消费的音频。Discord 从 AudioPlayer 准备的 Opus 数据包测量本地消费,粒度为 20 ms;它无法测量远程收听者的播放。
  6. 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。
  7. 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