以编程方式使用文档

追踪 追踪语音智能体与追踪文本智能体不同。对话是连续的、双向的、可中断的:用户会与智能体抢话、在句中改变话题,并期望亚秒级响应。为了调试和评估这些系统,你的追踪需要将对话捕获为一个单一的、音频感知的单元,而不是一系列断开的文本交换。

本页面介绍了在 LangSmith 中追踪语音应用的核心约定。无论你使用哪个框架或模型提供商,都应遵循这些模式(OpenAI Realtime, Gemini Live, LiveKit, Pipecat,或你自己的)。

两种架构,两种追踪形状

你构建语音智能体的方式决定了追踪的外观。有两种常见的架构,它们产生根本不同的追踪。

级联

级联将独立的单一用途模型链接在一起:语音转文本(STT)转录用户的音频,语言模型(LLM)对文本进行推理并决定要做什么,文本转语音(TTS)合成回复。中间件、工具调用和检索步骤穿插其中。

因为每个阶段都是一个具有明确输入和输出的离散模型调用,所以级联的追踪方式与任何其他智能体管道相同。追踪是一个 STT, LLM, TTS, tool, and middleware runs: stages can run in parallel, and a new STT → LLM → TTS cycle repeats for each turn of the conversation. These runs have meaningful input/output pairs (audio in → transcript out, prompt in → completion out).

构建级联语音智能体最常用的两个框架是 LiveKitPipecat.

语音转语音(S2S)

语音转语音模型(例如 OpenAI Realtime API or Gemini Live) processes audio natively and replies with audio over a single persistent connection, typically a WebSocket. There is no STT/LLM/TTS decomposition to trace.

相反,模型服务器和你的客户端通过线路交换一系列 **事件** 流:音频块、转录片段、工具调用请求、轮次边界、中断和错误。追踪的自然单元是 **事件载荷**, not a request/response pair. Each event you record becomes one span whose content is the payload that crossed the wire.

本页面的其余部分描述适用于两种架构的约定。提供商指南涵盖了 OpenAI RealtimeGemini Live.

核心约定

这些是我们建议的在 LangSmith 中充分利用语音追踪的最佳实践。你应该以最适合你基础设施和实现的方式追踪你的语音应用,但遵循我们在这里建议的结构将有助于使你的追踪保持一致,并且易于调试和评估。

我们建议三个高级约定:

  1. **将每个对话作为单个追踪进行追踪** 而不是将其拆分为多个追踪记录。
  2. **录制单个合并的音频文件** 并将其附加到根运行。
  3. **将追踪记录标记为音频** 使用 ls_modality 以便将其作为语音追踪记录进行渲染和筛选。

将每次对话作为单个追踪记录

对话是单个交互,因此我们建议将其保持在单个追踪记录中,并将各个模型调用或事件嵌套在一个代表整个对话的根运行下方。

不要将对话拆分为多个追踪记录。如果您在每次交换时开始新的追踪记录,您将丢失位于 **之间** exchanges:

  • 中断:当用户打断代理且代理停止时(介入)。
  • 时间和延迟:说话者之间的间隔,以及代理响应所需的时间。
  • 上下文:回顾对话的前面部分。
  • 对话级别的结果:用户的最终目标是否实现。

根运行下方悬挂的内容取决于您的 架构。对于 级联,子节点是模型调用和中间件:

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

对于 speech-to-speech agent,子节点是 **事件** 跨过套接字的事件:

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-in

有关将相关运行分组的背景信息,请参阅 嵌套追踪。要将多个独立会话归为一个用户,请使用 线程.

录制一个合并的音频文件

附加 **一个** 音频文件到包含以下内容的根运行 **两者** 用户和 agent 的音频,录制来源为 **实际播放给客户端的内容**,而不是模型生成的音频。

在客户端录制。常见做法是录制立体声 WAV,用户麦克风在左声道,agent 语音(从扬声器捕获)在右声道。这很重要,因为生成的音频和听到的音频不是同一回事:网络延迟、丢弃或重新排序的数据包,以及插话都会改变用户体验。插话在 agent 说话中途打断,应该在录音中表现为截断,因为这正是发生的事情。录制播放的内容,而不是生成的但可能从未被听到的内容,这才是使追踪忠实于真实交互的方式。

使用以下方式附加文件 附件 API:

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)}

将追踪标记为音频

设置 ls_modality 元数据字段为 "audio" 在根运行上。此操作将追踪标记为语音追踪,以便 LangSmith 能够正确渲染,您也可以 筛选 项目中的语音追踪。

from langsmith import traceable

@traceable(
    name="conversation",
    metadata={"ls_modality": "audio"},
)
def run_conversation(session_id: str):
    ...

后续步骤

Trace OpenAI Realtime

追踪基于 OpenAI Realtime API 构建的语音代理。

Trace Gemini Live

追踪基于 Gemini Live API 构建的语音代理。

Trace LiveKit

追踪使用 LiveKit Agents 构建的语音代理。

Trace Pipecat

追踪使用 Pipecat 构建的语音代理。

Upload files with traces

将对话音频录制附加到您的追踪中。

Log multimodal traces

在 LangSmith UI 中渲染音频和其他媒体。