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_trace(agentops/sdk/core.py:47 TraceContext.__exit__)。所以你既可以 with agentops.start_trace() as ctx:,也可以手动 start/end。
3.3 创建一个 trace:start_trace 走一遍
思路: 创建根 span 要特别处理“没有父”这件事——它必须是根,不能认错父。
TracingCore.start_trace(agentops/sdk/core.py:381)做三件事:
- 拼属性(tags;若是默认
"session"trace 还会加上主机/CPU/内存等系统元数据)。 - 调
make_span(trace_name, span_kind=SpanKind.SESSION, ...)创建并激活 span。 - 把得到的
TraceContext记进_active_traces(以十六进制 trace_id 为键,加锁),后面 shutdown / “结束所有 trace” 靠它。
关键在 make_span(agentops/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)。取值来自 AgentOpsSpanKindValues(agentops/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_decorator(agentops/sdk/decorators/__init__.py:13),但它没有被导出到包顶层。换句话说:上表里operation这一行的“种类值”确实存在于AgentOpsSpanKindValues,但当前没有任何公开装饰器 会产出它。
这是后台/dashboard 区分“这个 span 是 agent 还是 tool”的依据。
3.5 结束一个 trace:end_trace 与 finalize_span
要解决的小问题: 根 span 结束时必须做三件事,少一件都会丢数据。
end_trace(agentops/sdk/core.py:430)允许传 None——那就“结束所有活跃 trace”(靠 _active_traces)。具体收尾在 _end_single_trace(agentops/sdk/core.py:464)里:写入 agentops.session.end_state 属性 → 调 finalize_span → 从活跃表里删 → 再打一次重放 URL。
finalize_span(agentops/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_telemetry(agentops/sdk/core.py:78)是初始化时搭 OTel 脚手架的地方。一口气装了这些:
- 一个
TracerProvider,带着 resource 属性(service.name、project_id、导入的库列表。详见agentops/sdk/attributes.py:56get_global_resource_attributes)。 - 两个 span processor:
BatchSpanProcessor:攒一批、定期刷、走AuthenticatedOTLPExporter出去(§04 详讲)。InternalSpanProcessor(agentops/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):程序退出时 shutdown(agentops/sdk/core.py:271)会把所有还没结束的 trace 以 "Shutdown" 状态收尾、force flush、再 shutdown provider。这保证了“脚本跑完没手动 end”也不丢数据。
代码地图
| 主题 | 文件 | 符号名 |
|---|---|---|
| 中枢类 | agentops/sdk/core.py | TracingCore |
| 进程单例 | agentops/sdk/core.py | tracer |
| 会话握手 | agentops/sdk/core.py | TraceContext / TraceContext.__exit__ |
| 起 trace | agentops/sdk/core.py | start_trace / make_span |
| 终 trace | agentops/sdk/core.py | end_trace / _end_single_trace / finalize_span |
| 搭 OTel | agentops/sdk/core.py | setup_telemetry |
| 内部 processor | agentops/sdk/processors.py | InternalSpanProcessor |
| span 种类 | agentops/semconv/span_kinds.py | AgentOpsSpanKindValues / SpanKind |
| 属性生成 | agentops/sdk/attributes.py | get_trace_attributes / get_session_end_attributes |