语音AI的延迟简直是噩梦

创业者阿杰 中级 3小时前 更新于 2026年7月27日 522 浏览 12 点赞 约 1 分钟

要把这种流式传输的链路跑通,不能靠自动注入,因为LLM返回的是异步生成器。如果你用常规的追踪方式,Span会在生成器创建时就结束,而不是在流结束时结束。这导致监控数据完全失真。

为了解决这个问题,我写了一个自定义的OpenTelemetry拦截层,通过手动管理Trace生命周期来记录关键节点。

核心实现:拦截异步流

我定义了一个 Python 装饰器,不再是简单地包装函数,而是向 Agent 注入一个上下文对象,让 AI 逻辑在真正产生首个音频字节时才触发时间戳记录。

from opentelemetry import trace
import time

tracer = trace.get_tracer("zooid.voice")

def instrument_voice_app(func):
    async def wrapper(*args, **kwargs):
        with tracer.start_as_current_span("voice_turn") as span:
            turn_start = time.time()
            context = VoiceTurnContext()
            
            try:
                # 执行语音 Agent 逻辑
                result = await func(context, *args, **kwargs)
                
                # 记录语音特有指标:首音频延迟(TTFA)、STT置信度、Token成本
                if context.first_audio_time:
                    ttfa = context.first_audio_time - turn_start
                    span.set_attribute("voice.ttfa_ms", ttfa * 1000)
                
                if context.stt_confidence:
                    span.set_attribute("stt.confidence", context.stt_confidence)
                
                if context.cost_usd:
                    span.set_attribute("voice.turn_cost_usd", context.cost_usd)
                
                span.set_status(trace.StatusCode.OK)
                return result
                
            except Exception as e:
                span.set_status(trace.StatusCode.ERROR, str(e))
                span.record_exception(e)
                raise
    return wrapper

部署与踩坑:SigNoz 的数据可视化

数据发送到 OpenTelemetry Collector 之后,在 SigNoz 的 Traces 标签页能看到这些自定义属性。但坑在于:当你尝试在 Dashboard 里用 voice.ttfa_ms 画时间序列图时,经常会出现 "No Data"。

这是因为 SigNoz 底层使用 ClickHouse 存储,虽然 signoz_index_v3 架构支持存储这些数字,但自定义仪表盘的查询构建器依赖于物化视图(Materialized Views)来正确映射属性。如果属性没有被正确索引,仪表盘就抓不到数。

对于需要做 Voice AI 实战部署的人来说,建议在配置 SigNoz 时重点检查自定义属性的索引情况,否则你即便在代码里写了 set_attribute,最后也只能在单个 Trace 里看到数值,而没法做全局的延迟分析。

AI大模型LLMmonitoring

全部回复 (4)

调参侠小美 初级 9小时前
手动管生命周期太麻烦,有没有现成的插件能直接搞定?
0 回复
老大鹏 专家 9小时前
之前也被这坑卡过一周,确实得手动接管才能对上时间轴。
0 回复
咖啡续命折腾党 中级 9小时前
@老大鹏 手动对轴真的心累,你当时是用什么工具调的?
0 回复
强迫症脚本小子 专家 9小时前
记得在拦截层加个超时截断,不然流挂了Span永远不关闭。
0 回复

发表回复

支持 Markdown 格式