- 介绍一下传音的实时AI对话调度平台?
它的职责是把一次实时对话中的音频输入、语义处理、Agent调用和结果输出组织成可执行流程。核心有三层:编排层使用DAG Task表达依赖和并行关系;通信层使用WebSocket和gRPC流承载音频与事件;状态与观测层把会话状态外置到Redis,并用Trace、Metrics、Log跟踪跨服务链路。
我参与的重点是Taskflow的节点与取消链路、WebSocket S2S和barge-in场景、Redis会话迁移联调以及关键节点Trace埋点。
TaskFlow 用 DAG 管理一轮语音对话全流程任务(收音频→ASR 识别→Agent 调用→TTS 合成→下发音频);取消链路是用户挂断、插话、报错、超时时,让所有正在运行的任务、网络流、RPC 请求被安全终止,释放 goroutine、连接、内存资源,不会出现僵尸协程、残留播报、脏会话数据。
实现:context.Context做信号透传
整个DAG任务树基于上下文父子继承关系传递取消信号:一次对话初始化根上下文rootCtx,rootCancel := context.WithCancel(context.Background()),整个会话所有任务都派生自此根ctx。
DAG每个Task结构体内置独立上下文:
type Task struct {
taskID string
parentCtx context.Context
ctx context.Context
cancel context.CancelFunc
dagDepends \[\]string
runFunc func(ctx context.Context) error
}
父任务ctx取消->所有子任务ctx.Done()管道统一收到关闭信号,天然实现级联取消。
额外搭配context.WithTimeout给每个任务执行超时,兜底卡死的任务
任务内部统一的取消监听范式
所有任务的业务逻辑必须包裹select监听ctx关闭信号
func runTask(ctx context.Context) error {
for {
select {
case <- ctx.Done():
return ctx.Err()
case data := <-inputChan:
process(data)
}
}
}
DAG串行/并行任务的取消差异
串行节点:前序任务取消,后续任务不在调度执行
并行节点:父ctx取消,所有并行goroutine同时感知退出
资源兜底回收规则
所有cancel()函数必须搭配defer执行:任务正常结束、异常崩溃、被取消三种场景都调用cancel,杜绝goroutine泄露,长连接hanging
WebSocket S2S:各个后端服务之间(网关、ASR 服务、TTS 服务、编排服务)通过 WebSocket 传输二进制音频流 + 事件指令; barge-in:机器人正在播放语音回复时,用户中途说话,系统立刻停止播报、重置对话流程、接收用户新语音(语音打断)。
WebSocket S2S服务间音频流实现
基于gorilla/websocket库搭建服务端双向WS长连接,各个微服务之间建立点对点长流,区别于客户端对接网关的C2S WS
自定义二进制封装包格式:帧类型(音频帧/事件帧)+ 序列号 + 载荷数据,音频分片流式传输,附带时序编号防止乱序
每条WS读写goroutine绑定独立的ctx,复用TaskFlow的全局取消上下文:ctx取消时主动调用conn.Close()关闭ws连接,读写循环立刻退出;
增加重连、丢包检测、缓冲区限流逻辑,防止音频堆积造成延迟飙升
barge-in 整体链路与Go实现
ASR服务实时人声检测,识别到用户发色后,组装barge-in事件包,经由S2S WS发送给TASKFlow编排服务;
给编排服务解析到打断事件,主动调用会话顶层的rootCancel()触发整条流程的取消链路;
TTS播报任务被ctx信号终止,立刻关闭下发音频的WS流,清空待发送的语音缓存
增加防抖机制:过滤环境噪音、短促呼吸声,通过时间窗口过滤无效打断事件
会话所有运行数据(对话上下文、播报进度、打断开关、任务运行状态、Trace 信息)不存服务本地内存,全部持久化到 Redis 集群;多台服务实例负载均衡切换、机器故障重启时,新接管的实例从 Redis 读取会话数据,对话无感续上,这个过程就是会话迁移;联调就是打通编排、WS、语音服务、Redis 整套流程,解决切换后的各类 bug。
会话数据使用Protobuf序列化后存入Redis,以session:{sessionID}作为唯一Key,配置过期时间自动清理僵尸会话
封装Redis统一操作SDK:会话创建、状态更新(运行/打断/取消/结束)、会话读取、会话销毁方法;所有状态变更原子写入Redis;
会话迁移机制:客户端TCP连接被LB转发到其他服务实例,新实例拿到sessionID后第一时间拉去Redis完整会话数据,重新初始化TaskFlow流程、恢复WS流状态
高频更新场景使用Redis Hash结构存储细分字段,减少整段数据覆盖带来的性能损耗,并发更新场景使用Redis原子命令/分布式锁解决数据覆盖问题
一次对话横跨网关、TaskFlow 编排、ASR、Agent 大模型、TTS、Redis 多个服务,出现卡顿、超时、打断失败、取消不生效等线上问题时,依靠 Trace 链路追踪每个步骤的耗时、报错、执行状态,精准定位故障点;埋点就是在代码关键位置插入上报逻辑,采集链路数据。
基于OpenTelemetry做链路追踪,一次会话生成全局唯一TraceID,所有子任务、网络请求生成独立SpaceID,Trace ID全程放在context透传给所有服务
分层创建Span:
顶层Span:一次完整对话会话
子Span:每个DAG Task执行Span、WS收发数据Span、barge-in事件Span、Redis读写Span、任务取消Span
每个Span记录核心信息:执行耗时、执行结果(成功/取消/失败)、错误堆栈、关键参数、取消触发原因
配套Metrics指标打点:任务取消率、barge-in打断成功率、各节点平均耗时,结合结构化日志(携带Trace ID)做问题检索