Skip to content

Commit 0134e60

Browse files
committed
test(nextjs): Test error trace link for vercel ai in nextjs
1 parent 273cef9 commit 0134e60

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
const aiPipelineSpans = spans.filter(span => span.op === 'gen_ai.invoke_agent');
24+
const aiGenerateSpans = spans.filter(span => span.op === 'gen_ai.generate_text');
25+
const toolCallSpans = spans.filter(span => span.op === 'gen_ai.execute_tool');
26+
27+
expect(aiPipelineSpans.length).toBeGreaterThanOrEqual(1);
28+
expect(aiGenerateSpans.length).toBeGreaterThanOrEqual(1);
29+
expect(toolCallSpans.length).toBeGreaterThanOrEqual(0);
30+
31+
expect(errorEvent).toBeDefined();
32+
33+
//Verify error is linked to the same trace as the transaction
34+
expect(errorEvent?.contexts?.trace?.trace_id).toBe(aiTransaction.contexts?.trace?.trace_id);
35+
});

0 commit comments

Comments
 (0)