@@ -605,3 +605,168 @@ def test_update_current_span(sentry_init, capture_events):
605605 "thread.id" : mock .ANY ,
606606 "thread.name" : mock .ANY ,
607607 }
608+
609+
610+ class TestConversationIdPropagation :
611+ """Tests for conversation_id propagation to AI spans."""
612+
613+ def test_conversation_id_propagates_to_span_with_gen_ai_operation_name (
614+ self , sentry_init , capture_events
615+ ):
616+ """Span with gen_ai.operation.name data should get conversation_id."""
617+ sentry_init (traces_sample_rate = 1.0 )
618+ events = capture_events ()
619+
620+ scope = sentry_sdk .get_current_scope ()
621+ scope .set_conversation_id ("conv-op-name-test" )
622+
623+ with sentry_sdk .start_transaction (name = "test-tx" ):
624+ with start_span (op = "http.client" ) as span :
625+ span .set_data ("gen_ai.operation.name" , "chat" )
626+
627+ (event ,) = events
628+ span_data = event ["spans" ][0 ]["data" ]
629+ assert span_data .get ("gen_ai.conversation.id" ) == "conv-op-name-test"
630+
631+ def test_conversation_id_propagates_to_span_with_ai_op (
632+ self , sentry_init , capture_events
633+ ):
634+ """Span with ai.* op should get conversation_id."""
635+ sentry_init (traces_sample_rate = 1.0 )
636+ events = capture_events ()
637+
638+ scope = sentry_sdk .get_current_scope ()
639+ scope .set_conversation_id ("conv-ai-op-test" )
640+
641+ with sentry_sdk .start_transaction (name = "test-tx" ):
642+ with start_span (op = "ai.chat.completions" ):
643+ pass
644+
645+ (event ,) = events
646+ span_data = event ["spans" ][0 ]["data" ]
647+ assert span_data .get ("gen_ai.conversation.id" ) == "conv-ai-op-test"
648+
649+ def test_conversation_id_propagates_to_span_with_gen_ai_op (
650+ self , sentry_init , capture_events
651+ ):
652+ """Span with gen_ai.* op should get conversation_id."""
653+ sentry_init (traces_sample_rate = 1.0 )
654+ events = capture_events ()
655+
656+ scope = sentry_sdk .get_current_scope ()
657+ scope .set_conversation_id ("conv-gen-ai-op-test" )
658+
659+ with sentry_sdk .start_transaction (name = "test-tx" ):
660+ with start_span (op = "gen_ai.invoke_agent" ):
661+ pass
662+
663+ (event ,) = events
664+ span_data = event ["spans" ][0 ]["data" ]
665+ assert span_data .get ("gen_ai.conversation.id" ) == "conv-gen-ai-op-test"
666+
667+ def test_conversation_id_not_propagated_to_non_ai_span (
668+ self , sentry_init , capture_events
669+ ):
670+ """Non-AI span should NOT get conversation_id."""
671+ sentry_init (traces_sample_rate = 1.0 )
672+ events = capture_events ()
673+
674+ scope = sentry_sdk .get_current_scope ()
675+ scope .set_conversation_id ("conv-should-not-appear" )
676+
677+ with sentry_sdk .start_transaction (name = "test-tx" ):
678+ with start_span (op = "http.client" ) as span :
679+ span .set_data ("some.other.data" , "value" )
680+
681+ (event ,) = events
682+ span_data = event ["spans" ][0 ]["data" ]
683+ assert "gen_ai.conversation.id" not in span_data
684+
685+ def test_conversation_id_not_propagated_when_not_set (
686+ self , sentry_init , capture_events
687+ ):
688+ """AI span should not have conversation_id if not set on scope."""
689+ sentry_init (traces_sample_rate = 1.0 )
690+ events = capture_events ()
691+
692+ # Ensure no conversation_id is set
693+ scope = sentry_sdk .get_current_scope ()
694+ scope .remove_conversation_id ()
695+
696+ with sentry_sdk .start_transaction (name = "test-tx" ):
697+ with start_span (op = "ai.chat.completions" ):
698+ pass
699+
700+ (event ,) = events
701+ span_data = event ["spans" ][0 ]["data" ]
702+ assert "gen_ai.conversation.id" not in span_data
703+
704+ def test_conversation_id_not_propagated_to_span_without_op (
705+ self , sentry_init , capture_events
706+ ):
707+ """Span without op and without gen_ai.operation.name should NOT get conversation_id."""
708+ sentry_init (traces_sample_rate = 1.0 )
709+ events = capture_events ()
710+
711+ scope = sentry_sdk .get_current_scope ()
712+ scope .set_conversation_id ("conv-no-op-test" )
713+
714+ with sentry_sdk .start_transaction (name = "test-tx" ):
715+ with start_span (name = "unnamed-span" ) as span :
716+ span .set_data ("regular.data" , "value" )
717+
718+ (event ,) = events
719+ span_data = event ["spans" ][0 ]["data" ]
720+ assert "gen_ai.conversation.id" not in span_data
721+
722+ def test_conversation_id_propagates_with_gen_ai_operation_name_no_op (
723+ self , sentry_init , capture_events
724+ ):
725+ """Span with gen_ai.operation.name but no op should still get conversation_id."""
726+ sentry_init (traces_sample_rate = 1.0 )
727+ events = capture_events ()
728+
729+ scope = sentry_sdk .get_current_scope ()
730+ scope .set_conversation_id ("conv-no-op-but-data-test" )
731+
732+ with sentry_sdk .start_transaction (name = "test-tx" ):
733+ with start_span (name = "unnamed-span" ) as span :
734+ span .set_data ("gen_ai.operation.name" , "embedding" )
735+
736+ (event ,) = events
737+ span_data = event ["spans" ][0 ]["data" ]
738+ assert span_data .get ("gen_ai.conversation.id" ) == "conv-no-op-but-data-test"
739+
740+ def test_conversation_id_propagates_to_transaction_with_ai_op (
741+ self , sentry_init , capture_events
742+ ):
743+ """Transaction with ai.* op should get conversation_id."""
744+ sentry_init (traces_sample_rate = 1.0 )
745+ events = capture_events ()
746+
747+ scope = sentry_sdk .get_current_scope ()
748+ scope .set_conversation_id ("conv-tx-ai-op-test" )
749+
750+ with sentry_sdk .start_transaction (op = "ai.workflow" , name = "AI Workflow" ):
751+ pass
752+
753+ (event ,) = events
754+ trace_data = event ["contexts" ]["trace" ]["data" ]
755+ assert trace_data .get ("gen_ai.conversation.id" ) == "conv-tx-ai-op-test"
756+
757+ def test_conversation_id_not_propagated_to_non_ai_transaction (
758+ self , sentry_init , capture_events
759+ ):
760+ """Non-AI transaction should NOT get conversation_id."""
761+ sentry_init (traces_sample_rate = 1.0 )
762+ events = capture_events ()
763+
764+ scope = sentry_sdk .get_current_scope ()
765+ scope .set_conversation_id ("conv-tx-should-not-appear" )
766+
767+ with sentry_sdk .start_transaction (op = "http.server" , name = "HTTP Request" ):
768+ pass
769+
770+ (event ,) = events
771+ trace_data = event ["contexts" ]["trace" ]["data" ]
772+ assert "gen_ai.conversation.id" not in trace_data
0 commit comments