4
4
import time
5
5
import json
6
6
import threading
7
+ import weakref
7
8
from typing import Collection
8
9
from wrapt import wrap_function_wrapper
9
10
from opentelemetry .trace import SpanKind , get_tracer , Tracer , set_span_in_context
23
24
)
24
25
from .utils import set_span_attribute , JSONEncoder
25
26
from agents import FunctionTool , WebSearchTool , FileSearchTool , ComputerTool
27
+ from agents .tracing .scope import Scope
26
28
27
29
28
30
_instruments = ("openai-agents >= 0.0.19" ,)
29
31
30
32
_root_span_storage = {}
33
+ _storage_lock = threading .RLock ()
31
34
_instrumented_tools = set ()
32
35
33
36
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
+
34
72
class OpenAIAgentsInstrumentor (BaseInstrumentor ):
35
73
"""An instrumentor for OpenAI Agents SDK."""
36
74
@@ -118,14 +156,8 @@ async def _wrap_agent_run_streamed(
118
156
return await wrapped (* args , ** kwargs )
119
157
120
158
agent_name = getattr (agent , "name" , "agent" )
121
- thread_id = threading .get_ident ()
122
159
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 ()
129
161
130
162
with tracer .start_as_current_span (
131
163
f"{ agent_name } .agent" ,
@@ -136,8 +168,7 @@ async def _wrap_agent_run_streamed(
136
168
context = ctx ,
137
169
) as span :
138
170
try :
139
- if not root_span :
140
- _root_span_storage [thread_id ] = span
171
+ ctx = _get_or_set_root_span_context (span )
141
172
142
173
extract_agent_details (agent , span )
143
174
set_model_settings_span_attributes (agent , span )
@@ -217,13 +248,8 @@ async def _wrap_agent_run(
217
248
prompt_list = args [2 ] if len (args ) > 2 else None
218
249
agent_name = getattr (agent , "name" , "agent" )
219
250
model_name = get_model_name (agent )
220
- thread_id = threading .get_ident ()
221
- root_span = _root_span_storage .get (thread_id )
222
251
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 ()
227
253
228
254
with tracer .start_as_current_span (
229
255
f"{ agent_name } .agent" ,
@@ -234,8 +260,7 @@ async def _wrap_agent_run(
234
260
context = ctx ,
235
261
) as span :
236
262
try :
237
- if not root_span :
238
- _root_span_storage [thread_id ] = span
263
+ ctx = _get_or_set_root_span_context (span )
239
264
240
265
extract_agent_details (agent , span )
241
266
set_model_settings_span_attributes (agent , span )
@@ -391,9 +416,6 @@ def extract_run_config_details(run_config, span):
391
416
392
417
def extract_tool_details (tracer : Tracer , tools ):
393
418
"""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
-
397
419
for tool in tools :
398
420
if isinstance (tool , FunctionTool ):
399
421
tool_id = id (tool )
@@ -407,10 +429,7 @@ def extract_tool_details(tracer: Tracer, tools):
407
429
def create_wrapped_tool (original_tool , original_func ):
408
430
async def wrapped_on_invoke_tool (tool_context , args_json ):
409
431
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 ()
414
433
415
434
with tracer .start_as_current_span (
416
435
f"{ tool_name } .tool" ,
@@ -452,10 +471,7 @@ async def wrapped_on_invoke_tool(tool_context, args_json):
452
471
453
472
elif isinstance (tool , (WebSearchTool , FileSearchTool , ComputerTool )):
454
473
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 ()
459
475
460
476
span = tracer .start_span (
461
477
f"{ tool_name } .tool" ,
0 commit comments