From 69b90e6aceeb88fbbd68b61095c44076ef7fe300 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 30 Jun 2026 16:07:46 +0200 Subject: [PATCH 1/2] test: Move conversation ID tests to their own file --- tests/tracing/test_conversation_id.py | 191 ++++++++++++++++++++++++++ tests/tracing/test_misc.py | 184 ------------------------- 2 files changed, 191 insertions(+), 184 deletions(-) create mode 100644 tests/tracing/test_conversation_id.py diff --git a/tests/tracing/test_conversation_id.py b/tests/tracing/test_conversation_id.py new file mode 100644 index 0000000000..087b039c1d --- /dev/null +++ b/tests/tracing/test_conversation_id.py @@ -0,0 +1,191 @@ +import pytest + +import sentry_sdk +from sentry_sdk import start_span + + +def test_conversation_id_propagates_to_span_with_gen_ai_operation_name( + self, sentry_init, capture_events +): + """Span with gen_ai.operation.name data should get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-op-name-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="http.client") as span: + span.set_data("gen_ai.operation.name", "chat") + + (event,) = events + span_data = event["spans"][0]["data"] + assert span_data.get("gen_ai.conversation.id") == "conv-op-name-test" + + +def test_conversation_id_propagates_to_span_with_ai_op( + self, sentry_init, capture_events +): + """Span with ai.* op should get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-ai-op-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="ai.chat.completions"): + pass + + (event,) = events + span_data = event["spans"][0]["data"] + assert span_data.get("gen_ai.conversation.id") == "conv-ai-op-test" + + +@pytest.mark.parametrize("stream_gen_ai_spans", [True, False]) +def test_conversation_id_propagates_to_span_with_gen_ai_op( + self, sentry_init, capture_events, capture_items, stream_gen_ai_spans +): + """Span with gen_ai.* op should get conversation_id.""" + sentry_init( + traces_sample_rate=1.0, + stream_gen_ai_spans=stream_gen_ai_spans, + ) + + if stream_gen_ai_spans: + items = capture_items("span") + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-gen-ai-op-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="gen_ai.invoke_agent"): + pass + + spans = [item.payload for item in items if item.type == "span"] + span_data = spans[0]["attributes"] + else: + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-gen-ai-op-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="gen_ai.invoke_agent"): + pass + + (event,) = events + span_data = event["spans"][0]["data"] + + assert span_data.get("gen_ai.conversation.id") == "conv-gen-ai-op-test" + + +def test_conversation_id_not_propagated_to_non_ai_span( + self, sentry_init, capture_events +): + """Non-AI span should NOT get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-should-not-appear") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="http.client") as span: + span.set_data("some.other.data", "value") + + (event,) = events + span_data = event["spans"][0]["data"] + assert "gen_ai.conversation.id" not in span_data + + +def test_conversation_id_not_propagated_when_not_set(self, sentry_init, capture_events): + """AI span should not have conversation_id if not set on scope.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + # Ensure no conversation_id is set + scope = sentry_sdk.get_current_scope() + scope.remove_conversation_id() + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(op="ai.chat.completions"): + pass + + (event,) = events + span_data = event["spans"][0]["data"] + assert "gen_ai.conversation.id" not in span_data + + +def test_conversation_id_not_propagated_to_span_without_op( + self, sentry_init, capture_events +): + """Span without op and without gen_ai.operation.name should NOT get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-no-op-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(name="unnamed-span") as span: + span.set_data("regular.data", "value") + + (event,) = events + span_data = event["spans"][0]["data"] + assert "gen_ai.conversation.id" not in span_data + + +def test_conversation_id_propagates_with_gen_ai_operation_name_no_op( + self, sentry_init, capture_events +): + """Span with gen_ai.operation.name but no op should still get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-no-op-but-data-test") + + with sentry_sdk.start_transaction(name="test-tx"): + with start_span(name="unnamed-span") as span: + span.set_data("gen_ai.operation.name", "embedding") + + (event,) = events + span_data = event["spans"][0]["data"] + assert span_data.get("gen_ai.conversation.id") == "conv-no-op-but-data-test" + + +def test_conversation_id_propagates_to_transaction_with_ai_op( + self, sentry_init, capture_events +): + """Transaction with ai.* op should get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-tx-ai-op-test") + + with sentry_sdk.start_transaction(op="ai.workflow", name="AI Workflow"): + pass + + (event,) = events + trace_data = event["contexts"]["trace"]["data"] + assert trace_data.get("gen_ai.conversation.id") == "conv-tx-ai-op-test" + + +def test_conversation_id_not_propagated_to_non_ai_transaction( + self, sentry_init, capture_events +): + """Non-AI transaction should NOT get conversation_id.""" + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + scope = sentry_sdk.get_current_scope() + scope.set_conversation_id("conv-tx-should-not-appear") + + with sentry_sdk.start_transaction(op="http.server", name="HTTP Request"): + pass + + (event,) = events + trace_data = event["contexts"]["trace"]["data"] + assert "gen_ai.conversation.id" not in trace_data diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index b688c8abe2..207b5c5512 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -606,187 +606,3 @@ def test_update_current_span(sentry_init, capture_events): "thread.id": mock.ANY, "thread.name": mock.ANY, } - - -class TestConversationIdPropagation: - """Tests for conversation_id propagation to AI spans.""" - - def test_conversation_id_propagates_to_span_with_gen_ai_operation_name( - self, sentry_init, capture_events - ): - """Span with gen_ai.operation.name data should get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-op-name-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="http.client") as span: - span.set_data("gen_ai.operation.name", "chat") - - (event,) = events - span_data = event["spans"][0]["data"] - assert span_data.get("gen_ai.conversation.id") == "conv-op-name-test" - - def test_conversation_id_propagates_to_span_with_ai_op( - self, sentry_init, capture_events - ): - """Span with ai.* op should get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-ai-op-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="ai.chat.completions"): - pass - - (event,) = events - span_data = event["spans"][0]["data"] - assert span_data.get("gen_ai.conversation.id") == "conv-ai-op-test" - - @pytest.mark.parametrize("stream_gen_ai_spans", [True, False]) - def test_conversation_id_propagates_to_span_with_gen_ai_op( - self, sentry_init, capture_events, capture_items, stream_gen_ai_spans - ): - """Span with gen_ai.* op should get conversation_id.""" - sentry_init( - traces_sample_rate=1.0, - stream_gen_ai_spans=stream_gen_ai_spans, - ) - - if stream_gen_ai_spans: - items = capture_items("span") - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-gen-ai-op-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="gen_ai.invoke_agent"): - pass - - spans = [item.payload for item in items if item.type == "span"] - span_data = spans[0]["attributes"] - else: - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-gen-ai-op-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="gen_ai.invoke_agent"): - pass - - (event,) = events - span_data = event["spans"][0]["data"] - - assert span_data.get("gen_ai.conversation.id") == "conv-gen-ai-op-test" - - def test_conversation_id_not_propagated_to_non_ai_span( - self, sentry_init, capture_events - ): - """Non-AI span should NOT get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-should-not-appear") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="http.client") as span: - span.set_data("some.other.data", "value") - - (event,) = events - span_data = event["spans"][0]["data"] - assert "gen_ai.conversation.id" not in span_data - - def test_conversation_id_not_propagated_when_not_set( - self, sentry_init, capture_events - ): - """AI span should not have conversation_id if not set on scope.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - # Ensure no conversation_id is set - scope = sentry_sdk.get_current_scope() - scope.remove_conversation_id() - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(op="ai.chat.completions"): - pass - - (event,) = events - span_data = event["spans"][0]["data"] - assert "gen_ai.conversation.id" not in span_data - - def test_conversation_id_not_propagated_to_span_without_op( - self, sentry_init, capture_events - ): - """Span without op and without gen_ai.operation.name should NOT get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-no-op-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(name="unnamed-span") as span: - span.set_data("regular.data", "value") - - (event,) = events - span_data = event["spans"][0]["data"] - assert "gen_ai.conversation.id" not in span_data - - def test_conversation_id_propagates_with_gen_ai_operation_name_no_op( - self, sentry_init, capture_events - ): - """Span with gen_ai.operation.name but no op should still get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-no-op-but-data-test") - - with sentry_sdk.start_transaction(name="test-tx"): - with start_span(name="unnamed-span") as span: - span.set_data("gen_ai.operation.name", "embedding") - - (event,) = events - span_data = event["spans"][0]["data"] - assert span_data.get("gen_ai.conversation.id") == "conv-no-op-but-data-test" - - def test_conversation_id_propagates_to_transaction_with_ai_op( - self, sentry_init, capture_events - ): - """Transaction with ai.* op should get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-tx-ai-op-test") - - with sentry_sdk.start_transaction(op="ai.workflow", name="AI Workflow"): - pass - - (event,) = events - trace_data = event["contexts"]["trace"]["data"] - assert trace_data.get("gen_ai.conversation.id") == "conv-tx-ai-op-test" - - def test_conversation_id_not_propagated_to_non_ai_transaction( - self, sentry_init, capture_events - ): - """Non-AI transaction should NOT get conversation_id.""" - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - scope = sentry_sdk.get_current_scope() - scope.set_conversation_id("conv-tx-should-not-appear") - - with sentry_sdk.start_transaction(op="http.server", name="HTTP Request"): - pass - - (event,) = events - trace_data = event["contexts"]["trace"]["data"] - assert "gen_ai.conversation.id" not in trace_data From b2dcb38b422b314abe7c0744608cdee3827cef91 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 30 Jun 2026 16:12:19 +0200 Subject: [PATCH 2/2] fix signatures --- tests/tracing/test_conversation_id.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tests/tracing/test_conversation_id.py b/tests/tracing/test_conversation_id.py index 087b039c1d..df442b4e82 100644 --- a/tests/tracing/test_conversation_id.py +++ b/tests/tracing/test_conversation_id.py @@ -5,7 +5,7 @@ def test_conversation_id_propagates_to_span_with_gen_ai_operation_name( - self, sentry_init, capture_events + sentry_init, capture_events ): """Span with gen_ai.operation.name data should get conversation_id.""" sentry_init(traces_sample_rate=1.0) @@ -23,9 +23,7 @@ def test_conversation_id_propagates_to_span_with_gen_ai_operation_name( assert span_data.get("gen_ai.conversation.id") == "conv-op-name-test" -def test_conversation_id_propagates_to_span_with_ai_op( - self, sentry_init, capture_events -): +def test_conversation_id_propagates_to_span_with_ai_op(sentry_init, capture_events): """Span with ai.* op should get conversation_id.""" sentry_init(traces_sample_rate=1.0) events = capture_events() @@ -44,7 +42,7 @@ def test_conversation_id_propagates_to_span_with_ai_op( @pytest.mark.parametrize("stream_gen_ai_spans", [True, False]) def test_conversation_id_propagates_to_span_with_gen_ai_op( - self, sentry_init, capture_events, capture_items, stream_gen_ai_spans + sentry_init, capture_events, capture_items, stream_gen_ai_spans ): """Span with gen_ai.* op should get conversation_id.""" sentry_init( @@ -80,9 +78,7 @@ def test_conversation_id_propagates_to_span_with_gen_ai_op( assert span_data.get("gen_ai.conversation.id") == "conv-gen-ai-op-test" -def test_conversation_id_not_propagated_to_non_ai_span( - self, sentry_init, capture_events -): +def test_conversation_id_not_propagated_to_non_ai_span(sentry_init, capture_events): """Non-AI span should NOT get conversation_id.""" sentry_init(traces_sample_rate=1.0) events = capture_events() @@ -99,7 +95,7 @@ def test_conversation_id_not_propagated_to_non_ai_span( assert "gen_ai.conversation.id" not in span_data -def test_conversation_id_not_propagated_when_not_set(self, sentry_init, capture_events): +def test_conversation_id_not_propagated_when_not_set(sentry_init, capture_events): """AI span should not have conversation_id if not set on scope.""" sentry_init(traces_sample_rate=1.0) events = capture_events() @@ -117,9 +113,7 @@ def test_conversation_id_not_propagated_when_not_set(self, sentry_init, capture_ assert "gen_ai.conversation.id" not in span_data -def test_conversation_id_not_propagated_to_span_without_op( - self, sentry_init, capture_events -): +def test_conversation_id_not_propagated_to_span_without_op(sentry_init, capture_events): """Span without op and without gen_ai.operation.name should NOT get conversation_id.""" sentry_init(traces_sample_rate=1.0) events = capture_events() @@ -137,7 +131,7 @@ def test_conversation_id_not_propagated_to_span_without_op( def test_conversation_id_propagates_with_gen_ai_operation_name_no_op( - self, sentry_init, capture_events + sentry_init, capture_events ): """Span with gen_ai.operation.name but no op should still get conversation_id.""" sentry_init(traces_sample_rate=1.0) @@ -156,7 +150,7 @@ def test_conversation_id_propagates_with_gen_ai_operation_name_no_op( def test_conversation_id_propagates_to_transaction_with_ai_op( - self, sentry_init, capture_events + sentry_init, capture_events ): """Transaction with ai.* op should get conversation_id.""" sentry_init(traces_sample_rate=1.0) @@ -174,7 +168,7 @@ def test_conversation_id_propagates_to_transaction_with_ai_op( def test_conversation_id_not_propagated_to_non_ai_transaction( - self, sentry_init, capture_events + sentry_init, capture_events ): """Non-AI transaction should NOT get conversation_id.""" sentry_init(traces_sample_rate=1.0)