语音AI的延迟简直是噩梦
要把这种流式传输的链路跑通,不能靠自动注入,因为LLM返回的是异步生成器。如果你用常规的追踪方式,Span会在生成器创建时就结束,而不是在流结束时结束。这导致监控数据完全失真。
下一篇
分享一个从底层逻辑理解LLM的视角 →
为了解决这个问题,我写了一个自定义的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 里看到数值,而没法做全局的延迟分析。