From d272c88c152d480229598eb686b7e023932c75e1 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Mon, 13 Apr 2026 12:45:07 -0700 Subject: [PATCH 1/5] ref(seer-grouping): v2.1 flag --- src/sentry/features/temporary.py | 2 + src/sentry/grouping/ingest/seer.py | 9 +- src/sentry/seer/similarity/config.py | 58 +++---- src/sentry/seer/similarity/types.py | 1 + src/sentry/seer/similarity/utils.py | 2 +- .../seer_similarity/test_training_mode.py | 28 +++- tests/sentry/seer/similarity/test_config.py | 148 ++++++++++-------- tests/sentry/seer/similarity/test_utils.py | 15 +- 8 files changed, 153 insertions(+), 110 deletions(-) diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index 881407d0fc7de6..da21333e50c5cc 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -565,6 +565,8 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("projects:similarity-view", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True) # Enable new similarity grouping model upgrade (version-agnostic rollout) manager.add("projects:similarity-grouping-model-upgrade", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) + # Enable next similarity grouping model rollout (for migrating EA projects from v2 to v2.1) + manager.add("projects:similarity-grouping-model-next", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) # Starfish: extract metrics from the spans manager.add("projects:span-metrics-extraction", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True) manager.add("projects:span-metrics-extraction-addons", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False) diff --git a/src/sentry/grouping/ingest/seer.py b/src/sentry/grouping/ingest/seer.py index ec9a4147c06acd..97a5b391e3fd2c 100644 --- a/src/sentry/grouping/ingest/seer.py +++ b/src/sentry/grouping/ingest/seer.py @@ -19,7 +19,6 @@ from sentry.seer.signed_seer_api import SeerViewerContext from sentry.seer.similarity.config import ( get_grouping_model_version, - get_new_model_version, should_send_to_seer_for_training, ) from sentry.seer.similarity.similar_issues import get_similarity_data_from_seer @@ -688,10 +687,10 @@ def maybe_send_seer_for_new_model_training( }, ) - # Mark the grouphash as sent to the new model so we don't send duplicate requests. + # Mark the grouphash as sent to this (non-stable) model so we don't send duplicate requests. # We update seer_latest_training_model (not seer_model) to preserve the original # grouping decision metadata. if gh_metadata: - new_version = get_new_model_version() - if new_version is not None: - gh_metadata.update(seer_latest_training_model=new_version.value) + gh_metadata.update( + seer_latest_training_model=get_grouping_model_version(event.project).value + ) diff --git a/src/sentry/seer/similarity/config.py b/src/sentry/seer/similarity/config.py index b3c92eb510ce34..3f6762539d9080 100644 --- a/src/sentry/seer/similarity/config.py +++ b/src/sentry/seer/similarity/config.py @@ -18,8 +18,12 @@ # Set to None to disable rollout entirely SEER_GROUPING_NEW_VERSION: GroupingVersion | None = GroupingVersion.V2 -# Feature flag name (version-agnostic) +# Model version to migrate EA projects from new to next +SEER_GROUPING_NEXT_VERSION: GroupingVersion | None = GroupingVersion.V2_1 + +# Feature flag names SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE = "projects:similarity-grouping-model-upgrade" +SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE = "projects:similarity-grouping-model-next" def get_grouping_model_version(project: Project) -> GroupingVersion: @@ -27,31 +31,28 @@ def get_grouping_model_version(project: Project) -> GroupingVersion: Get the model version to use for grouping decisions for this project. Returns: + - Next version if rollout is enabled for this project (for migrating EA projects from new to next) - New version if rollout is enabled for this project - Stable version otherwise """ - # Early return if no new version configured - if SEER_GROUPING_NEW_VERSION is None: - return SEER_GROUPING_STABLE_VERSION - - # Type is narrowed to GroupingVersion here - if features.has(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, project): + if SEER_GROUPING_NEXT_VERSION is not None and features.has( + SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, project + ): + return SEER_GROUPING_NEXT_VERSION + + if SEER_GROUPING_NEW_VERSION is not None and features.has( + SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, project + ): return SEER_GROUPING_NEW_VERSION + return SEER_GROUPING_STABLE_VERSION def is_new_model_rolled_out(project: Project) -> bool: """ - Check if the new model version is rolled out for this project. - - Returns False if: - - No new version is configured (rollout disabled globally) - - Feature flag is not enabled for this project + Check if any non-stable model version is rolled out for this project. """ - if SEER_GROUPING_NEW_VERSION is None: - return False - - return features.has(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, project) + return get_grouping_model_version(project) != SEER_GROUPING_STABLE_VERSION def get_new_model_version() -> GroupingVersion | None: @@ -67,29 +68,18 @@ def should_send_to_seer_for_training( grouphash_seer_latest_training_model: str | None, ) -> bool: """ - Check if we should send a training_mode=true request to Seer for the new model version. + Check if we should send a training_mode=true request to Seer for the + project's current model version. This is true when: - 1. A new version is being rolled out - 2. The project has the rollout feature enabled - 3. The grouphash hasn't already been sent to the new version - - Args: - project: The project - grouphash_seer_latest_training_model: The seer_latest_training_model value - from grouphash metadata - - Returns: - True if we should send a training_mode=true request + 1. The project is on a non-stable model version (via feature flags) + 2. The grouphash hasn't already been sent to that version """ - new_version = get_new_model_version() - if new_version is None: - return False - - if not is_new_model_rolled_out(project): + model_version = get_grouping_model_version(project) + if model_version == SEER_GROUPING_STABLE_VERSION: return False - if grouphash_seer_latest_training_model == new_version.value: + if grouphash_seer_latest_training_model == model_version.value: return False return True diff --git a/src/sentry/seer/similarity/types.py b/src/sentry/seer/similarity/types.py index f6fd15ddfc61c0..ea8e11f61d4592 100644 --- a/src/sentry/seer/similarity/types.py +++ b/src/sentry/seer/similarity/types.py @@ -14,6 +14,7 @@ class GroupingVersion(StrEnum): V1 = "v1" V2 = "v2" + V2_1 = "v2.1" class IncompleteSeerDataError(Exception): diff --git a/src/sentry/seer/similarity/utils.py b/src/sentry/seer/similarity/utils.py index ff530ed98c12dd..10aff5f469bdec 100644 --- a/src/sentry/seer/similarity/utils.py +++ b/src/sentry/seer/similarity/utils.py @@ -381,7 +381,7 @@ def stacktrace_exceeds_limits( # matching with existing data, we bypass the filter for them (their stacktraces will be truncated). # For V2 we apply length checks to all platforms since we're re-embedding everything anyway. if ( - model_version != GroupingVersion.V2 + model_version == GroupingVersion.V1 and platform in EVENT_PLATFORMS_BYPASSING_STACKTRACE_LENGTH_CHECK ): metrics.incr( diff --git a/tests/sentry/grouping/seer_similarity/test_training_mode.py b/tests/sentry/grouping/seer_similarity/test_training_mode.py index 03640f5d2a27cd..498a98e9fc55e0 100644 --- a/tests/sentry/grouping/seer_similarity/test_training_mode.py +++ b/tests/sentry/grouping/seer_similarity/test_training_mode.py @@ -3,7 +3,10 @@ from sentry.grouping.ingest.seer import maybe_send_seer_for_new_model_training from sentry.models.grouphash import GroupHash from sentry.models.grouphashmetadata import GroupHashMetadata -from sentry.seer.similarity.config import SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE +from sentry.seer.similarity.config import ( + SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, + SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, +) from sentry.testutils.cases import TestCase from sentry.testutils.helpers.eventprocessing import save_new_event @@ -212,3 +215,26 @@ def test_captures_exception_without_failing(self) -> None: "grouphash": self.grouphash.hash, }, ) + + def test_retrains_for_next_model_when_already_trained_for_new(self) -> None: + """v2 -> v2.1 transition: grouphash trained for v2, project now on v2.1""" + with ( + patch("sentry.grouping.ingest.seer.should_call_seer_for_grouping", return_value=True), + patch( + "sentry.grouping.ingest.seer.get_similarity_data_from_seer", + return_value=([], "v2.1"), + ) as mock_get_similarity_data, + self.feature(SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE), + ): + metadata, _ = GroupHashMetadata.objects.get_or_create(grouphash=self.grouphash) + metadata.seer_latest_training_model = "v2" + metadata.save() + + maybe_send_seer_for_new_model_training(self.event, self.grouphash, self.variants) + + mock_get_similarity_data.assert_called_once() + call_args = mock_get_similarity_data.call_args + assert call_args[0][0]["model"].value == "v2.1" + + metadata.refresh_from_db() + assert metadata.seer_latest_training_model == "v2.1" diff --git a/tests/sentry/seer/similarity/test_config.py b/tests/sentry/seer/similarity/test_config.py index 1fbdef12078865..76c89cd87f79d2 100644 --- a/tests/sentry/seer/similarity/test_config.py +++ b/tests/sentry/seer/similarity/test_config.py @@ -3,6 +3,8 @@ from sentry.seer.similarity.config import ( SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, SEER_GROUPING_NEW_VERSION, + SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, + SEER_GROUPING_NEXT_VERSION, SEER_GROUPING_STABLE_VERSION, get_grouping_model_version, get_new_model_version, @@ -14,92 +16,114 @@ class GetGroupingModelVersionTest(TestCase): - def test_returns_stable_when_rollout_disabled(self) -> None: - """When new model rollout is disabled, return stable version""" - with patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None): - result = get_grouping_model_version(self.project) - assert result == SEER_GROUPING_STABLE_VERSION - - def test_returns_stable_when_feature_not_enabled(self) -> None: - """When feature flag is not enabled for project, return stable version""" - result = get_grouping_model_version(self.project) - assert result == SEER_GROUPING_STABLE_VERSION + def test_returns_stable_when_no_flags(self) -> None: + assert get_grouping_model_version(self.project) == SEER_GROUPING_STABLE_VERSION - def test_returns_new_when_feature_enabled(self) -> None: - """When feature flag is enabled for project, return new version""" - with self.feature(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE): - result = get_grouping_model_version(self.project) - assert result == SEER_GROUPING_NEW_VERSION + def test_returns_stable_when_rollout_disabled(self) -> None: + with ( + patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None), + patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), + ): + assert get_grouping_model_version(self.project) == SEER_GROUPING_STABLE_VERSION + + def test_returns_flagged_version(self) -> None: + cases = [ + (SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, SEER_GROUPING_NEW_VERSION), + (SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, SEER_GROUPING_NEXT_VERSION), + ] + for feature_flag, expected_version in cases: + with self.subTest(feature_flag=feature_flag), self.feature(feature_flag): + assert get_grouping_model_version(self.project) == expected_version + + def test_next_flag_takes_priority_over_new_flag(self) -> None: + with self.feature( + [SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE] + ): + assert get_grouping_model_version(self.project) == SEER_GROUPING_NEXT_VERSION + + def test_falls_back_to_new_when_next_version_is_none(self) -> None: + with ( + patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), + self.feature( + [SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE] + ), + ): + assert get_grouping_model_version(self.project) == SEER_GROUPING_NEW_VERSION class IsNewModelRolledOutTest(TestCase): - def test_returns_false_when_no_new_version(self) -> None: - """When no new version is configured, rollout is not active""" - with patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None): - result = is_new_model_rolled_out(self.project) - assert result is False + def test_returns_false_when_no_flags(self) -> None: + assert is_new_model_rolled_out(self.project) is False - def test_returns_false_when_feature_not_enabled(self) -> None: - """When feature flag is not enabled, rollout is not active for project""" - result = is_new_model_rolled_out(self.project) - assert result is False + def test_returns_false_when_all_versions_none(self) -> None: + with ( + patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None), + patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), + ): + assert is_new_model_rolled_out(self.project) is False - def test_returns_true_when_feature_enabled(self) -> None: - """When feature flag is enabled, rollout is active for project""" - with self.feature(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE): - result = is_new_model_rolled_out(self.project) - assert result is True + def test_returns_true_when_flag_enabled(self) -> None: + for feature_flag in [ + SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, + SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, + ]: + with self.subTest(feature_flag=feature_flag), self.feature(feature_flag): + assert is_new_model_rolled_out(self.project) is True class GetNewModelVersionTest(TestCase): def test_returns_configured_version(self) -> None: - """Returns the configured new model version""" - result = get_new_model_version() - assert result == GroupingVersion.V2 + assert get_new_model_version() == GroupingVersion.V2 def test_returns_none_when_disabled(self) -> None: - """Returns None when rollout is disabled""" with patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None): - result = get_new_model_version() - assert result is None + assert get_new_model_version() is None class ShouldSendToSeerForTrainingTest(TestCase): def test_returns_false_when_no_rollout(self) -> None: - """Returns False when no new version is being rolled out""" - with patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None): + with ( + patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None), + patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), + ): result = should_send_to_seer_for_training( self.project, grouphash_seer_latest_training_model=None ) assert result is False - def test_returns_false_when_feature_not_enabled(self) -> None: - """Returns False when feature flag is not enabled for project""" + def test_returns_false_when_no_flags(self) -> None: result = should_send_to_seer_for_training( self.project, grouphash_seer_latest_training_model=None ) assert result is False - def test_returns_true_when_never_sent(self) -> None: - """Returns True when grouphash has never been sent to Seer""" - with self.feature(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE): - result = should_send_to_seer_for_training( - self.project, grouphash_seer_latest_training_model=None - ) - assert result is True - - def test_returns_true_when_sent_to_old_version(self) -> None: - """Returns True when grouphash was sent to an older model version""" - with self.feature(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE): - result = should_send_to_seer_for_training( - self.project, grouphash_seer_latest_training_model="v1" - ) - assert result is True - - def test_returns_false_when_already_sent_to_new_version(self) -> None: - """Returns False when grouphash was already sent to the new version""" - with self.feature(SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE): - result = should_send_to_seer_for_training( - self.project, grouphash_seer_latest_training_model="v2" - ) - assert result is False + def test_returns_true_when_training_needed(self) -> None: + cases = [ + # (feature_flag, seer_latest_training_model) + (SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, None), + (SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, "v1"), + (SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, None), + (SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, "v1"), + (SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, "v2"), # v2 → v2.1 transition + ] + for feature_flag, training_model in cases: + with self.subTest(feature_flag=feature_flag, training_model=training_model): + with self.feature(feature_flag): + result = should_send_to_seer_for_training( + self.project, grouphash_seer_latest_training_model=training_model + ) + assert result is True + + def test_returns_false_when_already_sent_to_current_version(self) -> None: + cases = [ + # (feature_flag, seer_latest_training_model) + (SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, "v2"), + (SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, "v2.1"), + ] + for feature_flag, training_model in cases: + with self.subTest(feature_flag=feature_flag, training_model=training_model): + with self.feature(feature_flag): + result = should_send_to_seer_for_training( + self.project, grouphash_seer_latest_training_model=training_model + ) + assert result is False diff --git a/tests/sentry/seer/similarity/test_utils.py b/tests/sentry/seer/similarity/test_utils.py index 2ae2a6f77bca3f..6e2c7866d976f2 100644 --- a/tests/sentry/seer/similarity/test_utils.py +++ b/tests/sentry/seer/similarity/test_utils.py @@ -1001,11 +1001,7 @@ def test_bypassed_platforms_always_pass_for_v1(self) -> None: stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False ) - def test_bypassed_platforms_are_checked_for_v2(self) -> None: - """ - Test that V2 model applies length checks to all platforms, including those - that are bypassed for V1. - """ + def _assert_bypassed_platforms_are_checked(self, model_version: GroupingVersion) -> None: for platform in ["python", "javascript", "node", "go", "php", "ruby"]: self.event.data["platform"] = platform # Create a stacktrace that will exceed the token limit (repetitive chars compress @@ -1016,14 +1012,19 @@ def test_bypassed_platforms_are_checked_for_v2(self) -> None: with self.options({"seer.similarity.max_token_count": 100}): variants = self.event.get_grouping_variants(normalize_stacktraces=True) - # V2 should enforce length checks on bypassed platforms assert ( stacktrace_exceeds_limits( - self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V2 + self.event, variants, ReferrerOptions.INGEST, model_version ) is True ) + def test_bypassed_platforms_are_checked_for_v2(self) -> None: + self._assert_bypassed_platforms_are_checked(GroupingVersion.V2) + + def test_bypassed_platforms_are_checked_for_v2_1(self) -> None: + self._assert_bypassed_platforms_are_checked(GroupingVersion.V2_1) + class GetTokenCountTest(TestCase): def setUp(self) -> None: From fa94c3ec614f04d3f6e186f1bef12eda7290d23e Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Tue, 14 Apr 2026 12:50:48 -0700 Subject: [PATCH 2/5] rm unused get_new_model_version --- src/sentry/seer/similarity/config.py | 8 -------- tests/sentry/seer/similarity/test_config.py | 11 ----------- 2 files changed, 19 deletions(-) diff --git a/src/sentry/seer/similarity/config.py b/src/sentry/seer/similarity/config.py index 3f6762539d9080..c23ae05f847cf5 100644 --- a/src/sentry/seer/similarity/config.py +++ b/src/sentry/seer/similarity/config.py @@ -55,14 +55,6 @@ def is_new_model_rolled_out(project: Project) -> bool: return get_grouping_model_version(project) != SEER_GROUPING_STABLE_VERSION -def get_new_model_version() -> GroupingVersion | None: - """ - Get the new model version being rolled out, if any. - Returns None if no rollout is in progress. - """ - return SEER_GROUPING_NEW_VERSION - - def should_send_to_seer_for_training( project: Project, grouphash_seer_latest_training_model: str | None, diff --git a/tests/sentry/seer/similarity/test_config.py b/tests/sentry/seer/similarity/test_config.py index 76c89cd87f79d2..a6a7de419ac0e8 100644 --- a/tests/sentry/seer/similarity/test_config.py +++ b/tests/sentry/seer/similarity/test_config.py @@ -7,11 +7,9 @@ SEER_GROUPING_NEXT_VERSION, SEER_GROUPING_STABLE_VERSION, get_grouping_model_version, - get_new_model_version, is_new_model_rolled_out, should_send_to_seer_for_training, ) -from sentry.seer.similarity.types import GroupingVersion from sentry.testutils.cases import TestCase @@ -71,15 +69,6 @@ def test_returns_true_when_flag_enabled(self) -> None: assert is_new_model_rolled_out(self.project) is True -class GetNewModelVersionTest(TestCase): - def test_returns_configured_version(self) -> None: - assert get_new_model_version() == GroupingVersion.V2 - - def test_returns_none_when_disabled(self) -> None: - with patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None): - assert get_new_model_version() is None - - class ShouldSendToSeerForTrainingTest(TestCase): def test_returns_false_when_no_rollout(self) -> None: with ( From d44011e31497eaa48a76046d20577a02136e49f3 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Tue, 14 Apr 2026 13:11:43 -0700 Subject: [PATCH 3/5] require model version --- src/sentry/seer/similarity/utils.py | 2 +- tests/sentry/seer/similarity/test_utils.py | 43 +++++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/sentry/seer/similarity/utils.py b/src/sentry/seer/similarity/utils.py index 10aff5f469bdec..09a8535f51bfc7 100644 --- a/src/sentry/seer/similarity/utils.py +++ b/src/sentry/seer/similarity/utils.py @@ -347,7 +347,7 @@ def stacktrace_exceeds_limits( event: Event | GroupEvent, variants: dict[str, BaseVariant], referrer: ReferrerOptions, - model_version: GroupingVersion | None = None, + model_version: GroupingVersion, ) -> bool: """ Check if a stacktrace exceeds length limits for Seer similarity analysis. diff --git a/tests/sentry/seer/similarity/test_utils.py b/tests/sentry/seer/similarity/test_utils.py index 6e2c7866d976f2..33718435aff5ae 100644 --- a/tests/sentry/seer/similarity/test_utils.py +++ b/tests/sentry/seer/similarity/test_utils.py @@ -886,7 +886,12 @@ def test_passes_when_string_length_below_token_limit(self) -> None: variants = self.event.get_grouping_variants(normalize_stacktraces=True) # Should pass because string length (50 chars) < max_token_count (10000) - assert stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False + assert ( + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) + is False + ) def test_blocks_when_token_count_exceeds_limit(self) -> None: """ @@ -902,7 +907,12 @@ def test_blocks_when_token_count_exceeds_limit(self) -> None: variants = self.event.get_grouping_variants(normalize_stacktraces=True) # Should be blocked because token count will exceed 100 - assert stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is True + assert ( + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) + is True + ) def test_passes_when_string_long_but_tokens_under_limit(self) -> None: """ @@ -922,7 +932,12 @@ def test_passes_when_string_long_but_tokens_under_limit(self) -> None: variants = self.event.get_grouping_variants(normalize_stacktraces=True) # Should pass because token count is under the limit despite long string - assert stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False + assert ( + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) + is False + ) def test_uses_cached_stacktrace_string(self) -> None: """ @@ -937,7 +952,9 @@ def test_uses_cached_stacktrace_string(self) -> None: variants = self.event.get_grouping_variants(normalize_stacktraces=True) with patch("sentry.seer.similarity.utils.get_stacktrace_string") as mock_get_stacktrace: - stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) # Should not call get_stacktrace_string since we have cached value mock_get_stacktrace.assert_not_called() @@ -955,7 +972,12 @@ def test_generates_stacktrace_when_not_cached(self) -> None: variants = self.event.get_grouping_variants(normalize_stacktraces=True) # No cached stacktrace_string, so it should generate one - assert stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False + assert ( + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) + is False + ) def test_ignores_events_not_grouped_on_stacktrace(self) -> None: """ @@ -973,7 +995,12 @@ def test_ignores_events_not_grouped_on_stacktrace(self) -> None: assert isinstance(contributing_variant, CustomFingerprintVariant) # Should return False because it's not grouped on stacktrace - assert stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False + assert ( + stacktrace_exceeds_limits( + self.event, variants, ReferrerOptions.INGEST, GroupingVersion.V1 + ) + is False + ) def test_bypassed_platforms_always_pass_for_v1(self) -> None: """ @@ -996,10 +1023,6 @@ def test_bypassed_platforms_always_pass_for_v1(self) -> None: ) is False ) - # Also passes when no model version is specified (backward compat) - assert ( - stacktrace_exceeds_limits(self.event, variants, ReferrerOptions.INGEST) is False - ) def _assert_bypassed_platforms_are_checked(self, model_version: GroupingVersion) -> None: for platform in ["python", "javascript", "node", "go", "php", "ruby"]: From d6c03d35a0c5012421ce5d06154a75fb1e9ef921 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Tue, 14 Apr 2026 13:38:22 -0700 Subject: [PATCH 4/5] rm unused is_new_model_rolled_out --- src/sentry/seer/similarity/config.py | 7 ------- tests/sentry/seer/similarity/test_config.py | 21 --------------------- 2 files changed, 28 deletions(-) diff --git a/src/sentry/seer/similarity/config.py b/src/sentry/seer/similarity/config.py index c23ae05f847cf5..aff686719db0a8 100644 --- a/src/sentry/seer/similarity/config.py +++ b/src/sentry/seer/similarity/config.py @@ -48,13 +48,6 @@ def get_grouping_model_version(project: Project) -> GroupingVersion: return SEER_GROUPING_STABLE_VERSION -def is_new_model_rolled_out(project: Project) -> bool: - """ - Check if any non-stable model version is rolled out for this project. - """ - return get_grouping_model_version(project) != SEER_GROUPING_STABLE_VERSION - - def should_send_to_seer_for_training( project: Project, grouphash_seer_latest_training_model: str | None, diff --git a/tests/sentry/seer/similarity/test_config.py b/tests/sentry/seer/similarity/test_config.py index a6a7de419ac0e8..1b51f1b9e61d16 100644 --- a/tests/sentry/seer/similarity/test_config.py +++ b/tests/sentry/seer/similarity/test_config.py @@ -7,7 +7,6 @@ SEER_GROUPING_NEXT_VERSION, SEER_GROUPING_STABLE_VERSION, get_grouping_model_version, - is_new_model_rolled_out, should_send_to_seer_for_training, ) from sentry.testutils.cases import TestCase @@ -49,26 +48,6 @@ def test_falls_back_to_new_when_next_version_is_none(self) -> None: assert get_grouping_model_version(self.project) == SEER_GROUPING_NEW_VERSION -class IsNewModelRolledOutTest(TestCase): - def test_returns_false_when_no_flags(self) -> None: - assert is_new_model_rolled_out(self.project) is False - - def test_returns_false_when_all_versions_none(self) -> None: - with ( - patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None), - patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), - ): - assert is_new_model_rolled_out(self.project) is False - - def test_returns_true_when_flag_enabled(self) -> None: - for feature_flag in [ - SEER_GROUPING_NEW_MODEL_ROLLOUT_FEATURE, - SEER_GROUPING_NEXT_MODEL_ROLLOUT_FEATURE, - ]: - with self.subTest(feature_flag=feature_flag), self.feature(feature_flag): - assert is_new_model_rolled_out(self.project) is True - - class ShouldSendToSeerForTrainingTest(TestCase): def test_returns_false_when_no_rollout(self) -> None: with ( From 46a3e30add1b06d39e1d6761d33ef0b16cc76a7f Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Tue, 14 Apr 2026 13:47:18 -0700 Subject: [PATCH 5/5] some outdated docstrings --- src/sentry/grouping/ingest/seer.py | 6 +++--- src/sentry/seer/similarity/utils.py | 2 +- tests/sentry/grouping/seer_similarity/test_training_mode.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sentry/grouping/ingest/seer.py b/src/sentry/grouping/ingest/seer.py index 97a5b391e3fd2c..114298f582458f 100644 --- a/src/sentry/grouping/ingest/seer.py +++ b/src/sentry/grouping/ingest/seer.py @@ -623,10 +623,10 @@ def maybe_send_seer_for_new_model_training( variants: dict[str, BaseVariant], ) -> None: """ - Send a training_mode=true request to Seer for the new model version if the existing - grouphash hasn't been sent to the new version yet. + Send a training_mode=true request to Seer for the project's current non-stable model + version if the existing grouphash hasn't been sent to that version yet. - This only happens for projects that have the new model rolled out. It helps + This only happens for projects on a non-stable model (via feature flags). It helps build data for existing groups without affecting production grouping decisions. Args: diff --git a/src/sentry/seer/similarity/utils.py b/src/sentry/seer/similarity/utils.py index 09a8535f51bfc7..2835403fb8f93f 100644 --- a/src/sentry/seer/similarity/utils.py +++ b/src/sentry/seer/similarity/utils.py @@ -353,7 +353,7 @@ def stacktrace_exceeds_limits( Check if a stacktrace exceeds length limits for Seer similarity analysis. For V1, platforms that bypass length checks (to maintain consistency with backfilled data) - have all stacktraces pass through. For V2, all platforms are subject to length checks. + have all stacktraces pass through. For non-V1 models, all platforms are subject to length checks. If we dont bypass length checks, we use a two-step approach: 1. First check raw string length - if shorter than token limit, pass immediately diff --git a/tests/sentry/grouping/seer_similarity/test_training_mode.py b/tests/sentry/grouping/seer_similarity/test_training_mode.py index 498a98e9fc55e0..51cb607012e902 100644 --- a/tests/sentry/grouping/seer_similarity/test_training_mode.py +++ b/tests/sentry/grouping/seer_similarity/test_training_mode.py @@ -33,6 +33,7 @@ def test_does_nothing_when_no_rollout(self) -> None: """Should not send request when no new version is being rolled out""" with ( patch("sentry.seer.similarity.config.SEER_GROUPING_NEW_VERSION", None), + patch("sentry.seer.similarity.config.SEER_GROUPING_NEXT_VERSION", None), patch( "sentry.grouping.ingest.seer.get_similarity_data_from_seer" ) as mock_get_similarity_data,