55from opentelemetry import trace
66from opentelemetry .sdk .trace import ReadableSpan
77
8- from agentops .sdk .decorators import agent , operation , session , workflow
8+ from agentops .sdk .decorators import agent , operation , session , workflow , task
99from agentops .semconv import SpanKind
1010from agentops .semconv .span_attributes import SpanAttributes
11+ from agentops .semconv import SpanAttributes
1112from tests .unit .sdk .instrumentation_tester import InstrumentationTester
1213
1314
@@ -75,9 +76,9 @@ def test_session():
7576 nested_operation = None
7677
7778 for span in operation_spans :
78- if span .attributes and span .attributes .get ('agentops.operation.name' ) == 'main_operation' :
79+ if span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'main_operation' :
7980 main_operation = span
80- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'nested_operation' :
81+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'nested_operation' :
8182 nested_operation = span
8283
8384 assert main_operation is not None , "main_operation span not found"
@@ -164,9 +165,9 @@ async def test_async_session():
164165 nested_operation = None
165166
166167 for span in operation_spans :
167- if span .attributes and span .attributes .get ('agentops.operation.name' ) == 'main_async_operation' :
168+ if span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'main_async_operation' :
168169 main_operation = span
169- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'nested_async_operation' :
170+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'nested_async_operation' :
170171 nested_operation = span
171172
172173 assert main_operation is not None , "main_async_operation span not found"
@@ -255,9 +256,9 @@ def test_generator_session():
255256 nested_operation = None
256257
257258 for span in operation_spans :
258- if span .attributes and span .attributes .get ('agentops.operation.name' ) == 'main_generator_operation' :
259+ if span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'main_generator_operation' :
259260 main_operation = span
260- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'nested_generator' :
261+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'nested_generator' :
261262 nested_operation = span
262263
263264 assert main_operation is not None , "main_generator_operation span not found"
@@ -347,9 +348,9 @@ async def test_async_generator_session():
347348 nested_operation = None
348349
349350 for span in operation_spans :
350- if span .attributes and span .attributes .get ('agentops.operation.name' ) == 'main_async_generator_operation' :
351+ if span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'main_async_generator_operation' :
351352 main_operation = span
352- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'nested_async_generator' :
353+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'nested_async_generator' :
353354 nested_operation = span
354355
355356 assert main_operation is not None , "main_async_generator_operation span not found"
@@ -442,11 +443,11 @@ def test_complex_session():
442443 level3_operation = None
443444
444445 for span in operation_spans :
445- if span .attributes and span .attributes .get ('agentops.operation.name' ) == 'level1_operation' :
446+ if span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'level1_operation' :
446447 level1_operation = span
447- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'level2_operation' :
448+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'level2_operation' :
448449 level2_operation = span
449- elif span .attributes and span .attributes .get ('agentops.operation.name' ) == 'level3_operation' :
450+ elif span .attributes and span .attributes .get (SpanAttributes . OPERATION_NAME ) == 'level3_operation' :
450451 level3_operation = span
451452
452453 assert level1_operation is not None , "level1_operation span not found"
@@ -478,4 +479,93 @@ def test_complex_session():
478479 assert level2_operation .context is not None
479480 assert level3_operation .parent .span_id == level2_operation .context .span_id
480481
481-
482+ def test_workflow_and_task_nesting (self , instrumentation : InstrumentationTester ):
483+ """Test that workflow and task decorators create proper span nesting."""
484+
485+ # Define a workflow with tasks
486+ @workflow
487+ def data_processing_workflow (data ):
488+ """Main workflow that processes data through multiple tasks"""
489+ result = process_input (data )
490+ result = transform_data (result )
491+ return result
492+
493+ @task
494+ def process_input (data ):
495+ """Task to process input data"""
496+ return f"Processed: { data } "
497+
498+ @task
499+ def transform_data (data ):
500+ """Task to transform processed data"""
501+ return f"Transformed: { data } "
502+
503+ # Test session with the workflow
504+ @session
505+ def test_workflow_session ():
506+ return data_processing_workflow ("test data" )
507+
508+ # Run the test
509+ result = test_workflow_session ()
510+
511+ # Verify the result
512+ assert result == "Transformed: Processed: test data"
513+
514+ # Get all spans captured during the test
515+ spans = instrumentation .get_finished_spans ()
516+
517+ # Print detailed span information for debugging
518+ print ("\n Detailed span information for workflow and task test:" )
519+ for i , span in enumerate (spans ):
520+ parent_id = span .parent .span_id if span .parent else "None"
521+ span_id = span .context .span_id if span .context else "None"
522+ print (f"Span { i } : name={ span .name } , span_id={ span_id } , parent_id={ parent_id } " )
523+
524+ # We should have 4 spans: session, workflow, and two tasks
525+ assert len (spans ) == 4
526+
527+ # Verify span kinds
528+ session_spans = [s for s in spans if s .attributes and s .attributes .get (SpanAttributes .AGENTOPS_SPAN_KIND ) == SpanKind .SESSION ]
529+ workflow_spans = [s for s in spans if s .attributes and s .attributes .get (SpanAttributes .AGENTOPS_SPAN_KIND ) == SpanKind .WORKFLOW ]
530+ task_spans = [s for s in spans if s .attributes and s .attributes .get (
531+ SpanAttributes .AGENTOPS_SPAN_KIND ) == SpanKind .TASK ]
532+
533+ assert len (session_spans ) == 1
534+ assert len (workflow_spans ) == 1
535+ assert len (task_spans ) == 2
536+
537+ # Find the workflow and task spans
538+ workflow_span = None
539+ process_task = None
540+ transform_task = None
541+
542+ for span in spans :
543+ if span .attributes and span .attributes .get (SpanAttributes .OPERATION_NAME ) == 'data_processing_workflow' :
544+ workflow_span = span
545+ elif span .attributes and span .attributes .get (SpanAttributes .OPERATION_NAME ) == 'process_input' :
546+ process_task = span
547+ elif span .attributes and span .attributes .get (SpanAttributes .OPERATION_NAME ) == 'transform_data' :
548+ transform_task = span
549+
550+ assert workflow_span is not None , "workflow span not found"
551+ assert process_task is not None , "process_input task span not found"
552+ assert transform_task is not None , "transform_data task span not found"
553+
554+ # Verify the session span is the root
555+ session_span = session_spans [0 ]
556+ assert session_span .parent is None
557+
558+ # Verify the workflow span is a child of the session span
559+ assert workflow_span .parent is not None
560+ assert session_span .context is not None
561+ assert workflow_span .parent .span_id == session_span .context .span_id
562+
563+ # Verify process_task is a child of the workflow span
564+ assert process_task .parent is not None
565+ assert workflow_span .context is not None
566+ assert process_task .parent .span_id == workflow_span .context .span_id
567+
568+ # Verify transform_task is a child of the workflow span
569+ assert transform_task .parent is not None
570+ assert workflow_span .context is not None
571+ assert transform_task .parent .span_id == workflow_span .context .span_id
0 commit comments