跳转至

如何将上下文管理器应用于 Agent 链路追踪,包括 LLM 调用耗时、异常捕获,以及与 OpenTelemetry 的结合?

Agent 调用链往往涉及工具调用、LLM 推理、检索增强等多个环节,排查慢调用和异常全靠日志是地狱模式。

上下文管理器天生为“进入-退出”模式设计,做链路追踪是一等公民。


🎯 核心思路:用 with 块包裹“追踪单元”

每个关键操作(LLM 调用、工具执行、RAG 检索)都作为一个 Span,用上下文管理器自动记录:

  • ⏱️ 耗时

  • ❌ 异常

  • 📝 元数据(模型名、prompt token 数、工具名等)

跟 OpenTelemetry 集成后,这些 Span 自然串成 Trace,直接推到 Jaeger / Zipkin。


🔧 实战:定制 @traceable 上下文管理器

from contextlib import contextmanager
from time import perf_counter
import traceback
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

@contextmanager
def traced_span(name: str, attributes: dict = None):
    span = tracer.start_span(name, attributes=attributes or {})
    start = perf_counter()
    try:
        yield span          # 可在 with 体内添加事件
    except Exception as e:
        span.record_exception(e)
        span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
        raise
    finally:
        duration = perf_counter() - start
        span.set_attribute("duration_ms", duration * 1000)
        span.end()

🧠 Agent 场景应用:LLM 调用追踪

def call_llm(prompt: str, model="gpt-4"):
    with traced_span("LLM Call", {"model": model, "prompt_len": len(prompt)}) as span:
        try:
            response = openai.chat.completions.create(...)
            span.set_attribute("usage.total_tokens", response.usage.total_tokens)
            span.set_attribute("status", "ok")
            return response
        except Exception as e:
            span.set_attribute("status", "fail")
            raise

🔗 多层嵌套自动形成父子关系(OpenTelemetry 的 context 传播):

Agent Loop
 └─ Tool Select
     └─ LLM Call
     └─ Web Search
         └─ HTTP Request

⚡ 升级:装饰器一步到位

def trace(name=None, attrs=None):
    def decorator(func):
        def wrapper(*args, **kwargs):
            span_name = name or func.__name__
            with traced_span(span_name, attrs) as span:
                return func(*args, **kwargs)
        return wrapper
    return decorator

@trace("LLM Reasoning", {"type": "agent"})
def reason(state: AgentState):
    ...

📊 与 OpenTelemetry 结合的关键点

关键能力 实现方式
自动注入 TraceID 无需手动传递,OpenTelemetry 自动管理 context
关联 Agent 事件 在 Span 内 span.add_event("token_used", {"count": n})
全链路异常关联 record_exception 自动带上堆栈,错误率立刻可见
零侵入接入框架 重写 BaseTool 的 run 方法,套一层 traced_span

🧨 面试加分点

  • 异步支持:用 @asynccontextmanager 做 async with,适配异步 Agent。

  • 开销控制:生产环境建议用采样 (Sampling),而非每个请求都全量 Trace。

  • Token 用量归因:在 LLM Span 上打上 llm.usage.total_tokens,成本追踪利器。

  • 兼容 Dify/LangChain:直接覆盖它们的 on_llm_start 回调,注入自定义 span。

最后考你:当 Agent 内部有大量短小工具调用时,如何在不丢失可见性的前提下避免 Span 爆炸?想想 SpanKind.INTERNAL 与自定义采样逻辑的结合。 😏