feat(seer): Add get_metric_metadata RPC for Seer metrics agent#113462
Merged
Conversation
Add a new cross-project RPC that returns distinct (metric.name, metric.type, metric.unit) tuples matching any of a set of metric.name substrings, with per-row event counts, ordered by count descending. Seer's metrics assisted-query agent will consume this from a new get_metric_candidates tool to short-circuit the iterative get_field_values(metric.name) + get_field_values(metric.type) discovery loop when the caller (e.g. Seer Explorer) does not scope the agent to a specific metric via metric_context. A single RPC returns name + type + unit for up to 20 distinct tuples, and the Seer-side tool reranks and truncates before passing candidates to the agent. Registered in both seer_rpc.py and organization_seer_rpc.py dispatch tables. Covered by unit tests that mock the events API client to verify the query shape (dataset=tracemetrics, sort=-count(), substring OR-query), tuple-level dedupe, has_more detection via over-fetch, and graceful handling of rows missing metric.name or metric.type. Also fixes SearchAgentTranslateEndpoint (sync /search-agent/translate/) to forward metric_context through options, matching the async /search-agent/start/ path. Previously the sync path only forwarded model_name, so a metrics-tab fallback to the sync endpoint would silently drop the metric scope and land on Seer as metric_context=None. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
isaacwang-sentry
marked this pull request as ready for review
April 20, 2026 21:39
Review feedback: silently returning empty candidates on internal failures (Organization.DoesNotExist, events API errors) masked real outages as "sparse metric catalog" responses. Seer's metric candidates tool only caught RPC-transport failures and treated handler-side failures as legitimate empty results, breaking Langfuse observability and misleading the sub-agent into retrying dead substrings. Return an explicit `error` key on handler-side failures — callers (Seer) translate this into a tool failure and fall back to get_field_values. Covered by new unit tests for the missing-org and events-query-failed paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
We over-fetch by 1 (per_page = limit + 1) specifically to detect whether Sentry has more matches than the caller asked for. The old check compared the *filtered* candidates list to limit, so a single malformed row (missing metric.name/metric.type) could push the post-filter count back down to limit and silently report has_more=False — hiding the fact that further matches existed. Low severity in practice (tracemetrics rows are unlikely to have null primary fields; Seer-side post-dedupe re-derives has_more anyway), but the contract is wrong. Check raw_rows length instead. New test pins the regression: 3 raw rows with one malformed → 2 candidates + has_more=True. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aliu39
approved these changes
Apr 20, 2026
isaacwang-sentry
added a commit
that referenced
this pull request
Apr 22, 2026
…#113616) Fix the `get_metric_metadata` RPC handler to use `count(value)` instead of zero-arg `count()` in its tracemetrics query. The `tracemetrics` dataset parser requires `count()` to take an attribute argument. Zero-arg `count()` parse-fails at the events layer with `status=400 body={'detail': 'Invalid number of arguments for count, was expecting 1 arguments', 'code': 'parse_error'}`. Every call to this handler since it was merged in #113462 has been returning `events_query_failed` — the Seer assisted-query metrics agent has never once received real candidates from this tool and has been silently falling back to `get_field_values` probes instead. The bug wasn't caught because the existing tests mocked `sentry.api.client` wholesale and never exercised the real parser. This PR also adds `TestGetMetricMetadataIntegration`, which persists real `TraceItem`s and hits the handler end-to-end via `TraceMetricsTestCase` — this is the shape of test that would have caught the original bug, and will catch future regressions in the aggregate-function shape. While here, the `ApiError` catch block now attaches `status_code` and a 500-char body prefix to its `logger.exception` extras so future flakes are debuggable from logs without a new deploy cycle. Reproduction (before this PR, on prod): ``` POST /api/0/organizations/1/seer-rpc/get_metric_metadata/ {"args": {"org_id": 1, "project_ids": [1], "name_substrings": ["storage.put.size"], "stats_period": "7d", "limit": 20}} ``` returns `{"candidates": [], "has_more": false, "error": "events_query_failed"}` on every substring, 25/25 tries. After this PR the same request returns populated candidates. Refs #113462 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
get_metric_metadata(org_id, project_ids, name_substrings, stats_period, limit)that returns distinct(metric.name, metric.type, metric.unit)tuples — plus per-row event counts — matching any of the provided metric-name substrings, ordered by count descending.seer_rpc.pyandorganization_seer_rpc.py. Implemented via/events/withdataset=tracemetrics, selectingmetric.name / metric.type / metric.unit / count()which implicitly groups by the non-aggregate fields./search-agent/translate/endpoint:SearchAgentTranslateEndpointpreviously droppedoptions.metric_context(only forwardedmodel_name), so a metrics-tab fallback from async polling to the sync path would silently lose the metric scope. Signature ofsend_translate_agentic_requestnow accepts and forwardsmetric_context, matching the async/search-agent/start/path.This is the Sentry-side half of adding candidate discovery to Seer's metrics agent; the Seer-side consumer lives in getsentry/seer#TBD (pushed in parallel). Seer falls back to
get_field_valueswhen this RPC is unavailable, so the two PRs can ship independently — but this one should land first so the Seer tool has something to call.Refs: follow-on to #113232 (which wired the metrics tab to Seer's metrics strategy).
Test plan
pytest tests/sentry/seer/assisted_query/test_metrics_tools.py -vv— new unit tests cover query shape (dataset=tracemetrics,sort=-count(), OR-combinedmetric.name:"*sub*"clauses), tuple-level dedupe,has_moredetection via over-fetch, and graceful handling of rows missingmetric.name/metric.type.pytest tests/sentry/seer/endpoints/test_trace_explorer_ai_translate_agentic.py -vv— existing happy-path test updated to assert the newmetric_context=Nonedefault; new testtest_translate_forwards_metric_contextverifies sync-path forwarding./api/0/organizations/<slug>/search-agent/translate/withstrategy=Metricsandoptions.metric_context={…}— confirm the payload reaching Seer carriesmetric_contextin options.🤖 Generated with Claude Code