Skip to content

Commit 7989c8b

Browse files
andreiborzaclaude
andauthored
fix(server-utils): Keep OpenAI decision confidence on Vercel AI decide spans (#25457)
## What Decide spans from Vercel AI `experimental_decide` with `openai.decisionModel()` now keep the confidence of each answer. ## Why The OpenAI provider moves confidence out of the answers into its provider metadata, so before this change Sentry lost it. TypeSafe models already kept it. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a01cfca commit 7989c8b

2 files changed

Lines changed: 36 additions & 6 deletions

File tree

‎packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -701,13 +701,15 @@ function getOutputMessages(
701701
}
702702

703703
/**
704-
* The AI SDK TypeSafe provider moves each answer's `confidence` out of the answers into
705-
* `providerMetadata.typesafe.confidence` (keyed by question id). Put it back so decide answers keep it.
704+
* The AI SDK TypeSafe and OpenAI providers move each answer's `confidence` out of the answers into
705+
* `providerMetadata.<provider>.confidence` (keyed by question id). Put it back so decide answers keep it.
706706
*/
707707
function withProviderConfidence(result: Record<string, unknown>): unknown {
708708
const { answers, providerMetadata } = result;
709-
const typesafe = isObjectLike(providerMetadata) ? providerMetadata.typesafe : undefined;
710-
const confidence = isObjectLike(typesafe) && isObjectLike(typesafe.confidence) ? typesafe.confidence : undefined;
709+
const metadata = isObjectLike(providerMetadata)
710+
? [providerMetadata.typesafe, providerMetadata.openai].find(m => isObjectLike(m) && isObjectLike(m.confidence))
711+
: undefined;
712+
const confidence = isObjectLike(metadata) && isObjectLike(metadata.confidence) ? metadata.confidence : undefined;
711713
if (!confidence || !isObjectLike(answers)) {
712714
return answers;
713715
}

‎packages/server-utils/test/integrations/vercel-ai/decide.test.ts‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2+
import { GEN_AI_OUTPUT_MESSAGES } from '@sentry/conventions/attributes';
23
import { GEN_AI_DECIDE } from '@sentry/conventions/op';
34
import { getMainCarrier, setCurrentClient, spanToStaticSpanJSON } from '@sentry/core';
45
import type { Span } from '@sentry/core';
@@ -41,7 +42,7 @@ describe('Vercel AI decide spans', () => {
4142
recordOutputs: true,
4243
},
4344
result: { answers: { wantsRefund: { type: 'boolean', probability: 0.99 } } },
44-
} as unknown as Message;
45+
} satisfies Message;
4546

4647
const span = createSpanFromMessage(message, {} as Parameters<typeof createSpanFromMessage>[1])!;
4748
expect(span).toBeDefined();
@@ -51,8 +52,35 @@ describe('Vercel AI decide spans', () => {
5152
const json = spanToStaticSpanJSON(endedSpans[0]!);
5253
expect(json.description).toBe('decide typesafe-ai/jev');
5354
expect(json.op).toBe(GEN_AI_DECIDE);
54-
expect(JSON.parse(json.data?.['gen_ai.output.messages'] as string)).toEqual([
55+
expect(JSON.parse(json.data?.[GEN_AI_OUTPUT_MESSAGES] as string)).toEqual([
5556
{ type: 'evaluation', answers: { wantsRefund: { type: 'boolean', probability: 0.99 } } },
5657
]);
5758
});
59+
60+
it.each(['typesafe', 'openai'])('restores answer confidence from `providerMetadata.%s`', provider => {
61+
const message = {
62+
type: 'experimental_decide',
63+
event: {
64+
provider,
65+
modelId: 'gpt-6-luna',
66+
state: 'I was charged twice',
67+
questions: { department: { type: 'choice', criteria: { billing: 'Charges', support: 'Other' } } },
68+
recordInputs: true,
69+
recordOutputs: true,
70+
},
71+
result: {
72+
answers: { department: { type: 'choice', choice: 'billing' } },
73+
providerMetadata: { [provider]: { confidence: { department: 0.91 } } },
74+
},
75+
} satisfies Message;
76+
77+
const span = createSpanFromMessage(message, {} as Parameters<typeof createSpanFromMessage>[1])!;
78+
enrichSpanOnEnd(span, message, {} as Parameters<typeof enrichSpanOnEnd>[2]);
79+
span.end();
80+
81+
const json = spanToStaticSpanJSON(endedSpans[0]!);
82+
expect(JSON.parse(json.data?.[GEN_AI_OUTPUT_MESSAGES] as string)).toEqual([
83+
{ type: 'evaluation', answers: { department: { type: 'choice', choice: 'billing', confidence: 0.91 } } },
84+
]);
85+
});
5886
});

0 commit comments

Comments
 (0)