44import time
55import json
66import threading
7+ import weakref
78from typing import Collection
89from wrapt import wrap_function_wrapper
910from opentelemetry .trace import SpanKind , get_tracer , Tracer , set_span_in_context
2324)
2425from .utils import set_span_attribute , JSONEncoder
2526from agents import FunctionTool , WebSearchTool , FileSearchTool , ComputerTool
27+ from agents .tracing .scope import Scope
2628
2729
2830_instruments = ("openai-agents >= 0.0.19" ,)
2931
3032_root_span_storage = {}
33+ _storage_lock = threading .RLock ()
3134_instrumented_tools = set ()
3235
3336
37+ def _get_or_set_root_span_context (span = None ):
38+ """Get root span context using scope-based trace_id approach.
39+
40+ Args:
41+ span: Current span to potentially set as root span
42+
43+ Returns:
44+ context: The appropriate context with root span set
45+ """
46+ current_trace = Scope .get_current_trace ()
47+
48+ if current_trace and current_trace .trace_id != "no-op" :
49+ trace_id = current_trace .trace_id
50+
51+ with _storage_lock :
52+ weak_ref = _root_span_storage .get (trace_id )
53+ root_span = weak_ref () if weak_ref else None
54+
55+ if root_span :
56+ return set_span_in_context (root_span , context .get_current ())
57+ else :
58+ ctx = context .get_current ()
59+ if span :
60+ def cleanup_callback (ref ):
61+ with _storage_lock :
62+ if _root_span_storage .get (trace_id ) is ref :
63+ del _root_span_storage [trace_id ]
64+
65+ _root_span_storage [trace_id ] = weakref .ref (span , cleanup_callback )
66+ return set_span_in_context (span , ctx )
67+ return ctx
68+ else :
69+ return context .get_current ()
70+
71+
3472class OpenAIAgentsInstrumentor (BaseInstrumentor ):
3573 """An instrumentor for OpenAI Agents SDK."""
3674
@@ -118,14 +156,8 @@ async def _wrap_agent_run_streamed(
118156 return await wrapped (* args , ** kwargs )
119157
120158 agent_name = getattr (agent , "name" , "agent" )
121- thread_id = threading .get_ident ()
122159
123- root_span = _root_span_storage .get (thread_id )
124-
125- if root_span :
126- ctx = set_span_in_context (root_span , context .get_current ())
127- else :
128- ctx = context .get_current ()
160+ ctx = _get_or_set_root_span_context ()
129161
130162 with tracer .start_as_current_span (
131163 f"{ agent_name } .agent" ,
@@ -136,8 +168,7 @@ async def _wrap_agent_run_streamed(
136168 context = ctx ,
137169 ) as span :
138170 try :
139- if not root_span :
140- _root_span_storage [thread_id ] = span
171+ ctx = _get_or_set_root_span_context (span )
141172
142173 extract_agent_details (agent , span )
143174 set_model_settings_span_attributes (agent , span )
@@ -217,13 +248,8 @@ async def _wrap_agent_run(
217248 prompt_list = args [2 ] if len (args ) > 2 else None
218249 agent_name = getattr (agent , "name" , "agent" )
219250 model_name = get_model_name (agent )
220- thread_id = threading .get_ident ()
221- root_span = _root_span_storage .get (thread_id )
222251
223- if root_span :
224- ctx = set_span_in_context (root_span , context .get_current ())
225- else :
226- ctx = context .get_current ()
252+ ctx = _get_or_set_root_span_context ()
227253
228254 with tracer .start_as_current_span (
229255 f"{ agent_name } .agent" ,
@@ -234,8 +260,7 @@ async def _wrap_agent_run(
234260 context = ctx ,
235261 ) as span :
236262 try :
237- if not root_span :
238- _root_span_storage [thread_id ] = span
263+ ctx = _get_or_set_root_span_context (span )
239264
240265 extract_agent_details (agent , span )
241266 set_model_settings_span_attributes (agent , span )
@@ -391,9 +416,6 @@ def extract_run_config_details(run_config, span):
391416
392417def extract_tool_details (tracer : Tracer , tools ):
393418 """Create spans for hosted tools and wrap FunctionTool execution."""
394- thread_id = threading .get_ident ()
395- root_span = _root_span_storage .get (thread_id )
396-
397419 for tool in tools :
398420 if isinstance (tool , FunctionTool ):
399421 tool_id = id (tool )
@@ -407,10 +429,7 @@ def extract_tool_details(tracer: Tracer, tools):
407429 def create_wrapped_tool (original_tool , original_func ):
408430 async def wrapped_on_invoke_tool (tool_context , args_json ):
409431 tool_name = getattr (original_tool , "name" , "tool" )
410- if root_span :
411- ctx = set_span_in_context (root_span , context .get_current ())
412- else :
413- ctx = context .get_current ()
432+ ctx = _get_or_set_root_span_context ()
414433
415434 with tracer .start_as_current_span (
416435 f"{ tool_name } .tool" ,
@@ -452,10 +471,7 @@ async def wrapped_on_invoke_tool(tool_context, args_json):
452471
453472 elif isinstance (tool , (WebSearchTool , FileSearchTool , ComputerTool )):
454473 tool_name = type (tool ).__name__
455- if root_span :
456- ctx = set_span_in_context (root_span , context .get_current ())
457- else :
458- ctx = context .get_current ()
474+ ctx = _get_or_set_root_span_context ()
459475
460476 span = tracer .start_span (
461477 f"{ tool_name } .tool" ,
0 commit comments