Skip to content

ref(seer): Type 8 stats/replay/profile/export RPC responses#117880

Open
azulus wants to merge 6 commits into
masterfrom
jeremy/seer-rpc-type-bug-prediction-stats
Open

ref(seer): Type 8 stats/replay/profile/export RPC responses#117880
azulus wants to merge 6 commits into
masterfrom
jeremy/seer-rpc-type-bug-prediction-stats

Conversation

@azulus

@azulus azulus commented Jun 16, 2026

Copy link
Copy Markdown
Member

Replaces the dict[str, Any] / TypedDict / un-annotated return types on eight seer RPC methods with explicit Pydantic models so the seer-side SDK consumer sees declared field shapes.

Bug-prediction stats cluster (three methods that emit attribute / tag distribution data for the bug-prediction agent):

  • get_baseline_tag_distributionBaselineTagDistributionResponse with typed BaselineTagDistributionEntry items (tag_key, tag_value, count)
  • get_comparative_attribute_distributionsComparativeAttributeDistributionsResponse with the five named fields the spans frequency-stats endpoint produces (baseline_distribution, total_baseline, outliers_distribution, total_outliers, outliers_function_value)
  • get_issues_statsIssuesStatsResponse | None__root__-based list passthrough since the issues-stats API shape is wider than the documented id/count/userCount/firstSeen/lastSeen/stats/lifetime contract

Replay / profile / export / error-event singletons:

  • export_explorer_indexesAgentExportIndexesResponse — migrated from a TypedDict (org_id, version, tables) so the typed-registry contract holds
  • get_replay_metadataReplayMetadataResponse | None__root__ dict passthrough; the aggregate replay-event shape lives in the replays UI's ReplayDetailsResponse and is wider than what sentry-side can lock down
  • rpc_get_profile_flamegraphProfileFlamegraphSuccessResponse | ProfileFlamegraphErrorResponse discriminated by the presence of execution_tree/metadata vs error
  • rpc_get_replay_summary_logsReplaySummaryLogsResponse ({"logs": [str, ...]})
  • get_error_event_detailsErrorEventDetailsResponse | None__root__ dict passthrough; the bare EventSerializer shape (a SentryEventData-ish dict the seer caller casts to its own model) is too wide for sentry to fix here without a coordinated update

Wire-identical across all paths. The non-passthrough response models carry a small __getitem__ / __contains__ proxy so existing test sites and seer callers can read fields with result["x"] / "x" in result until they're migrated to attribute access.

Replaces the `dict[str, Any]` return annotations on three seer RPC methods
that emit attribute / tag distribution data for the bug-prediction agent:

- `get_baseline_tag_distribution` → `BaselineTagDistributionResponse`
  with typed `BaselineTagDistributionEntry` items (tag_key, tag_value, count)
- `get_comparative_attribute_distributions` → `ComparativeAttributeDistributionsResponse`
  with the five named fields the spans frequency-stats endpoint produces
  (baseline_distribution, total_baseline, outliers_distribution,
  total_outliers, outliers_function_value)
- `get_issues_stats` → `IssuesStatsResponse | None` — a `__root__`-based
  list passthrough since the issues-stats API shape is wider than the
  documented `id/count/userCount/firstSeen/lastSeen/stats/lifetime` contract

Wire-identical across all paths.
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 16, 2026
Replaces the `dict[str, Any]` / TypedDict / un-annotated return types on five
seer RPC methods with explicit Pydantic models:

- `export_explorer_indexes` → `AgentExportIndexesResponse` — migrated from a
  TypedDict (`org_id, version, tables`) so the typed-registry contract holds
- `get_replay_metadata` → `ReplayMetadataResponse | None` — `__root__` dict
  passthrough; the aggregate replay-event shape lives in the replays UI's
  `ReplayDetailsResponse` and is wider than what sentry-side can lock down
- `rpc_get_profile_flamegraph` → `ProfileFlamegraphSuccessResponse | ProfileFlamegraphErrorResponse`
  discriminated by the presence of `execution_tree`/`metadata` vs `error`
- `rpc_get_replay_summary_logs` → `ReplaySummaryLogsResponse` (`{"logs": [str, ...]}`)
- `get_error_event_details` → `ErrorEventDetailsResponse | None` — `__root__`
  dict passthrough; the bare `EventSerializer` shape (a `SentryEventData`-ish
  dict the seer caller casts to its own model) is too wide for sentry to fix
  here without a coordinated update

Wire-identical across all paths. The non-passthrough response models carry a
small `__getitem__` / `__contains__` proxy so existing test sites and seer
callers can read fields with `result["x"]` / `"x" in result` until they're
migrated to attribute access.
@azulus azulus changed the title ref(seer): Type the bug-prediction-stats RPC responses ref(seer): Type 8 stats/replay/profile/export RPC responses Jun 16, 2026
The replay-metadata tests parse the returned value through a private
`_ReplayMetadataResponse` schema as a contract check, but `get_replay_metadata`
now returns the typed `ReplayMetadataResponse` (a `__root__` model) rather
than a bare dict. `BaseModel.parse_obj` rejects a model instance, so pass
`result.dict()` to round-trip through the wire shape.
Comment thread src/sentry/seer/agent/snapshot_indexes.py
`AgentExportIndexesResponse(**response.json())` raises pydantic's
`ValidationError` when the seer response is valid JSON but missing or
mistyped against the declared schema. The pre-existing handler only
caught `JSONDecodeError`, so a schema mismatch would surface as an
uncaught 500 instead of a `SeerApiError`. Add the missing arm so the
caller sees the same `SeerApiError` shape it gets for malformed JSON.
@azulus azulus marked this pull request as ready for review June 16, 2026 23:43
@azulus azulus requested review from a team as code owners June 16, 2026 23:43
Comment thread src/sentry/seer/sentry_data_models.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5932c95. Configure here.

Comment thread src/sentry/seer/sentry_data_models.py Outdated
`ProfileFlamegraphMetadata.start_ts`/`end_ts` come from float-valued
`min(precise.start_ts)` / `max(precise.finish_ts)` aggregates and
`thread_id` is the dict key from `_convert_profile_to_execution_tree`'s
`dict[str, int]` count map (always a string). Typing them as `int | None`
made Pydantic v1 silently truncate the fractional seconds and risk
rejecting non-numeric thread ids. Type as `float | None` / `str | None`
to match the upstream values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant