在线咨询
专属客服在线解答,提供专业解决方案
工单支持
专业技术支持团队,随时响应服务需求

语音智能体可观测性:状态、延迟、错误与字幕

「智能体不说话」是语音开发里最难查的一类问题。可能是 RTC 没连上、ASR 没出字、LLM 超时、TTS 挂了,也可能什么都正常,只是客户端忘了播放远端音轨——五种原因,用户侧的表现一模一样,都是一片安静。

解决办法只有一个:把状态、指标、错误、字幕放到同一条时间线上,一眼看出断在哪。用 agora-agent-client-toolkit@2.9.0 就能做到。

> 开始之前 凭证和客户端都沿用《Next.js 搭建浏览器端语音智能体》,本篇只加事件监听。


一. 事件通道与准备工作

RTC:音频 + 兼容的字幕数据流
RTM:智能体状态、管线指标、模块错误、消息回执
                ↓
        AgoraVoiceAI 2.9.0
                ↓
     事件时间线 / 监控 / 告警

客户端需要 RTC 4.24.6 和 RTM 2.2.4,并且已经用同一个用户 UID/Token 加入了目标频道。

本篇不需要新增凭证,沿用 《Next.js 搭建浏览器端语音智能体》的那几个就行。事件开关是创建智能体时的请求字段,不是密钥。


二. 服务端要开的字段

agent = Agent(
    client=client,
    advanced_features={"enable_rtm": True},
    parameters={
        "audio_scenario": "chorus",
        "data_channel": "rtm",
        "enable_metrics": True,
        "enable_error_message": True,
    },
).with_stt(FengmingSTT()).with_llm(llm).with_tts(tts)

这几项少一个,客户端就可能只收到部分事件。别用「能听到声音」来代替事件通道的验收。声音正常不代表指标和错误事件通了,等到线上出问题才发现没数据就晚了。


三. 最小客户端代码

import {
  AgoraVoiceAI, AgoraVoiceAIEvents, TranscriptHelperMode,
  type AgentMetric,
} from "agora-agent-client-toolkit";

type TimelineEvent = {
  at: number;
  kind: "state" | "metric" | "error" | "transcript";
  detail: string;
};

const timeline: TimelineEvent[] = [];
const record = (event: Omit<TimelineEvent, "at">) => {
  timeline.push({ at: Date.now(), ...event });
  if (timeline.length > 200) timeline.shift();
};

export async function observeAgent(rtcClient: unknown, rtmClient: unknown, channel: string) {
  const ai = await AgoraVoiceAI.init({
    rtcEngine: rtcClient as never,
    rtmConfig: { rtmEngine: rtmClient as never },
    renderMode: TranscriptHelperMode.TEXT,
    enableLog: true,
  });

  ai.on(AgoraVoiceAIEvents.TRANSCRIPT_UPDATED, items => {
    const latest = [...items].reverse().find(item => item.text);
    if (latest) record({ kind: "transcript", detail: `${latest.uid}: ${latest.text}` });
  });
  ai.on(AgoraVoiceAIEvents.AGENT_STATE_CHANGED, (_, event) => {
    record({ kind: "state", detail: String(event.state) });
  });
  ai.on(AgoraVoiceAIEvents.AGENT_METRICS, (_, metric: AgentMetric) => {
    record({ kind: "metric", detail: `${metric.type}/${metric.name}: ${metric.value}ms` });
  });
  ai.on(AgoraVoiceAIEvents.MESSAGE_ERROR, (uid, error) => {
    record({ kind: "error", detail: `${uid}/message/${error.code}: ${error.message}` });
  });
  ai.on(AgoraVoiceAIEvents.AGENT_ERROR, (uid, error) => {
    record({ kind: "error", detail: `${uid}/${error.type}/${error.code}: ${error.message}` });
  });
  ai.subscribeMessage(channel);
  return { ai, timeline };
}

页面卸载或结束对话时,这两行必须执行:

ai.unsubscribe();
ai.destroy();

漏了它们,React Strict Mode 重新挂载后事件会重复注册,表现为一条字幕出现两次、日志重复上报。这类问题在开发环境很容易被当成「玄学」,其实就是实例没销毁。


四. 上生产的观测建议

  • 状态转换记全 listening → thinking → speaking,每段停留多久也记下来。哪一段异常变长,问题就在哪一环。
  • 延迟按会话和模块统计 p50/p95/p99。只看平均值会漏掉真正影响体验的长尾。
  • 错误保留 module/type/code,用户文本、Token、密钥则要脱敏。
  • 字幕要区分进行中、已完成和被打断的 turn。别把每条中间字幕都当成一次独立的用户轮次,会把统计数据算飞。
  • 服务端保存 agentId/channel 到业务 sessionId 的映射,顺着用户投诉就能查到具体会话。但 Certificate 和模型凭据不能给前端。

五. 运行与验收

npm run typecheck
npm run build
npm run dev

完成两轮对话,并在第二轮主动打断。事件面板上必须同时出现这四类:

  1. 至少一次智能体状态变化。
  2. 至少一条带数值的管线指标。
  3. 用户和智能体双方的字幕。
  4. 一条人为诱发的错误。做法:在**独立的测试进程**里临时把 MiniMax Key 改成无效值,确认错误面板能拿到 module/code/message。别去动生产环境或 .env.local 里的真实凭证。

面板上你会看到状态在持续变化、tts/ttfb 这类指标带着真实数值、双方字幕不断刷新;错误测试进程那边则会收到一条 tts/1000 事件,module、code、message 三项齐全。


六. 故障排查

  • 只有字幕,没有状态和指标:检查 RTM 是否在 AgoraVoiceAI.init() 之前完成登录,以及服务端的 data_channelenable_metrics 有没有配。
  • 同一事件重复出现:多半是每次重连都新建了 Toolkit,却没销毁旧实例。
  • 错误事件收不到:检查 enable_error_message=true,并且 MESSAGE_ERRORAGENT_ERROR 两个事件都要监听。
  • 指标时间看着不对:统一用毫秒时间戳,别把事件上报时间当成模块耗时,这两个差得很远。

七. 下一步

客户端这条线通了,还有一条页面关掉也不能丢的线——服务端回调。

《语音智能体的服务端 Webhook:验签、幂等与订阅》

在声网,连接无限可能

想进一步了解「对话式 AI 与 实时互动」?欢迎注册,开启探索之旅。

本博客为技术交流与平台行业信息分享平台,内容仅供交流参考,文章内容不代表本公司立场和观点,亦不构成任何出版或销售行为。