跳转至

TTS 与呼入通话

电话文本转语音、入站策略和按号码路由、语音输出契约、会话启动以及过期呼叫清理器。属于 Voice call plugin 指南的一部分。

电话 TTS

Voice Call 使用核心 tts 配置在呼叫中流式传输语音。你可以在插件配置下使用相同结构覆盖它——它会与 tts 深度合并。

{
  tts: {
    provider: "elevenlabs",
    providers: {
      elevenlabs: {
        speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
        modelId: "eleven_multilingual_v2",
      },
    },
  },
}

Warning

Microsoft speech 在语音呼叫中会被忽略。 电话合成需要一个实现电话目标输出的提供商;Microsoft speech 提供商未实现,因此呼叫会跳过它,并尝试回退链中的其他提供商。

行为说明:

  • 插件配置中的旧版 tts.<provider> 键(openai、elevenlabs、microsoft、edge)会由 openclaw doctor --fix 修复;提交的配置应使用 tts.providers.<provider>。
  • 当启用 Twilio 媒体流时,会使用核心 TTS;否则呼叫会回退到提供商原生语音。
  • 如果 Twilio 媒体流已经处于活动状态,Voice Call 不会回退到 TwiML <Say>。如果在该状态下电话 TTS 不可用,播放请求会失败,而不是混合两条播放路径。
  • 当电话 TTS 回退到次要提供商时,Voice Call 会记录一条包含提供商链(from、to、attempts)的警告,用于调试。
  • 当 Twilio 打断或流拆除清空待处理的 TTS 队列时,已排队的播放请求会结束,而不是让等待播放完成的呼叫方挂起。
  • 恢复的呼叫方语音会丢弃仍在生成的较早自动回复。Twilio 流式处理会对语音开始和部分转录做出反应;运营商 webhook 呼叫会在新的语音事件到达时做出反应。显式语音请求仍然可用,已接受的代理工作可以完成,而无需说出过时的回复。

TTS 示例

{
  tts: {
    provider: "openai",
    providers: {
      openai: { speakerVoice: "alloy" },
    },
  },
}
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            provider: "elevenlabs",
            providers: {
              elevenlabs: {
                apiKey: "elevenlabs_key",
                speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
                modelId: "eleven_multilingual_v2",
              },
            },
          },
        },
      },
    },
  },
}
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            providers: {
              openai: {
                model: "gpt-4o-mini-tts",
                speakerVoice: "marin",
              },
            },
          },
        },
      },
    },
  },
}

入站呼叫

入站策略默认为 disabled。要启用入站呼叫,请设置:

{
  inboundPolicy: "allowlist",
  allowFrom: ["+15550001234"],
  inboundGreeting: "Hello! How can I help?",
}

Warning

inboundPolicy: "allowlist" 是低保证的来电号码筛选。插件会规范化提供商提供的 From 值,并将其与 allowFrom 比较。Webhook 验证会认证提供商投递和负载完整性,但它不能证明 PSTN/VoIP 来电号码所有权。请将 allowFrom 视为来电号码筛选,而不是强来电者身份。

自动回复使用代理系统。可通过 responseModel、responseSystemPrompt 和 responseTimeoutMs 进行调整。

按号码路由

当一个 Voice Call 插件接收多个电话号码的呼叫,并且每个号码都应表现得像一条不同的线路时,使用 numbers。例如,一个号码可以使用随意的个人助理,而另一个号码可以使用商务人格、不同的响应代理和不同的 TTS 语音。

路由根据提供商提供的被叫 To 号码选择。键必须是 E.164 号码。当呼叫到达时,Voice Call 会解析一次匹配的路由,将匹配的路由存储在呼叫记录上,并在问候语、经典自动回复路径、实时咨询路径和 TTS 播放中复用该有效配置。如果没有匹配的路由,则使用全局 Voice Call 配置。出站呼叫不使用 numbers;在发起呼叫时,请显式传入出站目标、消息和会话。

路由覆盖仅支持以下字段:

  • inboundGreeting
  • tts
  • agentId
  • responseModel
  • responseSystemPrompt
  • responseTimeoutMs

路由中的 tts 值会深度合并到全局 Voice Call tts 配置之上,因此通常只需覆盖提供商语音:

{
  inboundGreeting: "Hello from the main line.",
  responseSystemPrompt: "You are the default voice assistant.",
  tts: {
    provider: "openai",
    providers: {
      openai: { speakerVoice: "coral" },
    },
  },
  numbers: {
    "+15550001111": {
      inboundGreeting: "Silver Fox Cards, how can I help?",
      responseSystemPrompt: "You are a concise baseball card specialist.",
      tts: {
        providers: {
          openai: { speakerVoice: "alloy" },
        },
      },
    },
  },
}

语音输出契约

对于自动回复,Voice Call 会在系统提示中附加一个严格的语音输出契约,要求返回 {"spoken":"..."} JSON 回复。Voice Call 会防御性地提取语音文本:

  • 忽略标记为推理/错误内容的负载。
  • 解析直接 JSON、围栏 JSON 或内联 "spoken" 键。
  • 回退到纯文本,并移除可能的规划/元信息引导段落。

这使语音播放专注于面向呼叫方的文本,并避免将规划文本泄漏到音频中。

对话启动行为

对于出站 conversation 调用,首条消息处理与实时播放状态绑定:

  • Barge-in 队列清除和自动响应仅在初始问候语正在播放时被抑制。
  • 如果初始播放失败,通话会返回 listening,初始消息仍保留在队列中等待重试。
  • Twilio 流式传输的初始播放会在流连接时开始,无额外延迟。
  • Barge-in 会中止当前播放,并清除已排队但尚未播放的 Twilio TTS 条目。被清除的条目会解析为 skipped,因此后续响应逻辑可以继续,而无需等待永远不会播放的音频。
  • 实时语音对话使用实时流自身的开场轮次。Voice Call 不会为该初始消息发布旧版 <Say> TwiML 更新,因此出站 <Connect><Stream> 会话会保持连接。

Twilio 流断开宽限期

当 Twilio 经典流式传输或实时媒体流断开时,Voice Call 会等待 2000 ms 再自动结束通话:

  • 如果流在该窗口内重新连接,则取消自动结束。
  • 如果宽限期结束后没有流重新注册,则会结束通话,以防止活动通话卡住。
  • 实时桥接/会话资源、排队音频、转录所有权以及进行中的咨询工作会立即关闭。只有通话/提供商最终处理会等待重新连接。

过期通话回收器

使用 staleCallReaperSeconds(默认 120)来结束从未被接听且从未进入实时对话状态的通话,例如提供商从未传递终止 webhook 的通知模式通话。将其设置为 0 可禁用。

回收器每 30 秒运行一次,并且只结束没有 answeredAt 时间戳且尚未处于终止状态或实时状态(speaking/listening)的通话,因此已接听的对话永远不会被此定时器回收;maxDurationSeconds(默认 300)是另一个上限,用于结束运行时间过长的已接听通话。

对于通知类流程,运营商可能较慢地传递振铃/接听 webhook,请将 staleCallReaperSeconds 提高到默认值以上,以避免慢但正常的通话被过早回收;120-300 秒是合理的生产范围。

{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          maxDurationSeconds: 300,
          staleCallReaperSeconds: 120,
        },
      },
    },
  },
}

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw