Repository navigation
feat(seer-billing): Check Seer credits before starting autofix steps #130283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cbdde3a
c247328
e2855ba
33f5287
d70d6cb
58a1b63
8856cd4
c49f7ed
8e5ca8f
0ab3b10
69f5488
0f648e8
a3dae0d
f738ddf
94b979c
302de4f
0ec42da
6f7cb6f
99eb2ac
e8f415e
396779d
90c5db5
ff4faae
b86335d
245aca9
80bba23
914a60c
28e5990
766bc10
6c0043d
1c7c190
4e289d3
451f3bd
ab0d0aa
89bdfdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| DataCategory, | ||
| ) | ||
| from sentry.models.group import Group | ||
| from sentry.models.organization import Organization | ||
| from sentry.models.project import Project | ||
| from sentry.seer.agent.client import SeerAgentClient | ||
| from sentry.seer.agent.client_utils import ( | ||
|
|
@@ -22,7 +23,7 @@ | |
| ) | ||
| from sentry.seer.agent.on_completion_hook import extract_hook_definition | ||
| from sentry.seer.autofix.constants import AutofixReferrer | ||
| from sentry.seer.autofix.exceptions import NoSeerQuotaException | ||
| from sentry.seer.autofix.exceptions import NoSeerQuotaException, SeerOutOfCreditsException | ||
| from sentry.seer.autofix.feature.models import ( | ||
| FEATURE_ID, | ||
| LEGACY_FEATURE_ID, | ||
|
|
@@ -41,6 +42,7 @@ | |
| get_automation_handoff, | ||
| is_free_cohort_org, | ||
| ) | ||
| from sentry.seer.credits import SeerOperation, has_enough_seer_credits | ||
| from sentry.seer.models.run import SeerAgentRun, SeerRun | ||
| from sentry.seer.models.seer_api_models import UNKNOWN_RUN_ID_FOR_GROUP, SeerPermissionError | ||
| from sentry.users.models.user import User | ||
|
|
@@ -148,6 +150,39 @@ def _org_allows_coding(group: Group) -> bool: | |
| ) | ||
|
|
||
|
|
||
| def check_autofix_billing( | ||
| organization: Organization, | ||
| operation: SeerOperation, | ||
| *, | ||
| is_new_run: bool, | ||
| skip_quota_for_free_cohort: bool = False, | ||
| ) -> bool: | ||
| """ | ||
| Raise if the organization can't pay for the operation. Token-billed orgs are | ||
| checked for credits, and everyone else is checked for quota once per run. | ||
| Returns whether the operation uses quota. | ||
| """ | ||
| # Check credits for token-billed orgs. | ||
| if features.has("organizations:token-billed-seer-enabled", organization): | ||
| if features.has( | ||
| "organizations:seer-credit-checks", organization | ||
| ) and not has_enough_seer_credits(organization, operation): | ||
| raise SeerOutOfCreditsException() | ||
| return False | ||
|
|
||
| # Check quota for non-token-billed orgs once per run. | ||
| # Free cohort orgs skip quota when the caller passes skip_quota_for_free_cohort=True. | ||
| # Only agentic triage does, and it is not exposed via API. | ||
| skips_quota = skip_quota_for_free_cohort and is_free_cohort_org(organization) | ||
| uses_quota = is_new_run and not skips_quota | ||
| if uses_quota and not quotas.backend.check_seer_quota( | ||
| org_id=organization.id, | ||
| data_category=DataCategory.SEER_AUTOFIX, | ||
| ): | ||
| raise NoSeerQuotaException() | ||
| return uses_quota | ||
|
|
||
|
|
||
| def continue_single_run( | ||
| agent_run: SeerAgentRun, | ||
| group: Group, | ||
|
|
@@ -159,6 +194,12 @@ def continue_single_run( | |
| ) -> SeerRun: | ||
| from sentry.seer.autofix.on_completion_hook import AutofixOnCompletionHook | ||
|
|
||
| check_autofix_billing( | ||
| group.organization, | ||
| SeerOperation.AUTOFIX_SINGLE_RUN_REPLY, | ||
|
srest2021 marked this conversation as resolved.
Resolved
|
||
| is_new_run=False, | ||
| ) | ||
|
Comment on lines
+197
to
+201
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 AI-assisted review, posted on behalf of @billyvg. Should a user's reply to an agentic triage run skip the credit check? Continuing a step-based agentic triage run from the issue page does get checked, because
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in #130552 |
||
|
|
||
| enable_coding = _org_allows_coding(group) | ||
| open_pr = enable_coding and can_publish | ||
| payload = AutofixSingleRunFeaturePayload( | ||
|
|
@@ -209,24 +250,36 @@ def trigger_autofix_feature( | |
| from sentry.seer.autofix.on_completion_hook import AutofixOnCompletionHook | ||
|
|
||
| is_new_run = args.existing_run_id is None | ||
| # Free cohort orgs bypass quota only when called from agentic triage | ||
| # (allow_free_cohort=True). Not exposed via the API. | ||
| skip_quota = is_new_run and args.allow_free_cohort and is_free_cohort_org(group.organization) | ||
| if is_new_run and not skip_quota: | ||
| has_budget: bool = quotas.backend.check_seer_quota( | ||
| org_id=group.organization.id, | ||
| data_category=DataCategory.SEER_AUTOFIX, | ||
| try: | ||
| uses_quota = check_autofix_billing( | ||
| group.organization, | ||
| args.step.seer_operation, | ||
| is_new_run=is_new_run, | ||
| # Free cohort orgs bypass quota only when called from agentic triage | ||
| # (allow_free_cohort=True). Not exposed via the API. | ||
| skip_quota_for_free_cohort=args.allow_free_cohort, | ||
| ) | ||
| except NoSeerQuotaException: | ||
| logger.warning( | ||
| "autofix_feature.dispatch.quota_denied", | ||
| extra={ | ||
| "group_id": group.id, | ||
| "organization_id": group.organization.id, | ||
| "referrer": args.referrer.value, | ||
| }, | ||
| ) | ||
| raise | ||
| except SeerOutOfCreditsException: | ||
| logger.info( | ||
| "autofix_feature.dispatch.out_of_credits", | ||
| extra={ | ||
| "group_id": group.id, | ||
| "organization_id": group.organization.id, | ||
| "operation": args.step.seer_operation.value, | ||
| "referrer": args.referrer.value, | ||
| }, | ||
| ) | ||
| if not has_budget: | ||
| logger.warning( | ||
| "autofix_feature.dispatch.quota_denied", | ||
| extra={ | ||
| "group_id": group.id, | ||
| "organization_id": group.organization.id, | ||
| "referrer": args.referrer.value, | ||
| }, | ||
| ) | ||
| raise NoSeerQuotaException() | ||
| raise | ||
|
|
||
| single_run = should_use_single_run( | ||
| group, | ||
|
|
@@ -342,7 +395,7 @@ def trigger_autofix_feature( | |
| else: | ||
| raise Exception("Unhandled run_id branch, this should never happen") | ||
|
|
||
| if is_new_run and not skip_quota: | ||
| if uses_quota: | ||
| quotas.backend.record_seer_run( | ||
| group.organization.id, group.project.id, DataCategory.SEER_AUTOFIX | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.