Skip to content

Proposal_UniSpeaking自由对话模块五阶段接口设计 #11

Description

@suerzzh

自由对话模块五阶段接口设计

背景

自由对话采用一次性 Session:

  • 每次点击“开始自由对话”创建新的 sessionId
  • 不保存长期 Conversation 档案
  • 字幕、临时消息和实时事件到期自动删除
  • 仅保留最小用量记录,用于额度、计费和厂商账单对齐
  • 当前方案不绑定 Pion
  • 客户端通过 WebRTC + DataChannel 直连 Qwen Realtime
  • 业务后端负责鉴权、额度、Session、SDP 代理、状态、用量和数据清理

目标

为以下五个阶段定义统一接口:

  1. 开始
  2. 暂停
  3. 恢复
  4. 打断
  5. 结束

涉及四个端:

  • UI
  • Free Chat SDK
  • 业务后端
  • Qwen Realtime

一、开始

UI → SDK

await freeChatClient.start({
  coachId: "clara",
  voiceId: "clara_default",
  difficulty: "BEGINNER",
  language: "en-US"
});

SDK → 后端:创建 Session

POST /v1/free-chat/sessions

请求:

{
  "coach_id": "clara",
  "voice_id": "clara_default",
  "difficulty": "BEGINNER",
  "language": "en-US",
  "client": {
    "platform": "WEB",
    "app_version": "0.1.0",
    "device_id": "device_xxx"
  },
  "idempotency_key": "start_xxx"
}

响应:

{
  "session_id": "fcs_xxx",
  "status": "CREATED",
  "expires_at": "2026-07-17T10:30:00Z",
  "content_delete_at": "2026-07-18T10:30:00Z",
  "policy": {
    "max_duration_seconds": 600,
    "remaining_seconds": 600,
    "idle_timeout_seconds": 60
  },
  "realtime_config": {
    "voice": "clara_default",
    "modalities": ["text", "audio"],
    "turn_detection": {
      "type": "server_vad",
      "threshold": 0.5,
      "silence_duration_ms": 800
    }
  }
}

SDK → 后端:交换 SDP

POST /v1/free-chat/sessions/{sessionId}/sdp
Content-Type: application/sdp

发送:Offer SDP
接收:Answer SDP

SDK → Qwen

收到:

{
  "type": "session.created",
  "session": {
    "id": "provider_session_xxx"
  }
}

发送:

{
  "type": "session.update",
  "session": {
    "instructions": "<server-generated-prompt>",
    "voice": "clara_default",
    "modalities": ["text", "audio"],
    "turn_detection": {
      "type": "server_vad",
      "threshold": 0.5,
      "prefix_padding_ms": 500,
      "silence_duration_ms": 800
    }
  }
}

AI 主动开场:

{
  "type": "response.create"
}

SDK → 后端:绑定厂商 Session

POST /v1/free-chat/sessions/{sessionId}/provider-session
{
  "provider_session_id": "provider_session_xxx"
}

二、暂停

UI → SDK

await freeChatClient.pause();

SDK 内部

  • AI 正在回复时发送 response.cancel
  • 停止当前 AI 音频
  • 清空未播放音频缓冲
  • 禁用麦克风 Track
  • 保持 PeerConnection 和 DataChannel 不关闭

SDK → 后端

POST /v1/free-chat/sessions/{sessionId}/pause

请求:

{
  "reason": "USER_PAUSED",
  "client_paused_at": "2026-07-17T10:23:00Z"
}

响应:

{
  "session_id": "fcs_xxx",
  "status": "PAUSED",
  "remaining_seconds": 420,
  "max_pause_seconds": 120
}

三、恢复

UI → SDK

await freeChatClient.resume();

SDK → 后端

POST /v1/free-chat/sessions/{sessionId}/resume

请求:

{
  "client_resumed_at": "2026-07-17T10:23:20Z"
}

响应:

{
  "session_id": "fcs_xxx",
  "status": "ACTIVE",
  "pause_duration_seconds": 20,
  "remaining_seconds": 400
}

SDK 内部

  • 后端确认恢复成功后再启用音频
  • 恢复 AI 播放器
  • 重新启用麦克风 Track
  • 恢复后等待用户继续说话
  • 默认不自动发送 response.create

四、打断

UI / VAD → SDK

await freeChatClient.interrupt("USER_SPEAKING");

SDK → Qwen

{
  "event_id": "evt_interrupt_xxx",
  "type": "response.cancel"
}

SDK 内部

  • 停止当前 AI 音频
  • 清空当前回复音频缓冲
  • 保持麦克风开启
  • 保持 WebRTC 和 DataChannel 连接
  • 发出 response.interrupted

SDK → 后端(可选)

POST /v1/free-chat/sessions/{sessionId}/events
{
  "event_id": "evt_interrupt_xxx",
  "event_type": "ASSISTANT_RESPONSE_INTERRUPTED",
  "occurred_at": "2026-07-17T10:24:10Z",
  "payload": {
    "reason": "USER_SPEAKING",
    "provider_response_id": "resp_xxx"
  }
}

打断事件上报失败不得影响实时打断。


五、结束

UI → SDK

await freeChatClient.end("USER_ENDED");

SDK 内部清理顺序

  1. 状态切换为 ENDING
  2. AI 正在生成时发送 response.cancel
  3. 停止麦克风 Track
  4. 停止播放器并清空缓冲
  5. 收集 RTCPeerConnection.getStats()
  6. 关闭 DataChannel
  7. 关闭 PeerConnection
  8. 上报 telemetry
  9. 调用后端 end
  10. 清空 SDK 状态并通知 UI

SDK → 后端:质量上报

POST /v1/free-chat/sessions/{sessionId}/telemetry
{
  "metrics": {
    "duration_seconds": 388,
    "packet_loss_percent": 0.15,
    "max_jitter_ms": 20,
    "avg_rtt_ms": 105,
    "connection_disruptions": 0,
    "interrupt_count": 2,
    "pause_count": 1
  }
}

telemetry 失败不得阻止结束。

SDK → 后端:结束 Session

POST /v1/free-chat/sessions/{sessionId}/end

请求:

{
  "reason": "USER_ENDED",
  "client_ended_at": "2026-07-17T10:26:30Z",
  "provider_session_id": "provider_session_xxx"
}

响应:

{
  "session_id": "fcs_xxx",
  "status": "ENDED",
  "end_reason": "USER_ENDED",
  "usage_status": "PENDING_PROVIDER_CONFIRMATION",
  "estimated_usage": {
    "connected_seconds": 388,
    "active_seconds": 360,
    "paused_seconds": 20
  },
  "content_delete_at": "2026-07-18T10:26:30Z"
}

end 必须幂等,重复调用不能重复扣费。


HTTP 接口清单

POST /v1/free-chat/sessions
POST /v1/free-chat/sessions/{sessionId}/sdp
POST /v1/free-chat/sessions/{sessionId}/provider-session
POST /v1/free-chat/sessions/{sessionId}/pause
POST /v1/free-chat/sessions/{sessionId}/resume
POST /v1/free-chat/sessions/{sessionId}/events
POST /v1/free-chat/sessions/{sessionId}/telemetry
POST /v1/free-chat/sessions/{sessionId}/end

SDK 方法清单

start()
pause()
resume()
interrupt()
end()

非目标

  • 不做长期 Conversation 档案
  • 不做跨 Session 记忆
  • 不绑定 Pion
  • 不通过业务后端实时转发音频
  • 第一阶段不要求在原 Provider Session 上实现复杂断线续聊

详细接口设计

UniSpeaking_自由对话模块五阶段接口设计.md

Metadata

Metadata

Labels

documentationImprovements or additions to documentationproposal

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions