主题
语音追踪基础
在 LangSmith 中追踪语音和音频 agent 的最佳实践,包括对话音频、单追踪对话和音频模态标志。
追踪语音 agent 与追踪文本 agent 不同。对话是连续、双向且可中断的:用户会打断 agent、在句子中途改变话题,并期望亚秒级响应。要调试和评估这些系统,你的追踪需要将对话捕获为一个单一的、音频感知的单元,而不是一系列不连贯的文本交换。
本页介绍在 LangSmith 中追踪语音应用程序的核心约定。无论你使用哪种框架或模型提供商(OpenAI Realtime、Gemini Live、LiveKit、Pipecat 或你自己的),都请遵循这些模式。
两种架构,两种追踪形态
你构建语音 agent 的方式决定了追踪的外观。有两种常见架构,它们会产生根本不同的追踪。
级联
级联将独立的单一用途模型串联起来:语音转文本 (STT) 转写用户的音频,语言模型 (LLM) 对文本进行推理并决定要做什么,文本转语音 (TTS) 合成回复。中间件、工具调用和检索步骤位于它们之间。
由于每个阶段都是具有明确输入和输出的独立模型调用,级联的追踪方式与其他 agent 流水线相同。追踪是由 STT、LLM、TTS、工具和中间件运行组成的树:各阶段可以并行运行,并且对话的每个轮次都会重复新的 STT → LLM → TTS 循环。这些运行具有有意义的输入/输出对(音频输入 → 转录输出,提示输入 → 补全输出)。
构建级联语音 agent 最常用的两个框架是 LiveKit 和 Pipecat。
语音到语音 (S2S)
语音到语音模型(例如 OpenAI Realtime API 或 Gemini Live)原生处理音频,并通过单个持久连接(通常是 WebSocket)以音频回复。没有可供追踪的 STT/LLM/TTS 分解。
相反,模型服务器和你的客户端通过连接交换一系列事件:音频块、转录片段、工具调用请求、轮次边界、中断和错误。自然的追踪单元是事件载荷,而不是请求/响应对。你记录的每个事件都会成为一个 span,其内容是跨连接传输的载荷。
本页其余部分介绍适用于两种架构的约定。提供商指南涵盖 OpenAI Realtime 和 Gemini Live 的事件流细节。
核心约定
以下是我们建议的做法,可帮助你充分利用 LangSmith 中的语音追踪。你可以根据自身基础设施和实现选择最适合的方式追踪语音应用程序,但遵循我们在此建议的结构将有助于使追踪保持一致,并易于调试和评估。
我们推荐三条高层约定:
- 将每个对话作为单个追踪,而不是将其拆分为多个追踪。
- 录制单个合并的音频文件并将其附加到根运行。
- 使用
ls_modality将追踪标记为音频,以便其渲染和筛选为语音追踪。
将每个对话作为单个追踪
对话是一次单一交互,因此我们建议将其保留在单个追踪中,将各个模型调用或事件嵌套在代表整个对话的一个根运行之下。
不要将对话拆分为多个追踪。如果你为每次交换启动新追踪,就会丢失存在于交换之间的信息:
- 中断:用户打断 agent 且 agent 停止(barge-in)。
- 时序和延迟:说话者之间的间隙,以及 agent 响应所需的时间。
- 上下文:对对话早期部分的引用。
- 对话级结果:用户的目标最终是否得到解决。
根运行下挂载什么取决于你的架构。对于级联,子级是模型调用和中间件:
text
conversation ← root run (whole conversation; combined audio; ls_modality="audio")
│
├─ stt ← a transcription call
├─ llm ← a model call (may include middleware and tool runs)
├─ tts ← a synthesis call
└─ ... ← the pattern repeats as the conversation continues对于语音到语音 agent,子级是跨 socket 传输的事件:
text
conversation ← root run (whole conversation; combined audio; ls_modality="audio")
│
├─ input_transcription ← a fragment of the user's speech transcript
├─ output_transcription ← a fragment of the agent's speech transcript
├─ function_call: get_weather ← the model requested a tool
├─ function_response: get_weather ← the tool result heading back to the model
├─ turn_complete ← a turn boundary reported by the server
└─ interrupted ← the server detected user barge-inNOTE
语音 agent 没有可靠的 "轮次" 概念。说话者会重叠、打断并逐渐停止。不要将运行分组合成为轮次。请改为追踪真实单元:级联中的模型调用,或语音到语音流中的事件载荷。
有关分组相关运行的背景知识,请参阅嵌套追踪。要为某个用户分组多个独立会话,请使用线程。
录制单个合并的音频文件
将一个包含用户和 agent 双方的音频文件附加到根运行,录制自实际播放给客户端的内容,而不是模型生成的音频。
在客户端录制。一种常见方法是使用立体声 WAV,一个声道为用户麦克风,另一个声道为在扬声器处捕获的 agent 语音。这很重要,因为生成的音频和听到的音频并不相同:网络延迟、丢包或乱序,以及打断都会改变用户的实际体验。如果在句子中途打断 agent,录音中应显示被截断,因为实际情况就是如此。录制播放出来的内容,而不是可能从未被听到的生成内容,才能使追踪忠实于真实交互。
使用附件 API附加文件:
python
from langsmith import traceable
from langsmith.schemas import Attachment
@traceable(name="conversation", metadata={"ls_modality": "audio"})
def run_conversation(session_id: str, conversation_audio: bytes):
# conversation_audio: a single recording of what was played to the client
# (e.g. stereo WAV: user mic on L, agent speech at the speaker on R)
...
return {"conversation": Attachment(mime_type="audio/wav", data=conversation_audio)}TIP
音频文件可能很大。对于高吞吐量的生产工作负载,请考虑降采样、使用压缩格式(如 MP3 或 Opus),或对哪些对话进行完整录制进行采样。
将追踪标记为音频
在根运行上将 ls_modality 元数据字段设置为 "audio"。这会将追踪标记为语音追踪,以便 LangSmith 正确渲染,并且你可以在项目中筛选语音追踪。
python
from langsmith import traceable
@traceable(
name="conversation",
metadata={"ls_modality": "audio"},
)
def run_conversation(session_id: str):
...NOTE
有关其他 ls_ 元数据字段,请参阅元数据参数参考。
后续步骤
Trace OpenAI Realtime
追踪基于 OpenAI Realtime API 构建的语音 agent。
Trace Gemini Live
追踪基于 Gemini Live API 构建的语音 agent。
Trace LiveKit
追踪使用 LiveKit Agents 构建的语音 agent。
Trace Pipecat
追踪使用 Pipecat 构建的语音 agent。
Upload files with traces
将对话音频录音附加到你的追踪。
Log multimodal traces
在 LangSmith UI 中渲染音频和其他媒体。