2828 TelemetryHandler ,
2929 get_telemetry_handler ,
3030)
31- from opentelemetry .util .genai .types import AgentInvocation , Workflow
31+ from opentelemetry .util .genai .types import AgentInvocation , Workflow , Step
3232from opentelemetry .util .genai .utils import gen_ai_json_dumps
3333
3434try :
7373 Status ,
7474 StatusCode ,
7575 Tracer ,
76+ get_current_span ,
7677 set_span_in_context ,
7778)
7879from opentelemetry .util .types import AttributeValue
@@ -521,6 +522,7 @@ def __init__(
521522 # util/genai integration (step 1 – workflow only)
522523 self ._handler = get_telemetry_handler ()
523524 self ._workflow : Workflow | None = None
525+ self ._steps : dict [str , Step ] = {}
524526
525527 # Metrics configuration
526528 self ._metrics_enabled = metrics_enabled
@@ -1341,28 +1343,15 @@ def on_trace_start(self, trace: Trace) -> None:
13411343 except Exception : # defensive – don’t break existing spans
13421344 self ._workflow = None
13431345
1344- otel_span = self ._tracer .start_span (
1345- name = trace .name ,
1346- attributes = attributes ,
1347- kind = SpanKind .SERVER , # Root span is typically server
1348- )
1349- self ._root_spans [trace .trace_id ] = otel_span
1350-
1351- token = attach (set_span_in_context (otel_span ))
1352- self ._tokens [trace .trace_id ] = token
1353-
13541346 def on_trace_end (self , trace : Trace ) -> None :
13551347 """End root span when trace ends."""
13561348 if root_span := self ._root_spans .pop (trace .trace_id , None ):
13571349 if root_span .is_recording ():
13581350 root_span .set_status (Status (StatusCode .OK ))
13591351 root_span .end ()
1360- try :
1361- if self ._workflow is not None :
1362- self ._handler .stop_workflow (self ._workflow )
1363- finally :
1364- self ._workflow = None
1365- self ._cleanup_spans_for_trace (trace .trace_id )
1352+ if self ._workflow is not None :
1353+ self ._handler .stop_workflow (self ._workflow )
1354+
13661355
13671356 def on_span_start (self , span : Span [Any ]) -> None :
13681357 """Start child span for agent span."""
@@ -1432,6 +1421,35 @@ def on_span_start(self, span: Span[Any]) -> None:
14321421 attributes [GEN_AI_AGENT_DESCRIPTION ] = agent_desc_override
14331422 attributes .update (self ._get_server_attributes ())
14341423
1424+ # For agent spans, create a GenAI Step under the workflow and
1425+ # then make the OpenAI Agents span a child of that Step span.
1426+ if _is_instance_of (span .span_data , AgentSpanData ) and self ._workflow is not None :
1427+ try :
1428+ step_attrs : dict [str , Any ] = dict (attributes )
1429+ step = Step (
1430+ name = agent_name or span_name ,
1431+ step_type = "agent_start" , # or "chain" if you prefer
1432+ attributes = step_attrs ,
1433+ )
1434+
1435+ # Parent the Step to the Workflow entity.
1436+ step .parent_run_id = self ._workflow .run_id
1437+
1438+ content = self ._agent_content .get (span .span_id )
1439+ if content and content .get ("input_messages" ):
1440+ step .input_data = safe_json_dumps (content ["input_messages" ])
1441+
1442+ self ._handler .start_step (step )
1443+ self ._steps [str (span .span_id )] = step
1444+
1445+ # After starting the Step, use its span as the parent context
1446+ # for the OpenAI Agents span.
1447+ parent_span = get_current_span ()
1448+ context = set_span_in_context (parent_span )
1449+ except Exception :
1450+ # Defensive: do not break agent spans if util/genai fails.
1451+ pass
1452+
14351453 otel_span = self ._tracer .start_span (
14361454 name = span_name ,
14371455 context = context ,
@@ -1441,7 +1459,7 @@ def on_span_start(self, span: Span[Any]) -> None:
14411459 self ._otel_spans [span .span_id ] = otel_span
14421460 self ._tokens [span .span_id ] = attach (set_span_in_context (otel_span ))
14431461
1444- # util/genai step 2 : create AgentInvocation entity for agent spans.
1462+ # util/genai step 3 : create AgentInvocation entity for agent spans.
14451463 if _is_instance_of (span .span_data , AgentSpanData ):
14461464 try :
14471465 # Prepare attributes for the AgentInvocation entity. We start
@@ -1515,7 +1533,17 @@ def on_span_end(self, span: Span[Any]) -> None:
15151533 self ._agent_content .pop (span .span_id , None )
15161534 self ._span_parents .pop (span .span_id , None )
15171535 return
1518-
1536+ key = str (span .span_id )
1537+ step = self ._steps .pop (key , None )
1538+ if step is not None :
1539+ try :
1540+ # Optional: attach output from agent/span
1541+ content = self ._agent_content .get (span .span_id )
1542+ if content and content .get ("output_messages" ):
1543+ step .output_data = safe_json_dumps (content ["output_messages" ])
1544+ self ._handler .stop_step (step )
1545+ except Exception :
1546+ pass
15191547 try :
15201548 # Extract and set attributes
15211549 attributes : dict [str , AttributeValue ] = {}
0 commit comments