Skip to content

Commit 0cebb57

Browse files
authored
test(nextjs): Test error trace linking for Vercel AI in Next.js (#17200)
Add a test to verify that error trace linking works in Next.js and correctly bubbles up to the parent span.
1 parent 5a57f48 commit 0cebb57

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { generateText } from 'ai';
2+
import { MockLanguageModelV1 } from 'ai/test';
3+
import { z } from 'zod';
4+
import * as Sentry from '@sentry/nextjs';
5+
6+
export const dynamic = 'force-dynamic';
7+
8+
// Error trace handling in tool calls
9+
async function runAITest() {
10+
const result = await generateText({
11+
experimental_telemetry: { isEnabled: true },
12+
model: new MockLanguageModelV1({
13+
doGenerate: async () => ({
14+
rawCall: { rawPrompt: null, rawSettings: {} },
15+
finishReason: 'tool-calls',
16+
usage: { promptTokens: 15, completionTokens: 25 },
17+
text: 'Tool call completed!',
18+
toolCalls: [
19+
{
20+
toolCallType: 'function',
21+
toolCallId: 'call-1',
22+
toolName: 'getWeather',
23+
args: '{ "location": "San Francisco" }',
24+
},
25+
],
26+
}),
27+
}),
28+
tools: {
29+
getWeather: {
30+
parameters: z.object({ location: z.string() }),
31+
execute: async args => {
32+
throw new Error('Tool call failed');
33+
},
34+
},
35+
},
36+
prompt: 'What is the weather in San Francisco?',
37+
});
38+
}
39+
40+
export default async function Page() {
41+
await Sentry.startSpan({ op: 'function', name: 'ai-error-test' }, async () => {
42+
return await runAITest();
43+
});
44+
45+
return (
46+
<div>
47+
<h1>AI Test Results</h1>
48+
</div>
49+
);
50+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForTransaction, waitForError } from '@sentry-internal/test-utils';
3+
4+
test('should create AI spans with correct attributes and error linking', async ({ page }) => {
5+
const aiTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
6+
return transactionEvent.transaction === 'GET /ai-error-test';
7+
});
8+
9+
const errorEventPromise = waitForError('nextjs-15', async errorEvent => {
10+
return errorEvent.exception?.values?.[0]?.value?.includes('Tool call failed');
11+
});
12+
13+
await page.goto('/ai-error-test');
14+
15+
const aiTransaction = await aiTransactionPromise;
16+
const errorEvent = await errorEventPromise;
17+
18+
expect(aiTransaction).toBeDefined();
19+
expect(aiTransaction.transaction).toBe('GET /ai-error-test');
20+
21+
const spans = aiTransaction.spans || [];
22+
23+
// Each generateText call should create 2 spans: one for the pipeline and one for doGenerate
24+
// Plus a span for the tool call
25+
// TODO: For now, this is sadly not fully working - the monkey patching of the ai package is not working
26+
// because of this, only spans that are manually opted-in at call time will be captured
27+
// this may be fixed by https://github.com/vercel/ai/pull/6716 in the future
28+
const aiPipelineSpans = spans.filter(span => span.op === 'gen_ai.invoke_agent');
29+
const aiGenerateSpans = spans.filter(span => span.op === 'gen_ai.generate_text');
30+
const toolCallSpans = spans.filter(span => span.op === 'gen_ai.execute_tool');
31+
32+
expect(aiPipelineSpans.length).toBeGreaterThanOrEqual(1);
33+
expect(aiGenerateSpans.length).toBeGreaterThanOrEqual(1);
34+
expect(toolCallSpans.length).toBeGreaterThanOrEqual(0);
35+
36+
expect(errorEvent).toBeDefined();
37+
38+
//Verify error is linked to the same trace as the transaction
39+
expect(errorEvent?.contexts?.trace?.trace_id).toBe(aiTransaction.contexts?.trace?.trace_id);
40+
});

0 commit comments

Comments
 (0)