跳到内容

使用 Traces

Tracing 是让 Agent-lightning 几乎可以在不重写核心逻辑的情况下训练任何 agent 的秘密能力。这个想法源于 LLMOps 工作流中的可观察性工具,并且在 Agent-lightning 中,它演变成学习循环中的一流原语。除了帮助你了解 rollout 中发生的事情之外,traces 还提供 reward spans 和其他学习信号,这些信号为强化学习和微调算法提供支持。

OpenTelemetry spans

Agent-lightning 将每个记录的操作存储为 SpanLightningStore 中。命名源自 OpenTelemetry spans,如上图所示。一个 span 可以表示一个 LLM 调用、一个工具调用、一个图边、一个显式 reward 发射,或者一个任意的 Python 代码块。Spans 形成一个树,其中父 spans 描述高级步骤,而子 spans 记录详细的工作。以下部分将介绍 spans 的生成方式以及它们到达存储后如何解释。

写入 Spans

大多数 Runner 实现会将一个 Tracer 纳入 agent 的生命周期中。tracer 负责安装 instrumentation、缓冲 OpenTelemetry spans,并将它们提交到 LightningStore。当 runner 执行 rollout 时,它会分配一个存储支持的 tracing 上下文

async with tracer.trace_context(
    name="my-rollout",
    store=store,
    rollout_id=rollout.rollout_id,
    attempt_id=attempt.attempt_id,
):
    await run_agent_logic()

上下文管理器然后从存储中请求序列号,将 OpenTelemetry spans 转换为 Span 对象,并在尝试的中间或结束时(取决于 tracer 实现)将它们持久化。Agent-lightning 提供了两个开箱即用的 tracer;两者都依赖于 OpenTelemetry Traces,并且忽略 metrics 或 logs。

什么是 instrumentation?

简单来说,instrumentation 意味着在你的代码中添加“补丁”或钩子,以便你可以在运行时观察它的行为。把它想象成在飞机上安装飞行记录仪——instrumentation 会记录关键的操作、输入、输出和时间,而不会改变代码的行为。在 Agent-lightning tracers 中,这种 instrumentation 会自动创建 spans(小的、结构化的工作记录),显示 agent 的每个部分做了什么、花费了多长时间以及不同的步骤如何连接在一起。

AgentOps Tracer

AgentOpsTracer 将是在使用 Trainer 但未显式指定 tracer 时默认的 tracer。它启动 AgentOps SDK 本地,安装提供的 instrumentation 钩子(LangChain、LangGraph、LiteLLM、FastAPI 等),由 AgentOps Python SDK 提供,并通过本地 OpenTelemetry TracerProvider 将所有内容转发。 AgentOpsTracer 从不调用托管的 AgentOps 服务;相反,它附加了一个由 Agent-lightning 团队实现的 LightningSpanProcessor,以便将 spans 捕获并直接发送到存储。

因为它共享 AgentOps instrumentation 表面,任何受 AgentOps 支持的框架都会自动在 Agent-lightning 中获得 tracing。我们还在 AgentOps 之上添加了额外的钩子,以捕获 SDK 今天遗漏的功能

  1. 某些提供程序会发出额外的元数据——例如,vLLM 返回的 token ID——这些元数据未被库存 SDK 记录。我们用缺失的有效负载来扩充这些 spans。
  2. AgentOps 会尽最大努力构建父子关系,但混合 instrumentation(例如,OpenAI Agent SDK 与直接的 OpenAI Chat Completion 调用)可能会导致段断开连接。我们的实现(实际上是在 TracerTraceToTriplet 适配器中实现)在可以从 rollout 上下文中推断层次结构时,会修复这些关系。
  3. 某些版本的下游框架根本不会为关键事件发出 spans(LangGraph 节点入口是一个常见的例子)。tracer 安装了轻量级的 shim,以便这些 spans 一致地出现。

如果供应商集成行为异常,鼓励用户将 tracer 与 Hooks 结合使用,以检查原始 spans 或诊断,或者为相关框架实现专门的 tracer。

OpenTelemetry Tracer

OtelTracer 是一个最小的实现,它初始化一个 vanilla TracerProvider,并让你通过标准的 opentelemetry.trace API 直接控制 span 创建。当你在 agent 中已经有显式 instrumentation 时,或者 AgentOps SDK 不支持你的框架时,或者你想从业务逻辑中发出自定义 spans 时,请使用它。

注意

Microsoft Agent Framework 是一个典型的例子,它具有内置的 OpenTelemetry 支持。一旦你设置了 OBSERVABILITY_SETTINGS.enable_otel = True,该框架将自动发出 OpenTelemetry spans,并且 OtelTracer 将能够捕获它们。无需额外的 instrumentation。

在你的 agent 中,你可以调用 opentelemetry.trace.get_trace_provider().get_tracer("my-agent") 并像在任何 OpenTelemetry 应用程序中一样使用该 tracer 来 创建 spans。由 OtelTracer 附加的 Lightning span 处理器保证每个 span 都会被序列化、转换并写入存储。同样适用于发出的 rewards (emit_reward) 和其他 emitter 信号,它们只是手动创建 spans 的一种特殊情况。

Weave Tracer (实验性)

WeaveTracer 是一个实验性的 tracer,它与 Weave Python SDK 集成。将其用作 AgentOpsTracer 的替代品,当 AgentOps SDK 不适合你的环境时。

Weave SDK 直接 instrumentation LLM 调用和 agent 库。与 AgentOpsTracer 不同,Weave 不依赖 OpenTelemetry 来导出 spans;它通过专用的 Weave Trace Server 路由所有内容。Agent-lightning 实现了一个自定义的 Weave Trace Server,以便 Weave SDK 捕获的每个调用都可以持久化到 LightningStore

警告

WeaveTracer 仍然是实验性的,并且没有像 AgentOpsTracer 那样经过彻底测试。它可能与默认情况下发出 OpenTelemetry instrumentation 的库(例如,基于 LiteLLM 的 LLM 代理)冲突。谨慎使用该 tracer,并将任何问题报告给 Agent-lightning 团队。

LLM 代理

有时,运行器无法直接观察到代理,因为代理使用的语言不同或运行在远程环境中。 LLMProxy 通过对 LLM 调用服务器端进行工具化来弥合这一差距。它封装了 LiteLLM 并添加了中间件,该中间件接受带有前缀的路由,例如 /rollout/{rid}/attempt/{aid}/v1/chat/completions。在转发之前,中间件会将路径重写为 /v1/chat/completions,从 LightningStore 获取一个单调递增的 sequence_id,将 x-rollout-idx-attempt-idx-sequence-id 注入到请求头中,然后将请求转发到后端 LLM 端点。

LiteLLM 为请求/响应生成 OpenTelemetry span。一个自定义的 LightningSpanExporter 从记录的请求头中读取 rollout/attempt/sequence 标识符,并将每个 span 持久化到存储中。由于 sequence_id 在请求开始时分配,因此即使在时钟偏移或异步响应的机器上,trace 也能保持严格的顺序。

sequenceDiagram
    participant Agent
    participant Proxy as LLM Proxy
    participant Backend as LLM Backend
    participant Store as LightningStore

    Agent->>Proxy: POST /rollout/{rid}/attempt/{aid}/v1/chat/completions
    Proxy->>Store: get_next_span_sequence_id(rid, aid)
    Store-->>Proxy: sequence_id
    Proxy->>Backend: Forward /v1/chat/completions<br>(headers: rid, aid, sid)
    Backend-->>Proxy: Response (tokens, usage, token_ids)
    Proxy->>Store: Export OTEL spans (rid, aid, sequence_id)
    Proxy-->>Agent: OpenAI-compatible response

LLMProxy 实际上提供了比仅仅用于 tracing 的中间件更多的功能。阅读 Serving LLM 以获取更多详细信息。

分布式追踪

Agent-lightning 通过为每次 attempt 分配一个单调递增的 sequence_id 来强制确定性的 span 排序。在调用 LightningStore.add_spanLightningStore.add_otel_span 之前,tracer 预计会调用 LightningStore.get_next_span_sequence_id 来获取下一个 sequence id。这消除了时钟偏移,并合并了在不同机器或线程上生成的 span。如果您实现自定义 tracer 或 exporter,请确保执行此操作(或尊重 LLMProxy 等组件在 header 中提供的那个);否则,适配器将难以正确重建执行树。

自定义 Tracer

如果内置的 tracer 都无法适应您的环境,那么首先要考虑的选项是直接从您的 agent 实现中 返回 span。如果这不可行,或者您想在一个统一的努力中支持多个 agent,您可以可以通过继承 Tracer 来实现您自己的 tracer。

自定义 tracer 必须至少实现 trace_contexttrace_context 协程应该安装或激活您需要的任何工具化,然后 yield 一个 span 处理器,最终将 span 添加到存储中。如果您生成 OpenTelemetry ReadableSpan 对象,可以使用 LightningSpanProcessor,或者如果您自己生成 Span 实例,则可以直接调用 LightningStore.add_span

高级 tracer 通常在 init_worker 内部运行辅助服务(例如,启动遥测守护程序或附加到容器运行时),并在 teardown_worker 中将其拆解。从 Tracer 继承的 ParallelWorkerBase 生命周期确保在每个 runner 子进程中执行这些 hook。

读取 Traces

通常,有两种方法可以读取 trace。当您只需要快速查看时,Tracer.get_last_trace 会返回最近捕获的原始 OpenTelemetry span。对于历史分析,请使用 LightningStore.query_spans API,它会 yield 归一化的 Span 对象,按 rollout ID 和 attempt ID 键入。将这些查询与 LightningStore.query_rollouts 结合使用,以将 span 与 rollout 状态、重试和时间信息对齐。

Span 以异步方式到达,来自不同的进程,并形成层次结构而不是简单的列表。每个 span 的属性既繁琐又不便于人工阅读。这种组合使得原始 trace 耗时,尤其是在您只关心特定信号(例如奖励、LLM 提示、响应或工具输出)时。了解存储如何暴露 trace 以及适配器如何重塑它们,将为您在调试或训练时节省数小时。

为什么 trace 难以阅读?

单个 rollout 的 trace 树通常混合了多个抽象层:一个 planner span 可能包含几个 LLM span,其中每个 span 包含工具执行 span,这些 span 本身可以触发嵌套的 agent 调用。不同级别也有工具化。例如,当请求委托给另一个库(例如,从 LangChain 到 OpenAI)时,两个库可能会为同一个请求发出 span。在顶层,可能存在并发运行的 agent,它们可能会略微失序地刷新 span。按 sequence_id 排序可恢复时间顺序视图,但解释树需要有关父子关系的额外上下文和 rollout 元数据。

适配器

Adapters 将 span 列表转换为训练算法可以直接使用的更高层次的数据结构。Agent-lightning 提供了几个开箱即用的适配器

Adapter 是常规的 Python 可调用实例,因此您可以将它们通过 adapter 参数插入到 Trainer 中,或者在探索期间手动调用它们。在 Trainer 中使用时,adapter 会被打包到 Algorithm 中,算法运行之前,通过 Algorithm.set_adapter 方法。

您还可以通过扩展上述实现或继承基类来定制 Adapter。如果您需要定制格式,请继承 TraceAdapter(用于存储 span)或 OtelTraceAdapter(用于原始 OpenTelemetry span)并实现 adapt(这两个类通常可以共享相同的实现)。

读取奖励

奖励记录为命名为 agentlightning.annotation 的专用 span。通过 emit_rewardemit_annotation 发出奖励可确保该值存储在 span 的 attributes 中。要审核奖励,请从存储中获取 span,并使用 agentlightning.emitter 中的辅助工具

from agentlightning.emitter import find_final_reward

spans = await store.query_spans(rollout_id)
reward = find_final_reward(spans)
print(f"Final reward: {reward}")

find_reward_spans 返回每个奖励 span,以便您可以可视化中间的塑造信号,而 find_final_reward 提取每个 attempt 的最后一个非空奖励。虽然这些辅助工具很方便,但它们可能无法帮助您完全理解奖励 span 与其他 span 之间的时间或层次关系。使用 Adapter — 尤其是您正在使用的算法中使用的那个 — 仍然是检查生成的 span 的推荐方法。