公司动态

LangSmith平台侧:根据span_id + parent_span_id,拼装出Trace树形视图

📅 2026/8/6 2:26:01
LangSmith平台侧:根据span_id + parent_span_id,拼装出Trace树形视图
✅真实上下文里面只存「当前正在执行的那一个span」不是存完整树。真实逻辑ContextVar里面只保存当前活跃的spanRun对象不是一整棵大树。ContextVar → current_span 【正在跑的这个span】举嵌套调用Trace(根span A) └─ Span B └─ Span C执行时序跑Acurrent_span A进入Bcurrent_span B把B压入上下文进入Ccurrent_span C压入CC执行结束弹出current_span回到BB结束弹出current_span回到AA结束current_span置None上下文里面永远只有栈顶那一个span不是把A/B/C全部塞进去。子Span创建的时候读取current_span拿到父span只拷贝父的span_id存到自己的parent_span_id字段。子span只记录父ID不持有父对象引用。整条Trace树不会保存在上下文里所有span创建完成之后各自独立上报到LangSmith/LangFuse服务端。服务端拿到一堆扁平的span数据依靠每一条span自带的parent_span_id在服务端内存把树组装出来展示给你看。 关键点进程本地不会维护完整Trace树本地只维护当前栈顶span。每个span只记住自己的父ID各个span之间本地是解耦的。树形结构是后端平台做的视图组装不是本地内存维护一棵树。为什么这样设计解耦各个span可以独立创建、独立上报不需要持有整个链路对象。并发多个Trace协程上下文隔离互相不会串数据。即使一部分span上报失败其他span依旧可以正常上报后端根据id尽可能拼接。这就是解耦业务代码不需要关心整条链路只需要知道“我的父是谁”。举个扁平上报例子发给LangSmith的http请求上报的是一条条独立的Run(Span)// span A 根节点{run_id:A,parent_run_id:null,...}// span B{run_id:B,parent_run_id:A,...}// span C{run_id:C,parent_run_id:B,...}服务收到这3条扁平记录根据parent_run_id做一次递归渲染出树形Trace视图。本地Python端根本不存在一棵完整的树对象。结合前面全套知识串一遍完整流程函数是对象回调处理器BaseCallbackHandler实例对象可以作为参数传给LangGraph组件。回调机制LLM/Tool/Node执行前后框架调用handler上的on_xx_start / on_xx_end方法。contextvars协程上下文回调内部读取current_span拿到栈顶父span拿到parent_span_id。创建子span仅记录父id压入上下文函数结束弹出。每个span独立异步http上报给平台。平台侧根据span_id parent_span_id拼装出Trace树形视图。