11"""Strands Agents tracing helpers."""
22
3+ import uuid
34import weakref
45from typing import Any
56
910
1011
1112_SPANS_BY_OTEL_SPAN : "weakref.WeakKeyDictionary[Any, Span]" = weakref .WeakKeyDictionary ()
13+ _SPANS_BY_INVALID_OTEL_KEY : dict [uuid .UUID , Span ] = {}
14+
15+
16+ class _InvalidOtelSpanKey :
17+ """Per-call key for OTEL's shared INVALID_SPAN singleton.
18+
19+ Strands keeps using the returned span object as an OTEL span, so this proxy
20+ delegates span methods to INVALID_SPAN while giving Braintrust a unique key
21+ for matching each start/end lifecycle pair.
22+ """
23+
24+ def __init__ (self , otel_span : Any ):
25+ self .key = uuid .uuid4 ()
26+ self ._otel_span = otel_span
27+
28+ def __getattr__ (self , name : str ) -> Any :
29+ return getattr (self ._otel_span , name )
30+
31+ def __eq__ (self , other : Any ) -> bool :
32+ return self ._otel_span == other
33+
34+ def __hash__ (self ) -> int :
35+ return hash (self .key )
36+
37+ def __bool__ (self ) -> bool :
38+ return bool (self ._otel_span )
1239
1340
1441def _arg (args : Any , kwargs : dict [str , Any ], index : int , name : str , default : Any = None ) -> Any :
@@ -51,15 +78,23 @@ def _agent_metrics_and_metadata(result: Any) -> tuple[dict[str, Any], dict[str,
5178 return bt_metrics , metadata
5279
5380
81+ def _is_valid_otel_span (otel_span : Any ) -> bool :
82+ span_context = getattr (otel_span , "get_span_context" , lambda : None )()
83+ return bool (getattr (span_context , "is_valid" , False ))
84+
85+
5486def _span_for_otel (otel_span : Any ) -> Span | None :
5587 if otel_span is None :
5688 return None
89+ if isinstance (otel_span , _InvalidOtelSpanKey ):
90+ return _SPANS_BY_INVALID_OTEL_KEY .get (otel_span .key )
5791 return _SPANS_BY_OTEL_SPAN .get (otel_span )
5892
5993
6094def _start_span_for_otel (otel_span : Any , * , name : str , span_type : str , input : Any = None , metadata : Any = None ) -> Any :
6195 if otel_span is None :
6296 return otel_span
97+ span_key = otel_span if _is_valid_otel_span (otel_span ) else _InvalidOtelSpanKey (otel_span )
6398 parent = None
6499 # Strands passes parent OTEL spans into child start methods. If present, nest under the mirrored BT span.
65100 if isinstance (metadata , dict ):
@@ -68,8 +103,11 @@ def _start_span_for_otel(otel_span: Any, *, name: str, span_type: str, input: An
68103 span = (parent .start_span if parent is not None else start_span )(
69104 name = name , type = span_type , input = input , metadata = metadata
70105 )
71- _SPANS_BY_OTEL_SPAN [otel_span ] = span
72- return otel_span
106+ if isinstance (span_key , _InvalidOtelSpanKey ):
107+ _SPANS_BY_INVALID_OTEL_KEY [span_key .key ] = span
108+ else :
109+ _SPANS_BY_OTEL_SPAN [span_key ] = span
110+ return span_key
73111
74112
75113def _end_span_for_otel (
@@ -80,7 +118,11 @@ def _end_span_for_otel(
80118 metrics : Any = None ,
81119 error : BaseException | None = None ,
82120) -> None :
83- span = _SPANS_BY_OTEL_SPAN .pop (otel_span , None )
121+ span = (
122+ _SPANS_BY_INVALID_OTEL_KEY .pop (otel_span .key , None )
123+ if isinstance (otel_span , _InvalidOtelSpanKey )
124+ else _SPANS_BY_OTEL_SPAN .pop (otel_span , None )
125+ )
84126 if span is None :
85127 return
86128 if error is not None :
0 commit comments