Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export type ChannelEventType =
| 'embed'
| 'embedMany'
| 'rerank'
| 'experimental_evaluate';
| 'experimental_evaluate'
| 'experimental_decide';

/**
* The context object the AI SDK passes through one tracing-channel call. It is the same object
Expand Down Expand Up @@ -451,7 +452,9 @@ export function createSpanFromMessage(
}
case 'rerank':
return startGenAiSpan('rerank', modelId, baseAttributes);
// `ai` 7.0.128 renamed `experimental_evaluate` to `experimental_decide`; older 7.x still publishes the old name.
case 'experimental_evaluate':
case 'experimental_decide':
return startGenAiSpan('evaluate', modelId, {
...baseAttributes,
...(recordInputs
Expand Down Expand Up @@ -654,7 +657,7 @@ function getOutputMessages(
result: Record<string, unknown>,
finishReason: string | undefined,
): string | undefined {
if (type === 'experimental_evaluate') {
if (type === 'experimental_evaluate' || type === 'experimental_decide') {
return stringify([{ type: 'evaluation', answers: withProviderConfidence(result) }]);
}
// `languageModelCall` exposes the response as a `content` parts array; top-level results expose
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { getMainCarrier, setCurrentClient, spanToStaticSpanJSON } from '@sentry/core';
import type { Span } from '@sentry/core';
import { createSpanFromMessage, enrichSpanOnEnd } from '../../../src/integrations/vercel-ai/vercel-ai-dc-subscriber';
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';

type Message = Parameters<typeof createSpanFromMessage>[0];

describe('Vercel AI evaluate spans', () => {
let endedSpans: Span[];

beforeEach(() => {
getMainCarrier().__SENTRY__ = undefined;
const client = new TestClient(
getDefaultTestClientOptions({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1,
}),
);
setCurrentClient(client);
client.init();
endedSpans = [];
client.on('spanEnd', span => endedSpans.push(span));
});

afterEach(() => {
getMainCarrier().__SENTRY__ = undefined;
});

// `ai` 7.0.128 renamed the channel event from `experimental_evaluate` to `experimental_decide`.
it.each(['experimental_evaluate', 'experimental_decide'] as const)('creates an evaluate span for `%s`', type => {
const message = {
type,
event: {
provider: 'gateway',
modelId: 'typesafe-ai/jev',
state: 'I want a refund',
questions: { wantsRefund: { type: 'boolean', instructions: 'Is a refund requested?' } },
recordInputs: true,
recordOutputs: true,
},
result: { answers: { wantsRefund: { type: 'boolean', probability: 0.99 } } },
} as unknown as Message;

const span = createSpanFromMessage(message, {} as Parameters<typeof createSpanFromMessage>[1])!;
expect(span).toBeDefined();
enrichSpanOnEnd(span, message, {} as Parameters<typeof enrichSpanOnEnd>[2]);
span.end();

const json = spanToStaticSpanJSON(endedSpans[0]!);
expect(json.description).toBe('evaluate typesafe-ai/jev');
expect(json.op).toBe('gen_ai.evaluate');
expect(JSON.parse(json.data?.['gen_ai.output.messages'] as string)).toEqual([
{ type: 'evaluation', answers: { wantsRefund: { type: 'boolean', probability: 0.99 } } },
]);
});
});
Loading