66
77import json
88import os
9+
910import pytest
10- from unittest .mock import Mock , patch
1111
12- from opentelemetry import trace
13- from opentelemetry .sdk .trace import TracerProvider , ReadableSpan
12+ from opentelemetry .sdk .trace import TracerProvider
1413from opentelemetry .sdk .trace .export import SimpleSpanProcessor
1514from opentelemetry .sdk .trace .export .in_memory_span_exporter import (
1615 InMemorySpanExporter ,
1716)
18- from opentelemetry .trace import SpanKind
19-
2017from opentelemetry .util .genai .processor .traceloop_span_processor import (
2118 TraceloopSpanProcessor ,
2219)
@@ -122,9 +119,9 @@ def test_simple_workflow_with_tasks(self, setup_tracer):
122119 and s .attributes .get ("gen_ai.workflow.name" )
123120 == "pirate_joke_generator"
124121 ]
125- assert len ( workflow_spans ) >= 1 , (
126- "Should have at least one workflow span"
127- )
122+ assert (
123+ len ( workflow_spans ) >= 1
124+ ), "Should have at least one workflow span"
128125
129126 # Find task spans
130127 task_spans = [
@@ -150,9 +147,9 @@ def test_simple_workflow_with_tasks(self, setup_tracer):
150147 traceloop_keys = [
151148 k for k in traceloop_keys if k != "_traceloop_processed"
152149 ]
153- assert len ( traceloop_keys ) == 0 , (
154- f"Span { span . name } should not have traceloop.* attributes, found: { traceloop_keys } "
155- )
150+ assert (
151+ len ( traceloop_keys ) == 0
152+ ), f"Span { span . name } should not have traceloop.* attributes, found: { traceloop_keys } "
156153
157154 def test_nested_agent_with_tool (self , setup_tracer ):
158155 """Test @agent pattern with nested @tool calls."""
@@ -247,9 +244,9 @@ def test_nested_agent_with_tool(self, setup_tracer):
247244 input_data = json .loads (
248245 agent_with_input [0 ].attributes ["gen_ai.input.messages" ]
249246 )
250- assert isinstance (input_data , list ), (
251- "Input should be normalized to message array"
252- )
247+ assert isinstance (
248+ input_data , list
249+ ), "Input should be normalized to message array"
253250
254251
255252class TestParentChildRelationships :
@@ -288,9 +285,9 @@ def test_parent_child_hierarchy_preserved(self, setup_tracer):
288285 if child .parent and child .parent .span_id in span_map :
289286 valid_parent_refs += 1
290287
291- assert valid_parent_refs >= 1 , (
292- "At least one child should have a valid parent reference"
293- )
288+ assert (
289+ valid_parent_refs >= 1
290+ ), "At least one child should have a valid parent reference"
294291
295292
296293class TestContentNormalization :
@@ -345,22 +342,22 @@ def test_normalize_entity_input_output(self, setup_tracer):
345342 ]
346343
347344 # Should have at least the mutated original span with gen_ai.input.messages
348- assert len ( spans_with_input ) >= 1 , (
349- f"Should have spans with normalized input, got { len (spans ) } spans total"
350- )
345+ assert (
346+ len (spans_with_input ) >= 1
347+ ), f"Should have spans with normalized input, got { len ( spans ) } spans total"
351348
352349 # Verify normalization
353350 for span in spans_with_input :
354351 input_str = span .attributes .get ("gen_ai.input.messages" )
355352 if input_str :
356353 input_data = json .loads (input_str )
357- assert isinstance (input_data , list ), (
358- "Input should be list of messages"
359- )
354+ assert isinstance (
355+ input_data , list
356+ ), "Input should be list of messages"
360357 if input_data :
361- assert "role" in input_data [ 0 ], (
362- "Messages should have role field"
363- )
358+ assert (
359+ "role" in input_data [ 0 ]
360+ ), "Messages should have role field"
364361
365362 # Check output normalization
366363 spans_with_output = [
@@ -374,9 +371,9 @@ def test_normalize_entity_input_output(self, setup_tracer):
374371 "gen_ai.output.messages"
375372 )
376373 output_data = json .loads (output_str )
377- assert isinstance (output_data , list ), (
378- "Output should be list of messages"
379- )
374+ assert isinstance (
375+ output_data , list
376+ ), "Output should be list of messages"
380377
381378 def test_normalize_string_input (self , setup_tracer ):
382379 """Test normalization of simple string inputs."""
@@ -402,9 +399,9 @@ def test_normalize_string_input(self, setup_tracer):
402399 and any (k .startswith ("gen_ai." ) for k in s .attributes .keys ())
403400 ]
404401
405- assert len ( spans_with_genai ) >= 1 , (
406- "Should have spans with gen_ai.* attributes after processing"
407- )
402+ assert (
403+ len ( spans_with_genai ) >= 1
404+ ), "Should have spans with gen_ai.* attributes after processing"
408405
409406 def test_normalize_list_of_strings (self , setup_tracer ):
410407 """Test normalization of list inputs."""
@@ -431,9 +428,9 @@ def test_normalize_list_of_strings(self, setup_tracer):
431428 for s in spans
432429 if s .attributes and "gen_ai.span.kind" in s .attributes
433430 ]
434- assert len ( spans_with_genai ) >= 1 , (
435- "Should have processed spans with gen_ai attributes"
436- )
431+ assert (
432+ len ( spans_with_genai ) >= 1
433+ ), "Should have processed spans with gen_ai attributes"
437434
438435
439436class TestModelInference :
@@ -480,9 +477,9 @@ def test_preserve_explicit_model(self, setup_tracer):
480477 and s .attributes .get ("gen_ai.request.model" ) == "gpt-4"
481478 ]
482479
483- assert len ( spans_with_model ) >= 1 , (
484- "Should preserve explicit model attribute"
485- )
480+ assert (
481+ len ( spans_with_model ) >= 1
482+ ), "Should preserve explicit model attribute"
486483
487484
488485class TestSpanFiltering :
@@ -500,18 +497,18 @@ def test_filters_non_llm_spans(self, setup_tracer):
500497 spans = exporter .get_finished_spans ()
501498
502499 # Should only have the original span, no synthetic spans
503- assert len ( spans ) == 1 , (
504- f"Expected 1 span (non-LLM filtered), got { len (spans )} "
505- )
500+ assert (
501+ len (spans ) == 1
502+ ), f"Expected 1 span (non-LLM filtered), got { len ( spans ) } "
506503
507504 # Original span should not have gen_ai.* attributes
508505 span = spans [0 ]
509506 gen_ai_attrs = [
510507 k for k in span .attributes .keys () if k .startswith ("gen_ai." )
511508 ]
512- assert len ( gen_ai_attrs ) == 0 , (
513- "Non-LLM span should not have gen_ai.* attributes"
514- )
509+ assert (
510+ len ( gen_ai_attrs ) == 0
511+ ), "Non-LLM span should not have gen_ai.* attributes"
515512
516513 def test_includes_traceloop_spans (self , setup_tracer ):
517514 """Test that Traceloop task/workflow spans are included."""
@@ -534,9 +531,9 @@ def test_includes_traceloop_spans(self, setup_tracer):
534531 for s in spans
535532 if s .attributes and s .attributes .get ("gen_ai.span.kind" ) == "task"
536533 ]
537- assert len ( spans_with_kind ) >= 1 , (
538- f"Traceloop task should be transformed, got { len (spans ) } spans"
539- )
534+ assert (
535+ len (spans_with_kind ) >= 1
536+ ), f"Traceloop task should be transformed, got { len ( spans ) } spans"
540537
541538
542539class TestOperationInference :
@@ -560,9 +557,9 @@ def test_infer_chat_operation(self, setup_tracer):
560557 if s .attributes and "gen_ai.system" in s .attributes
561558 ]
562559
563- assert len ( spans_with_genai ) >= 1 , (
564- f"Should have processed spans with gen_ai attributes, got { len (spans ) } total spans"
565- )
560+ assert (
561+ len (spans_with_genai ) >= 1
562+ ), f"Should have processed spans with gen_ai attributes, got { len ( spans ) } total spans"
566563
567564 def test_infer_embedding_operation (self , setup_tracer ):
568565 """Test that 'embedding' operation is inferred from span name."""
@@ -587,9 +584,9 @@ def test_infer_embedding_operation(self, setup_tracer):
587584 in s .attributes .get ("gen_ai.request.model" , "" )
588585 ]
589586
590- assert len ( spans_with_embedding ) >= 1 , (
591- f"Should process embedding spans, got { len (spans ) } total spans"
592- )
587+ assert (
588+ len (spans_with_embedding ) >= 1
589+ ), f"Should process embedding spans, got { len ( spans ) } total spans"
593590
594591
595592class TestComplexWorkflow :
@@ -673,9 +670,9 @@ def test_full_pirate_joke_workflow(self, setup_tracer):
673670 spans = exporter .get_finished_spans ()
674671
675672 # Should have many spans (original mutated + synthetic)
676- assert len ( spans ) >= 8 , (
677- f"Expected at least 8 spans in full workflow, got { len (spans )} "
678- )
673+ assert (
674+ len (spans ) >= 8
675+ ), f"Expected at least 8 spans in full workflow, got { len ( spans ) } "
679676
680677 # Verify workflow span exists - look for spans with the workflow name
681678 workflow_spans = [
@@ -685,9 +682,9 @@ def test_full_pirate_joke_workflow(self, setup_tracer):
685682 and s .attributes .get ("gen_ai.workflow.name" )
686683 == "pirate_joke_generator"
687684 ]
688- assert len ( workflow_spans ) >= 1 , (
689- f"Should have workflow span, got { len (spans ) } total spans, workflow_spans= { len ( workflow_spans ) } "
690- )
685+ assert (
686+ len (workflow_spans ) >= 1
687+ ), f"Should have workflow span, got { len ( spans ) } total spans, workflow_spans= { len ( workflow_spans ) } "
691688
692689 # Verify all task names are present
693690 task_names = {"joke_creation" , "signature_generation" }
@@ -698,9 +695,9 @@ def test_full_pirate_joke_workflow(self, setup_tracer):
698695 if agent_name in task_names :
699696 found_tasks .add (agent_name )
700697
701- assert len ( found_tasks ) >= 1 , (
702- f"Should find task spans, found: { found_tasks } "
703- )
698+ assert (
699+ len ( found_tasks ) >= 1
700+ ), f"Should find task spans, found: { found_tasks } "
704701
705702 # Verify no traceloop.* attributes remain (mutation)
706703 for span in spans :
@@ -711,9 +708,9 @@ def test_full_pirate_joke_workflow(self, setup_tracer):
711708 if k .startswith ("traceloop." )
712709 and k != "_traceloop_processed"
713710 ]
714- assert len ( traceloop_keys ) == 0 , (
715- f"Span { span . name } should not have traceloop.* attributes"
716- )
711+ assert (
712+ len ( traceloop_keys ) == 0
713+ ), f"Span { span . name } should not have traceloop.* attributes"
717714
718715
719716class TestEdgeCases :
0 commit comments