Opik Python SDK:用 opik_context.get_current_span_data 在 track 追踪函数中读取当前 Span 数据
【免费下载链接】comet-llmDebug, evaluate, and monitor your LLM applications, RAG systems, and agentic workflows with comprehensive tracing, automated evaluations, and production-ready dashboards.项目地址: https://gitcode.com/GitHub_Trending/co/comet-llm
本文围绕 Opik Python SDK 的 API 文档页get_current_span_data展开,讲解如何在@track装饰器包裹的函数内部获取当前正在执行的 Span 上下文数据(SpanData),包括其返回结构、返回None的边界条件、底层基于contextvars的 span 栈实现原理,以及在 Guardrails、分布式追踪等真实场景中的典型用法。读完本文,你可以掌握在任意被追踪代码中"自下而上"读取 trace_id、span id、父 span 关系、model/usage 等字段的方法,并理解它与get_current_trace_data、update_current_span、get_distributed_trace_headers等配套函数的调用关系。
文档定位:一个 API 参考页背后是完整的上下文模块
仓库中的关联文档 get_current_span_data.rst 本身是一页 Sphinxautofunction生成的 API 参考页,仅一行核心指令:
.. autofunction:: opik.opik_context.get_current_span_data它指向 SDK 中真实实现的 opik_context.py 模块里的get_current_span_data函数。该模块所属的opik_context子包整体介绍"如何从被追踪函数内部访问当前 span 和 trace 数据",完整用法示例见 opik_context/index.rst:
from opik import opik_context, track @track def my_function(): # Get the current span data span_data = opik_context.get_current_span_data() print(span_data) # Get the current trace data trace_data = opik_context.get_current_trace_data() print(trace_data) # Update the current span metadata opik_context.update_current_span(metadata={"my_key": "my_value"}) # Update the current trace tags opik_context.update_current_trace(tags=["my_tag"])下面逐层拆解get_current_span_data的签名、行为与底层机制。
函数签名与核心行为
get_current_span_data的实现位于 opik_context.py:
def get_current_span_data() -> Optional[span.SpanData]: """ Returns the current span created by track() decorator or None if no span was found. """ span_data = context_storage.top_span_data() if span_data is None: return None return span.SpanData(**span_data.__dict__)要点逐条说明:
- 无参数调用:它不接收任何参数,完全依赖"当前执行上下文"来定位 span。也就是说,调用方必须位于由
@track装饰器(或等价的 span context manager)创建的追踪函数内部。 - 返回值类型:
Optional[span.SpanData]。在追踪上下文内返回SpanData对象;若上下文中不存在任何 span,返回None而不是抛异常。这与同模块的update_current_span、get_distributed_trace_headers(上下文为空时抛出OpikException("There is no span in the context."),见 opik_context.py)形成明确对比:"读取"语义宽容,"写入/派生"语义严格。 - 返回的是副本:实现通过
span.SpanData(**span_data.__dict__)用当前栈顶 span 的全部属性重新构造了一个新的SpanData实例。从源码结构看,这是把上下文栈中维护的内部对象拷贝出来交给调用方,避免业务代码在函数执行过程中意外改动栈内状态(__dict__展开属于浅拷贝,嵌套的 metadata 等字典仍与内部对象共享引用,深度隔离并非设计目标)。
返回的 SpanData 结构有哪些字段
SpanData定义于 span_data.py,继承自 observation_data.py 的ObservationData。字段分为两组:
SpanData 自有字段:
| 字段 | 类型 | 说明 |
|---|---|---|
trace_id | str | 所属 trace 的 id(SpanData 的必填字段) |
id | str | 当前 span 的 id,默认由helpers.generate_id自动生成 |
parent_span_id | Optional[str] | 父 span 的 id,根 span 为None |
type | SpanType | span 类型,默认"general",LLM 调用为"llm" |
usage | Optional[Dict[str, Any] \| OpikUsage] | token 用量数据 |
model | Optional[str] | 所用 LLM 名称 |
provider | Optional[str \| LLMProvider] | LLM 提供方 |
total_cost | Optional[float] | 本次调用成本(USD) |
ObservationData 基类通用字段(TraceData与SpanData共享):name、start_time、end_time、metadata、input、output、tags、feedback_scores、project_name、error_info、attachments、source(默认"sdk")、environment。
拿到span_data后,一个典型用法是提取关键标识用于日志关联或跨系统透传:
from opik import opik_context, track @track def process_user_query(query: str) -> str: span_data = opik_context.get_current_span_data() if span_data is not None: # 将 trace/span 标识写入业务日志,便于与 Opik UI 中的 trace 对齐 logger.info( "processing query, trace_id=%s, span_id=%s, project=%s", span_data.trace_id, span_data.id, span_data.project_name, ) return query.upper()边界条件:何时返回 None
返回None的场景包括:
- 在追踪上下文外调用:顶层脚本、未被
@track覆盖的普通函数中直接调用,此时 span 栈为空。 - 追踪被运行时配置禁用:SDK 有 tracing 运行时开关(
tracing_runtime_config.is_tracing_active(),见 opik_context.py 中update_current_span的短路逻辑)。从源码结构看,禁用追踪时 span 不会被压入上下文栈,get_current_span_data自然返回None。SDK 的单元测试也专门覆盖了这一路径,例如 test_track_disabled_mode.py 验证了 disabled 模式下上下文 API 的表现。
因此生产代码中的稳健写法是先判空再使用:
span_data = opik_context.get_current_span_data() if span_data is None: return # 或降级到本地日志底层原理:基于 contextvars 的不可变 span 栈
get_current_span_data的数据来源是 context_storage.py 中的OpikContextStorage。@track进入被追踪函数时会add_span_data压栈,函数退出时pop_span_data出栈;get_current_span_data内部调用的top_span_data()直接取栈顶元素:
def top_span_data(self) -> Optional[span.SpanData]: if self.span_data_stack_empty(): return None stack = self._spans_data_stack_context.get() return stack[-1]该实现有两个值得注意的设计点:
- 上下文隔离:span 栈存放在
contextvars.ContextVar中,且刻意使用不可变 tuple并以"取旧值、构造新值再 set"的模式更新(见 context_storage.py 类注释与 add_span_data)。这保证了多线程、asyncio 并发任务之间各自的 span 栈互不污染——"当前 span"是相对于当前执行上下文而言的,而不是进程全局单例。 - 嵌套 @track 的语义:
@track嵌套调用时内层 span 会压到栈顶,因此内层函数里get_current_span_data拿到的是最内层那个 span,其parent_span_id指向外层 span 的id,trace_id保持一致。这一父链关系正是 Opik 在 UI 中渲染嵌套 span 树的基础。
同一文件还提供了防御性工具trim_span_data_stack_to_certain_span(context_storage.py),供回调式集成(如 LangChain callback)在可能漏掉 pop 操作时按已知 span id 截断栈,避免"悬挂 span"污染后续上下文——这也从侧面说明 span 栈模型是 SDK 追踪体系的核心数据结构。
SDK 内部真实调用场景
get_current_span_data不只是给用户用的公共 API,SDK 自身也大量依赖它。在 guardrail.py 中,Guardrail 执行时通过current_span = get_current_span_data()找到当前 span,把守卫的评估结果(通过/拦截、分数)挂载到正在执行的追踪数据上,从而让拦截行为出现在 Opik 对应 trace 的 span 详情里。类似地,get_current_trace_data的调用点散布于 llm_judge.py、ragas_metric.py 等评估与集成模块中,用于把自动评估分数回写到当前 trace。
与同模块其他函数的协作关系
opik_context模块的完整 API 面(见 opik_context.py 的__all__)可以按"读取 / 更新 / 派生"三类理解get_current_span_data的位置:
| 函数 | 类别 | 无 span 时的行为 | 典型用途 |
|---|---|---|---|
get_current_span_data() | 读取 | 返回None | 获取当前 span 的 id、trace_id、父子关系 |
get_current_trace_data() | 读取 | 返回None | 获取当前 trace 的 id 与字段 |
update_current_span(...) | 更新 | 抛OpikException | 在追踪函数内补写 metadata、output、feedback_scores 等 |
update_current_trace(...) | 更新 | 抛OpikException | 补写 trace 级字段 |
get_distributed_trace_headers() | 派生 | 抛OpikException | 拿到{opik_trace_id, opik_parent_span_id}字典,透传给远程节点实现跨进程追踪 |
一个组合场景是分布式追踪:index.rst 给出了直接取分布式头的示例;若你需要更细粒度地拼装 header,也可以先span_data = opik_context.get_current_span_data(),再用span_data.trace_id与span_data.id构造等价内容——get_distributed_trace_headers 内部正是这样做的。
验证手段
该 API 的行为有对应测试可查证:
- test_span_context_manager.py:验证
@track/ span context manager 内get_current_span_data返回的 span 数据(trace_id、parent 关系等)符合预期; - test_track_disabled_mode.py:覆盖追踪禁用时的上下文行为;
- test_distributed_headers_context_manager.py:覆盖基于当前 span 的分布式头生成;
- 端到端层面,test_tracing.py 等 E2E 用例确认了这些字段最终落库后可在检索结果中被读取。
小结
opik_context.get_current_span_data是 Opik Python SDK 中"追踪函数自省"的入口:它以零参数返回当前@track上下文栈顶的SpanData副本,无上下文时安静地返回None;其背后是contextvars+ 不可变 tuple 实现的线程/任务安全 span 栈。掌握它之后,你可以在业务代码里把 trace_id / span_id 写入日志系统做跨系统关联,在自定义 Guardrail 或评估逻辑中把结果挂回当前 span,并在此基础上用get_distributed_trace_headers把追踪跨进程传递。如需创建新的 trace/span 而非读取现有上下文,请参阅 SDK 的 context manager 文档(context_manager/index.rst 所指向的目录)。
【免费下载链接】comet-llmDebug, evaluate, and monitor your LLM applications, RAG systems, and agentic workflows with comprehensive tracing, automated evaluations, and production-ready dashboards.项目地址: https://gitcode.com/GitHub_Trending/co/comet-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考