Skip to content

feat(ui): surface gen_ai spans in AI Traces#1331

Open
jared-outpost[bot] wants to merge 5 commits into
mainfrom
issue-1330-pydantic-ai-traces
Open

feat(ui): surface gen_ai spans in AI Traces#1331
jared-outpost[bot] wants to merge 5 commits into
mainfrom
issue-1330-pydantic-ai-traces

Conversation

@jared-outpost

@jared-outpost jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

The AI Traces view only recognized Vercel AI SDK spans, so standard gen_ai.* spans emitted by Sentry SDKs (sentry-python's PydanticAI/OpenAI/Anthropic integrations, and any other SDK following sentry-conventions) never showed up there even though the span data was present.

Adds a generic genAIHandler that detects sentry-conventions gen_ai.* spans lacking vercel.ai.* fields and maps their standard fields — model, provider, token usage, request messages, response text, finish reason, and tool calls — into the AI Traces view. Detection is purely span-shape based (op prefixed gen_ai., a gen_ai.* data key, no vercel.ai.*), so it's SDK- and provider-agnostic — the provider is just the gen_ai.system attribute. extractAllAIRootSpans now attributes each root span to a single handler so spans are never surfaced twice. Vercel AI SDK keeps its own handler only because it emits proprietary vercel.ai.* fields (v1/v2) on top of gen_ai.*.

Testing

pnpm test for genAI.spec.ts (13 tests, all pass).

Closes #1330

Adds a sentryPythonAIHandler that detects OpenTelemetry gen_ai.* spans
emitted by sentry-python AI integrations (PydanticAI, OpenAI, Anthropic)
and maps their standard fields (model, provider, tokens, messages,
response text, tool calls) into the AI Traces view.

The handler claims gen_ai.* spans that lack vercel.ai.* fields, and
extractAllAIRootSpans now attributes each root span to a single handler
so spans are never surfaced twice.

Fixes #1330
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
spotlightjs Skipped Skipped Jun 25, 2026 3:48pm

Request Review

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ Patch coverage is 89.80%. Project has 862 uncovered lines.
❌ Project coverage is 57.68%. Comparing base (base) to head (head).

Files with missing lines (1)
File Patch % Lines
packages/spotlight/src/ui/telemetry/components/insights/aiTraces/sdks/genAI.ts 89.44% ⚠️ 15 Missing and 25 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    59.68%    57.68%       -2%
==========================================
  Files           53        57        +4
  Lines         1672      2048      +376
  Branches      1204      1476      +272
==========================================
+ Hits           994      1186      +192
- Misses         678       862      +184
- Partials       128       154       +26

Generated by Codecov Action

@github-actions

Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ Patch coverage is 89.61%. Project has 863 uncovered lines.
❌ Project coverage is 57.77%. Comparing base (base) to head (head).

Files with missing lines (1)
File Patch % Lines
packages/spotlight/src/ui/telemetry/components/insights/aiTraces/sdks/sentryPythonAI.ts 89.26% ⚠️ 16 Missing and 26 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    59.68%    57.77%    -1.91%
==========================================
  Files           53        57        +4
  Lines         1672      2055      +383
  Branches      1204      1492      +288
==========================================
+ Hits           994      1192      +198
- Misses         678       863      +185
- Partials       128       155       +27

Generated by Codecov Action

@jared-outpost jared-outpost Bot marked this pull request as ready for review June 25, 2026 15:32
@jared-outpost jared-outpost Bot requested a review from sergical June 25, 2026 15:32
@jared-outpost

jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Ready for review — all checks green (unit tests, e2e, build, warden, codeql, semgrep). Self-review found nothing to address. cc @sergical since you filed #1330.

@jared-outpost

jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Holding off on auto-merge — the diff is 496 lines (gate is 150), so this one warrants a human look. @sergical, over to you for review.

extractRootSpans recursed into children of any non-python span,
including Vercel gen_ai.* parents, so nested gen_ai.chat/tool children
lacking vercel.ai.* fields were collected as separate roots and the
same workflow appeared twice in AI Traces. Stop descending at any
gen_ai.* span since it's already the root for its tree.
extractResponseData captured response text/finish reason only while
empty, so an agent root with multiple gen_ai.chat children showed the
first reply. Spans iterate in tree pre-order, so take the last span's
values to reflect the latest model turn.
extractTokenUsage stopped at the first usage, so a multi-turn agent
root under-reported tokens. Sum input/output tokens across all spans
and source trace.metadata token counts from the same aggregate so the
prompt, token total, and response stay consistent.
@sergical

Copy link
Copy Markdown
Member

why are we making a pydantic distinction if its just gen_ai spans? can you look at https://github.com/getsentry/sentry-conventions and make sure the ai trace view supports all gen ai traces instead of having these silly providers, what is the reason for that anyway?

Rename the sentry-python handler to a generic gen_ai handler keyed off
the standard sentry-conventions gen_ai.* attributes. The provider is just
the gen_ai.system attribute, not a reason to fork per-SDK handlers. Vercel
keeps its own handler only because of its proprietary vercel.ai.* fields.
@jared-outpost

jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

good call — there was no real reason for a provider distinction. the handler was already keyed off the standard gen_ai.* attributes; the "sentry-python / pydantic" framing was just misleading naming.

renamed it to a generic gen_ai handler that maps any span following sentry-conventions regardless of provider — the provider is just the gen_ai.system attribute, not a reason to fork. PydanticAI/OpenAI/Anthropic all flow through the same path now. cca6119.

the only handler I kept separate is Vercel AI SDK, since it emits proprietary vercel.ai.* fields (and v1/v2 quirks) on top of gen_ai.* that the standard conventions don't cover. the generic handler defers anything carrying vercel.ai.* to it. if you'd rather collapse that too and treat Vercel's extras as bonus attributes, happy to — just means dropping its provider-specific extraction.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 cca6119. Configure here.

const responseText = span.data[GEN_AI_RESPONSE_TEXT_FIELD];
if (responseText) {
trace.response.text = Array.isArray(responseText) ? responseText.map(String).join("") : String(responseText);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Finish reason can stay stale

Low Severity

When merging response data across spans, extractResponseData always updates gen_ai.response.text from the latest span but only changes finishReason if that span includes gen_ai.response.finish_reasons. A later turn’s text can appear with an earlier turn’s finish reason.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cca6119. Configure here.

@sergical sergical added the jared Trigger the Jared agent to work on stuff label Jun 25, 2026
Comment on lines +289 to +291
if (responseText) {
trace.response.text = Array.isArray(responseText) ? responseText.map(String).join("") : String(responseText);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The code incorrectly handles an empty responseText array. Because [] is truthy, it gets processed into an empty string, which can overwrite valid response text from previous spans.
Severity: MEDIUM

Suggested Fix

Modify the condition to ensure responseText is not just truthy, but also contains data. For arrays, check responseText.length > 0. Alternatively, only assign to trace.response.text if the processed value is a non-empty string. For example: if (responseText && (!Array.isArray(responseText) || responseText.length > 0)) { ... }.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
packages/spotlight/src/ui/telemetry/components/insights/aiTraces/sdks/genAI.ts#L289-L291

Potential issue: In multi-turn AI agent traces, the code iterates through spans to
extract the final response text. The check `if (responseText)` is truthy for an empty
array `[]`. The code then processes this empty array into an empty string
(`[].map(String).join("")` results in `""`). This empty string then overwrites
`trace.response.text`, potentially erasing a valid response from a previous span. This
can happen if an intermediate span, such as a tool call, has an empty
`gen_ai.response.text` array. If this is the last span in the trace, the response text
will be permanently lost.

@sergical

Copy link
Copy Markdown
Member

can we confirm PydanticAI uses gen_ai traces and doesn't need a separate handler like the vercel ai sdk v1 and v2 since those are the ones that need vercel.ai where as all the subsequent versions are using gen_ai as expected

@jared-outpost

jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

confirmed — PydanticAI emits only standard gen_ai.* spans, no vercel.ai.*, so it goes through the generic handler with no special casing. checked sentry-python's pydantic_ai integration:

  • ops are gen_ai.chat / gen_ai.invoke_agent / gen_ai.execute_tool (from OP.* in consts.py)
  • attributes are all SPANDATA.GEN_AI_*gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens/output_tokens, gen_ai.request.messages, gen_ai.response.text, gen_ai.response.tool_calls, gen_ai.tool.{name,input,output}, etc.
  • the provider is just gen_ai.system, read off the model object at runtime — OpenAI/Anthropic/Gemini/etc. all flow through the same path, nothing hardcoded
  • zero vercel references anywhere in the integration

so your read is right: vercel.ai.* is the v1/v2 special case, and everything else (PydanticAI + the rest of sentry-python's AI integrations) is plain gen_ai handled generically. no separate PydanticAI handler needed.

@sergical

Copy link
Copy Markdown
Member

and this isn't only sentry-python specific, this will work across all sentry sdks if tehy want to use spotlight, right?

@jared-outpost

jared-outpost Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

right — nothing in the handler is Python-specific. it matches purely on the span shape: op prefixed gen_ai., at least one gen_ai.* data attribute, and no vercel.ai.*. any Sentry SDK that emits the standard sentry-conventions gen_ai.* spans — JS, Ruby, Go, etc. — surfaces in AI Traces through the same path. the "sentry-python" framing in the original PR title/description is just the example that prompted it; the implementation is SDK-agnostic. I'll update the PR title/description to drop the python-specific wording so it's not misleading.

@jared-outpost jared-outpost Bot changed the title feat(ui): surface sentry-python gen_ai spans in AI Traces feat(ui): surface gen_ai spans in AI Traces Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jared Trigger the Jared agent to work on stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support PydanticAI spans in Spotlight AI Traces view

1 participant