Files
magic-sonar/docs/architecture/public-model-outbound.md
2026-08-18 11:57:35 +08:00

23 KiB
Raw Permalink Blame History

公网模型外呼目标架构

状态:100 通受控 PoC 的实施基线。模型、价格和外部资料访问日期为 2026-08-17。 本文沉淀 H-265 已验收设计;H-261 调研基线见《AI 外呼 SaaS 调研与架构指南》。如候选调研与本文冲突,PoC 以本文的最终选型为准。

1. 最终选型与适用边界

主链路只使用阿里云百炼华北 2(北京)的一套模型,减少地域、鉴权和网络路径。唯一备选仍在同一地域,只覆盖单模型限流、故障或版本回归;它不覆盖阿里云账号、网络或地域级故障。

环节 主模型 地域、端点与协议 鉴权边界 官方原价
流式 ASR fun-asr-realtime-2026-02-28 北京;wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inferencerun-task 后发送单声道二进制音频,以 finish-task / task-finished 收口 1 2 服务端在握手头设置 Authorization: Bearer <API_KEY>;浏览器、线路和租户均不得获得密钥 0.00033 元/输入音频秒,输出不计费
流式 LLM qwen3.7-flash-2026-07-15,非思考模式 北京;POST https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/chat/completionsOpenAI 兼容 Chat Completionsstream=true 返回 SSE 3 4 仅会话编排器持有服务端 Bearer API Key;工具调用必须再经过本地白名单和幂等门禁 单次输入不超过 32K Token:输入 0.2 元/百万 Token、输出 0.8 元/百万 Token
流式 TTS cosyvoice-v3.5-plus;定制音色预先由 voice-enrollment 注册 北京;同一 WebSocket inference 端点;run-task、多次 continue-taskfinish-task,返回二进制音频流 5 6 仅会话编排器持有密钥;只接受已审批、未过期且未撤回的 voice ID 1.5 元/万计费字符;汉字计 2 字符,其余字符计 1;音色注册暂无公开价 7 8

TTS 官方当前精确模型 ID 就是 cosyvoice-v3.5-plus,没有日期快照 ID。每通固化请求模型 ID、voice ID 和供应商实际返回的模型标识,避免静默升级后无法对账或回溯。

唯一故障备选为:

  • ASRparaformer-realtime-8k-v2
  • LLMqwen-flash-2025-07-28
  • TTScosyvoice-v3-flash,使用预先生成并审批的独立备份 voice ID。

备选必须预热和通过同一契约测试,但不做常态路由 9 10 11 。同一环节最多切换一次;主备均失败时播放本地缓存、已批准且已授权的结束语并挂断。账号、网络或北京地域故障时停止新呼叫,不再增加第二家供应商。

1.1 事实、决策、假设和待核实项

类别 内容
已核实事实 上述模型 ID、北京地域端点、流式协议、Bearer 鉴权、模型能力和 2026-08-17 官方原价
已验收决策 PostgreSQL 是事实源;Redis 仅存可丢短态;PG outbox 代替消息队列;每环节只切一次备选;PoC 只需控制/worker 与媒体/会话两个逻辑进程
规划假设 每接通分钟含 60 秒 ASR、3,000 输入 Token、600 输出 Token、125 汉字;固定成本 9,900 元/月、月接通 198,000 分钟;不使用缓存或 Batch
待合同/控制台/PoC 现有线路双向媒体、上下行分离/AEC、及时停播、回调签名/序号、线路计费起点与取整;音色注册、媒体和存储成本;真实延迟、账单差异及生产留存期限

任一线路前提不成立即为 PoC 阻断项:没有实时双向媒体;不能及时停止尚未播放的下行音频;上下行混流且无可靠 AEC;无法验签、防重放或查询真实终态;不能提供逐通计费量、单位、取整和 provider call ID。

2. 组件边界与端到端数据流

flowchart TB
  subgraph C[控制面]
    UI[运营控制台] --> API[租户 API]
    API --> AUTH[Logto]
    API --> PG[(PostgreSQL)]
    API --> OBJ[(现有对象存储)]
    WORKER[调度 / 恢复 / 对账 worker] <--> PG
    WORKER --> ADAPTER[现有线路适配器]
  end
  subgraph D[实时数据面]
    LINE[现有线路双向媒体] <--> MEDIA[媒体网关\nG.711 / 抖动缓冲 / 连续重采样]
    MEDIA --> VAD[VAD / 端点检测]
    VAD --> ASR[流式 ASR]
    ASR --> SESSION[会话编排器]
    SESSION --> LLM[流式 LLM]
    LLM --> TTS[流式 TTS / 已授权音色]
    TTS --> MEDIA
    SESSION -. 路由 / 租约 / generation .-> REDIS[(Redis)]
  end
  ADAPTER <--> LINE
  PG <--> SESSION
  SESSION --> OBJ

这些是逻辑职责,不要求拆成微服务。PoC 复用现有 PostgreSQL、Redis、对象存储和部署;无吞吐证据前不增加 Kafka/RabbitMQ、工作流引擎、服务网格、独立计费服务或事件溯源框架。

组件 最小职责 不负责
租户 API 验证 Logto token;配置和发布不可变话术/音色/线路版本;写审计 实时音频、供应商状态解释
调度/恢复/对账 worker 批次展开、时段/黑名单/频控、租户与线路并发、PG 槽位、outbox、模糊结果查询恢复、逐通对账 直接处理媒体帧
线路适配器 幂等发起/挂断/停播、能力声明、回调验签、事件映射、状态查询、用量原样返回 对话策略、租户授权
媒体网关 G.711、20 ms 帧、抖动缓冲、连续重采样、上下行隔离/AEC、VAD、立即停播 业务动作和长期事实
会话编排器 固化本通版本;ASR→LLM→TTS 流;generation、打断、取消、超时、一次备选和工具门禁 身份源、批量调度、最终账单
PostgreSQL 业务和通话唯一事实源、事件 inbox/outbox、审计与用量 媒体帧和不稳定 partial
Redis 带 TTL 的 session 路由、短租约、心跳、快速限流与 generation 终态、额度事实、唯一账单
对象存储 经授权的参考音频、录音和导出对象;租户前缀隔离 密钥和检索索引

正常数据流:控制面发布不可变版本;worker 在一个 PG 事务内抢占并发槽、创建 call 和 outbox;事务提交后适配器幂等发起呼叫;接通媒体直接进入实时数据面;每轮固化稳定转写、回合边界、工具副作用、generation、延迟和供应商用量;挂断后 worker 查询线路用量并追加对账结果。

2.1 状态所有权与数据落点

PostgreSQL:必须持久化 Redis:可丢失并重建 仅进程内有界缓冲
organization、套餐/并发上限、批次/task item、联系人来源与黑名单、不可变话术/音色授权版本、call/provider call 映射、幂等键与请求哈希、允许的状态迁移、原始回调摘要与 event ID、稳定转写/回合边界/工具副作用、generation、模型与线路用量、账单 adjustment、审计、删除任务 session→媒体节点路由、租户/线路快速拒绝缓存、限流计数、短租约/心跳、播放 generation/cancel token;丢失后从 PG 重建,不能改变终态或额度 RTP/PCM 帧、VAD 窗口、抖动缓冲、ASR partial、LLM token、未播放 TTS chunk;不持久化模型思维链

Redis 不可用时停止新呼叫,已有通话可依靠进程内状态安全结束;PG 不可写时停止新呼叫和新工具副作用,已有通话仅允许安全结束。正在通话的媒体节点失联且线路不支持重连时,不伪装恢复对话:适配器挂断并记录 media_lost

3. 主要接口与错误语义

3.1 线路适配器

ValidateConfig(config) -> capabilities
Originate(idempotency_key, caller, callee, media, metadata) -> provider_call_id
Hangup(provider_call_id, reason)                             # 幂等
StopPlayback(provider_call_id, generation_id)                # 必须有及时生效的确认语义
GetCall(provider_call_id | idempotency_key) -> provider_state
GetUsage(provider_call_id) -> quantity/unit/rounding/window
VerifyWebhook(headers, raw_body, received_at) -> normalized_event

capabilities 必须声明双向媒体方式、上下行分离或 AEC、codec、采样率、20 ms ptime、最大并发、DTMF、停播确认、回调重试/排序、计费起点和取整。metadata 只放内部不可逆关联标识,不发送联系人明文、租户密钥或无关个人数据。

统一事件为 requested/ringing/answered/media_started/ended/failed,至少包含 provider event ID、provider call ID、发生时间或序号、标准原因与原始原因摘要。标准错误至少区分:

错误 语义与处理
invalid_request / invalid_number 确定性失败,不重试;释放槽位并审计
rate_limited / provider_unavailable 尚未产生外部副作用时按上限退避;受时段和任务期限约束
originate_unknown 发起请求超时且结果未知;必须先 GetCall(idempotency_key),禁止直接重拨
auth_failed / webhook_untrusted 停止新任务或拒绝事件;告警;绝不通过重试绕过鉴权
media_lost / stop_playback_unsupported 当前通话安全挂断;后者直接判定 PoC No-Go
usage_incomplete 不覆盖旧账单;追加异常并进入对账队列

Webhook 进入状态机前必须限制 body 大小、校验 Content-Type/schema、用原始 body 做常量时间签名校验,并检查时间窗/nonce 防重放。唯一约束 (adapter, provider_event_id) 去重;供应商没有 event ID 时才用规范化原始载荷哈希作为降级指纹,并把该缺陷列入适配器风险。

3.2 模型流与取消

模型客户端对编排器暴露统一的流式语义,而不是把供应商事件泄漏给业务:

StartASR(call_id, generation_id, audio_format) -> partial/final/usage
StreamLLM(call_id, generation_id, prompt_version, messages, allowed_tools) -> token/tool_call/usage
StartTTS(call_id, generation_id, voice_version, text) -> audio_chunk/usage
Cancel(call_id, generation_id, reason) -> acknowledged

每个分片都关联 call ID 和 generation ID。generation 变化后,旧 ASR/LLM/TTS 分片一律丢弃。工具调用只有在结构校验、租户授权和本地幂等门禁全部通过后才执行;一旦工具产生外部副作用或音频已经播放,不透明重试该回合。

媒体路径固定为:

上行 G.711 A-law/μ-law 8 kHz mono → 解码 → 有状态重采样 16 kHz → VAD / ASR
TTS PCM(供应商输出采样率)→ 低通 → 有状态重采样 8 kHz → G.711 → 下行 20 ms 帧

滤波器和重采样器状态必须跨帧连续。优先使用独立上行;若只能得到混流,必须由线路/AEC PoC 证明机器人播音不会触发自身 VAD。录音按上下行独立轨保存或明确标注混音,禁止把机器人播音再次计入 ASR。

4. 状态机、时序与恢复

stateDiagram-v2
  [*] --> scheduled
  scheduled --> dispatching
  scheduled --> cancelled
  dispatching --> requested
  dispatching --> cancelled
  dispatching --> failed
  requested --> ringing
  requested --> answered
  requested --> cancelled
  requested --> failed
  ringing --> answered
  ringing --> failed
  answered --> media_active
  answered --> ending
  answered --> failed
  media_active --> ending
  media_active --> failed
  ending --> ended
  failed --> [*]
  cancelled --> [*]
  ended --> [*]

允许供应商跳过中间事件向前推进,例如 requested → answeredended/failed/cancelled 是终态,迟到的 ringing/answered 不得令其回退。回调顺序不等于事实顺序:优先使用供应商序号/发生时间和允许迁移表;仍冲突时保留原始事件并调用 GetCall,以追加 correction/audit 的方式收敛。账单修正也只追加 adjustment。

线路已接通但尚未收到 media_started 即失败时,call 收敛到 failed;该终态迁移的唯一获胜者写 outbox,执行幂等 Hangup、单次释放 PG 槽位并调用 GetUsage 对账。重复 failed 或迟到的 media_started/answered 只入 inbox/audit 并返回 2xx,不重复挂断、释放槽位或结算,也不回退终态。

实时会话子状态 listening → thinking → speaking 主要驻留内存。只将回合边界、稳定转写、工具副作用、generation、延迟和用量落 PG。

4.1 正常通话与用户打断

sequenceDiagram
  participant W as worker
  participant P as PostgreSQL
  participant A as 线路适配器
  participant M as 媒体网关
  participant O as 会话编排器
  participant X as ASR/LLM/TTS

  W->>P: 抢占槽位 + call + outbox(同一事务)
  W->>A: Originate(idempotency_key)
  A-->>P: requested/ringing/answered(验签、去重)
  A->>M: G.711 上行
  M->>X: 连续重采样音频
  X-->>O: ASR final
  O->>X: LLM SSE → 完整短句 → TTS
  X-->>M: generation N 音频块
  M-->>A: G.711 下行
  alt 用户在播报时开口
    M->>M: 清空本地播放队列
    M->>A: StopPlayback(call, N)
    M->>O: generation N → N+1
    O->>X: Cancel(N)
    X-->>O: 迟到 token/音频 N
    O->>O: 丢弃,不阻断新上行 ASR
  end
  A-->>P: ended(唯一终态)
  W->>A: GetUsage(provider_call_id)
  W->>P: 模型用量 + 线路用量 + adjustment

4.2 模型超时与回调乱序

  • 主模型在阶段超时且尚未调用工具或播放音频:同环节切换唯一备选一次。
  • 已调用工具或已播放内容后失败:不透明重试;播放已批准的澄清/结束语。
  • 备选也失败或模型熔断:停止新任务;当前通话安全结束并记 model_degraded
  • ended(seq=9) 先到:验签并落 inbox,call 进入终态;随后 answered(seq=5) 只保留事件,不回退状态。
  • 重复 ended:唯一约束命中后返回 2xx,不重复释放槽位或结算。
  • 无序号或终态冲突:GetCall 查询真实状态并追加 correction;原始事件不删除。

调度任务可在节点重启后从 PG 恢复;对话媒体不能无损恢复时立即挂断。恢复器扫描过期租约和非终态 call,先查询线路真实状态,再决定释放槽位、恢复监听或收敛终态,禁止依据本地超时直接假定呼叫不存在。

5. 超时、重试、SLO 与降级

以下是 PoC 验收目标,不是供应商承诺:

指标 目标 / 测量边界
同租户幂等键重复发起 第二通为 0;同 key 同请求返回原 call,不同请求哈希返回冲突
用户开口到最后一帧机器人音频 P95 ≤ 200 ms;从首个有效上行语音帧计到线路最后播放帧
用户回合结束到首段可播放音频 P95 ≤ 1.0 s;以线路收到首块而非 TTS 服务返回计时
回调验签入库 P99 ≤ 2 s;重复事件返回 2xx,不能触发重试风暴
终态收敛 99.9% 在挂断后 5 分钟内;其余在 30 分钟内查询收敛或告警
租户/线路并发上限 超额发起为 0;PG 槽位是硬门禁,Redis 仅作快路径
逐通账单差异 汇总差异 ≤ 5%,每个差异可追到单位、取整、重试或缺失用量

退避采用有上限的指数退避加抖动,并受通话剩余时限、呼叫时段和批次期限约束。ASR 连接仅在协议确认可安全续传音频时重连,否则结束该回合。固定降级顺序为:主模型 → 唯一备选 → 本地缓存的已批准结束语 → 挂断。控制面故障时不启动新批次,已建立的数据面会话只继续到安全终态。

6. 成本公式与计费边界

每接通分钟的规划用量为:60 秒上行 ASR;4 个回合合计 3,000 输入 Token、600 输出 Token;机器人播报 30 秒,按 250 汉字/播报分钟即 125 汉字,暂不计标点;无缓存、无 Batch、单次 LLM 输入不超过 32K Token。

项目 显式复算(元/接通分钟)
现有线路 60 秒 × 0.1 元 / 60 秒 = 0.10000
ASR 60 × 0.00033 = 0.01980
LLM 输入 3,000 / 1,000,000 × 0.2 = 0.00060
LLM 输出 600 / 1,000,000 × 0.8 = 0.00048
TTS 125 汉字 × 2 计费字符 × 1.5 / 10,000 = 0.03750
线路 + 已知模型变量小计 0.15838
固定摊销规划值 F/M = 9,900 / 198,000 = 0.05000
当前可复算规划值 0.20838 元/接通分钟

规划容量 M = 30 并发 × 60 × 10 小时/日 × 22 日 × 50% 有效利用率 = 198,000 接通分钟/月F=9,900 元/月覆盖应用、PostgreSQL、Redis、对象存储和监控基线,是 H-260 规划假设,不是供应商报价。完整公式为:

C = 0.20838 + V_media/storage + E_voice / 该音色累计接通分钟

V_media/storage 和音色注册费 E_voice 均待合同、控制台或 PoC,不能藏入 0.20838。全部已选项目均以人民币计价;保留 1 USD = 7.20 CNY 仅作未来外币账单规划汇率,对当前结果影响为 0。

计费边界:ASR 预算先按每个识别会话向上取整到秒,待账单确认;LLM 按每请求实际输入/输出 Token 且不计缓存优惠;TTS 按实际计费字符,标点、数字和空格各计 1;线路题设按精确接通分钟计算,若合同按每通向上取整,应替换为 0.1 × ceil(通话秒/60) / (通话秒/60)

每通必须保存供应商返回的模型、音频秒、输入/输出 Token、计费字符、线路计费量/单位/取整和内部公式版本。PoC 用真实 usage、账单、FV_media/storageE_voice 替换规划值。

7. 安全、租户、声音授权与留存

  • Logto token 必验签名、iss/aud/exp、organization 和 scopeorganization 来自 token,不接受请求体覆盖。业务主键/唯一约束、对象前缀、Redis key、幂等键和用量聚合均包含 organization。
  • 北京 API Key 只存在服务端密钥管理系统;开发、测试、生产分工作空间和密钥,组件只取得调用所需最小权限。密钥、Authorization、完整 webhook body、联系人号码和参考音频不得进入普通日志。
  • 线路回调签名与 Logto 是独立信任边界。回调中的租户字段不能直接选租户,只能通过已存在的 provider call 映射恢复 organization。
  • 音色保存实名/主体、用途、渠道、期限、授权文本版本、样本哈希、审批人和撤回状态。只有 approved 且未过期/撤回的 voice ID 可进入不可变发布版本;撤回立即阻止新通话并清理主备派生 voice 和缓存。
  • 每通开场披露企业身份、AI 语音身份和录音事实,并提供拒绝/退订路径;保存披露事件和音频时间点。人声按敏感个人信息边界处理,电话场景具体法律适用仍需律师确认 12 13 14
  • 不保存模型思维链;工具参数字段级脱敏。对象存储服务端加密、按租户前缀隔离,使用短时签名 URL;账单和审计只存必要摘要。

PoC 只呼叫已同意的内部号码;参考音频、录音和转写在验收后 7 日内删除,不含内容的用量、延迟、安全和审计摘要保留 180 日。生产录音/转写、授权证据期限与删除 SLA 必须由合同、线路规则和法律意见确认后配置;在此之前不默认长期留存。

8. 最小 PoC 与实现拆分

按以下顺序拆任务即可,不需要先搭建额外基础设施:

  1. 线路契约与 mock:实现 capabilities、幂等发起/挂断/停播、验签、防重放、乱序/重复事件、查询恢复和用量取整契约测试;覆盖无 media_startedrequested → ringing → answered → failed,断言终态收敛、槽位只释放一次且仍执行计费/对账,再注入重复 failed 和迟到事件验证不重复副作用。
  2. 调度与持久化:实现 PG call/task/slot/inbox/outbox、单调状态机、租户/线路硬并发门禁、恢复和 adjustment;Redis 只做可丢快路径。
  3. 媒体与打断:用固定 G.711 8 kHz 样本验证连续重采样、上下行隔离/AEC、20 ms 帧、generation 丢弃和 StopPlayback。
  4. 模型编排:接入主模型和唯一备选,验证流式、取消、工具幂等、有界超时、一次切换与已批准结束语。
  5. 受控通话与对账:只用测试凭据和已同意的内部号码完成 100 通,采集首帧、VAD、ASR final、LLM 首 token、TTS 首块、线路首/末播放帧、generation、模型/线路 usage 和账单。

100 通需覆盖正常对话、噪声/口音、连续打断、沉默、忙线、主 ASR/LLM/TTS 分别注入超时、WebSocket 断开、重复/乱序回调和节点重启。

Go 条件:

  1. 双向媒体、上下行分离或 AEC、StopPlayback 全部通过,否则直接 No-Go。
  2. 重复发起、终态分叉、并发超额和回调验签绕过均为 0。
  3. 打断 P95 ≤ 200 ms,用户回合结束到线路首音频 P95 ≤ 1.0 s。
  4. 三个主模型分别故障时,唯一备选能受控降级;备选失败时安全结束且不重放工具副作用。
  5. 内部逐通用量与线路/模型账单总差异 ≤ 5%,差异可解释,并用实测值替换 0.20838 的规划输入。
  6. 未授权、过期或撤回音色调用为 0;跨租户读写、缓存命中、对象 URL 和回调映射越权为 0。

9. 官方来源

以下资料均访问于 2026-08-17;采购和实施前应重新核验价格、地域和模型状态。


  1. 阿里云百炼,Fun-ASR 实时语音识别模型与价格↩︎

  2. 阿里云百炼,Fun-ASR WebSocket API↩︎

  3. 阿里云百炼,Qwen3.7 Flash 模型、快照、能力与价格↩︎

  4. 阿里云百炼,OpenAI 兼容端点与流式调用↩︎

  5. 阿里云百炼,CosyVoice v3.5 Plus 模型与价格↩︎

  6. 阿里云百炼,CosyVoice WebSocket API↩︎

  7. 阿里云百炼,语音合成模型与计费字符规则↩︎

  8. 阿里云百炼,声音复刻 API 与公开定价说明↩︎

  9. 阿里云百炼,Paraformer 实时 8k v2↩︎

  10. 阿里云百炼,Qwen Flash↩︎

  11. 阿里云百炼,CosyVoice v3 Flash↩︎

  12. 全国人大,中华人民共和国个人信息保护法↩︎

  13. 国家互联网信息办公室,互联网信息服务深度合成管理规定↩︎

  14. 国家互联网信息办公室,人工智能生成合成内容标识办法↩︎