跳到主要内容

TracingCore 与 trace/span 生命周期

本章讲整个 SDK 的中枢TracingCore。读完你会明白“一棵 span 树到底是怎么被创建、嵌套、结束、刷出去的”。

3.1 核心概念:trace = 根 span = 一个 session

要解决的小问题: AI agent 的一次运行(一个 session)里套了很多步:agent 调 task,task 调 tool,tool 里又调 LLM。怎么把这棵“树”表达出来?

思路: 不另造轮子。直接用 OpenTelemetry 的 span 树:

  • 一个 session = 一个根 span(OTel 里叫 root span)。
  • session 里的每一步 = 一个子 span,按调用嵌套自动成树。
  • AgentOps 里把“根 span / session”统称为 trace

这个决定是整个 SDK 能兼容任何 OTLP 后端的根本原因。

3.2 TraceContext:一个小握手,抓住 span + token

AgentOps 不直接用 OTel 的 with tracer.start_as_current_span(...) 来管 session,因为 session 的生命周期不一定包在一个 with 块里(你可能 start_trace() 后隔很久才 end_trace())。于是它用一个小类 TraceContext 把两样东西抓住:

  • span:那个根 span 本身。
  • token:OTel context 的 attach token——结束时要用它 detach 才不会泄露 context。

TraceContext 同时是个上下文管理器:__exit__ 里根据是否有异常自动把结束状态置为 ERROR/OK,再调 tracer.end_traceagentops/sdk/core.py:47 TraceContext.__exit__)。所以你既可以 with agentops.start_trace() as ctx:,也可以手动 start/end。

3.3 创建一个 trace:start_trace 走一遍

思路: 创建根 span 要特别处理“没有父”这件事——它必须是根,不能认错父。

TracingCore.start_traceagentops/sdk/core.py:381)做三件事:

  1. 拼属性(tags;若是默认 "session" trace 还会加上主机/CPU/内存等系统元数据)。
  2. make_span(trace_name, span_kind=SpanKind.SESSION, ...) 创建并激活 span。
  3. 把得到的 TraceContext 记进 _active_traces(以十六进制 trace_id 为键,加锁),后面 shutdown / “结束所有 trace” 靠它。

关键在 make_spanagentops/sdk/core.py:526)里区分根与非根:

# 示意,非源码:根 span 不能继承当前 context,否则会认错父
if span_kind == SpanKind.SESSION:
span = tracer.start_span(span_name) # 不传 context = 开新根
else:
span = tracer.start_span(span_name, context=current_context) # 子 span 接当前 context
# 创完都把它设为当前 span,拿到 token 留着后面 detach
ctx = trace.set_span_in_context(span)
token = context_api.attach(ctx)

重点看:SESSION 类型不传 context——这是它成为“根”的唯一机制。span 名字统一是 {operation_name}.{span_kind}(如 my_session.session)。

3.4 span 名字里藏的“语义种类”

OTel 原生 span 有个 kind(INTERNAL/CLIENT…),但那太粗。AgentOps 另起一套“语义种类”放在 span 属性 agentops.span.kind 里(agentops/semconv/span_attributes.py:89 AGENTOPS_SPAN_KIND)。取值来自 AgentOpsSpanKindValuesagentops/semconv/span_kinds.py:6):

种类值含义对应装饰器
session一个会话(根)@trace
agent一个 agent@agent
task一个任务@task@operation 是它的别名,见下)
operation一个操作operation_decorator(未导出;@operation 产生此种类)
workflow一条工作流@workflow
tool一个工具调用@tool
guardrail一个护栏检查@guardrail
llm一次 LLM 调用(自动拦截产生)
http一个 HTTP 端点@track_endpoint

一个易踩的坑: operation = task 只是个别名agentops/sdk/decorators/__init__.py:17),所以 @operation 产生的是 TASK 种类的 span,而不是 OPERATION 种类。真正产生 OPERATION 种类的是 operation_decoratoragentops/sdk/decorators/__init__.py:13),但它没有被导出到包顶层。换句话说:上表里 operation 这一行的“种类值”确实存在于 AgentOpsSpanKindValues,但当前没有任何公开装饰器会产出它。

这是后台/dashboard 区分“这个 span 是 agent 还是 tool”的依据。

3.5 结束一个 trace:end_trace 与 finalize_span

要解决的小问题: 根 span 结束时必须做三件事,少一件都会丢数据。

end_traceagentops/sdk/core.py:430)允许传 None——那就“结束所有活跃 trace”(靠 _active_traces)。具体收尾在 _end_single_traceagentops/sdk/core.py:464)里:写入 agentops.session.end_state 属性 → 调 finalize_span → 从活跃表里删 → 再打一次重放 URL。

finalize_spanagentops/sdk/core.py:581)是关键三步,注释里说得很清楚:

# 示意,非源码:根 span 收尾的三件事
span.end() # 1. 结束 span,才能算出耗时、才会触发 processor 的 on_end
context_api.detach(token) # 2. detach context token,防止 context 泄露
self.provider.force_flush() # 3. 立刻刷,不等批量——根 span 特别需要这一下

重点看第 3 步:根 span 结束后立即 force_flush。因为 session 是用户最关心的那个 span,不能让它挤在批量队列里等上几秒。

3.6 启动时装了什么:setup_telemetry

setup_telemetryagentops/sdk/core.py:78)是初始化时搭 OTel 脚手架的地方。一口气装了这些:

  • 一个 TracerProvider,带着 resource 属性(service.name、project_id、导入的库列表。详见 agentops/sdk/attributes.py:56 get_global_resource_attributes)。
  • 两个 span processor
    • BatchSpanProcessor:攒一批、定期刷、走 AuthenticatedOTLPExporter 出去(§04 详讲)。
    • InternalSpanProcessoragentops/sdk/processors.py:15):不导出,只是“本地副作用”——认出根 span,在它结束时 upload_logfile(trace_id) 把本地日志也传上去。
  • 一个 MeterProvider(指标,token/成本统计走这里)。

3.7 进程级单例与退出收尾

文件末尾一行 tracer = TracingCore()agentops/sdk/core.py:650)创建了进程唯一的全局实例。所有装饰器、client、instrumentation 都 from agentops.sdk.core import tracer 共用它。

构造函数里 atexit.register(self.shutdown):程序退出时 shutdownagentops/sdk/core.py:271)会把所有还没结束的 trace 以 "Shutdown" 状态收尾、force flush、再 shutdown provider。这保证了“脚本跑完没手动 end”也不丢数据。

代码地图

主题文件符号名
中枢类agentops/sdk/core.pyTracingCore
进程单例agentops/sdk/core.pytracer
会话握手agentops/sdk/core.pyTraceContext / TraceContext.__exit__
起 traceagentops/sdk/core.pystart_trace / make_span
终 traceagentops/sdk/core.pyend_trace / _end_single_trace / finalize_span
搭 OTelagentops/sdk/core.pysetup_telemetry
内部 processoragentops/sdk/processors.pyInternalSpanProcessor
span 种类agentops/semconv/span_kinds.pyAgentOpsSpanKindValues / SpanKind
属性生成agentops/sdk/attributes.pyget_trace_attributes / get_session_end_attributes