From 7250b4f9052e958f88886a5fba3e9a56dd602616 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 15 Oct 2025 18:53:06 +0000 Subject: [PATCH 1/3] fix: Support previous v4 of Vercel AI SDK fix: Fix metric tracking for v5 responses --- .../__tests__/VercelProvider.test.ts | 46 +++++++++++++++++++ .../server-ai-vercel/package.json | 14 ++---- .../server-ai-vercel/src/VercelProvider.ts | 31 +++++++------ packages/sdk/server-ai/src/index.ts | 2 + 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/packages/ai-providers/server-ai-vercel/__tests__/VercelProvider.test.ts b/packages/ai-providers/server-ai-vercel/__tests__/VercelProvider.test.ts index 65a423a161..5dc97bb706 100644 --- a/packages/ai-providers/server-ai-vercel/__tests__/VercelProvider.test.ts +++ b/packages/ai-providers/server-ai-vercel/__tests__/VercelProvider.test.ts @@ -68,6 +68,52 @@ describe('VercelProvider', () => { }, }); }); + + it('supports v5 field names (inputTokens, outputTokens)', () => { + const mockResponse = { + usage: { + inputTokens: 40, + outputTokens: 60, + totalTokens: 100, + }, + }; + + const result = VercelProvider.createAIMetrics(mockResponse); + + expect(result).toEqual({ + success: true, + usage: { + total: 100, + input: 40, + output: 60, + }, + }); + }); + + it('prefers v5 field names over v4 when both are present', () => { + const mockResponse = { + usage: { + // v4 field names + promptTokens: 10, + completionTokens: 20, + // v5 field names (should be preferred) + inputTokens: 40, + outputTokens: 60, + totalTokens: 100, + }, + }; + + const result = VercelProvider.createAIMetrics(mockResponse); + + expect(result).toEqual({ + success: true, + usage: { + total: 100, + input: 40, // inputTokens preferred over promptTokens + output: 60, // outputTokens preferred over completionTokens + }, + }); + }); }); describe('invokeModel', () => { diff --git a/packages/ai-providers/server-ai-vercel/package.json b/packages/ai-providers/server-ai-vercel/package.json index ac08a8b1d9..c4027f9977 100644 --- a/packages/ai-providers/server-ai-vercel/package.json +++ b/packages/ai-providers/server-ai-vercel/package.json @@ -27,16 +27,7 @@ "author": "LaunchDarkly", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "^2.0.0", - "@launchdarkly/server-sdk-ai": "^0.12.1", - "ai": "^5.0.0" - }, - "optionalDependencies": { - "@ai-sdk/anthropic": "^2.0.0", - "@ai-sdk/cohere": "^2.0.0", - "@ai-sdk/google": "^2.0.0", - "@ai-sdk/mistral": "^2.0.0", - "@ai-sdk/openai": "^2.0.0" + "@launchdarkly/server-sdk-ai": "^0.12.1" }, "devDependencies": { "@ai-sdk/anthropic": "^2.0.0", @@ -49,6 +40,7 @@ "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", + "ai": "^5.0.0", "eslint": "^8.45.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.1.0", @@ -62,6 +54,6 @@ "typescript": "5.1.6" }, "peerDependencies": { - "@launchdarkly/js-server-sdk-common": "2.x" + "ai": "^4.0.0 || ^5.0.0" } } diff --git a/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts b/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts index a2ac1695bb..c89953224f 100644 --- a/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts +++ b/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts @@ -1,12 +1,11 @@ -import { LanguageModelV2 } from '@ai-sdk/provider'; -import { generateText } from 'ai'; +import { generateText, LanguageModel } from 'ai'; -import { LDLogger } from '@launchdarkly/js-server-sdk-common'; import { AIProvider, ChatResponse, LDAIConfig, LDAIMetrics, + LDLogger, LDMessage, LDTokenUsage, } from '@launchdarkly/server-sdk-ai'; @@ -16,10 +15,10 @@ import { * This provider integrates Vercel AI SDK with LaunchDarkly's tracking capabilities. */ export class VercelProvider extends AIProvider { - private _model: LanguageModelV2; + private _model: LanguageModel; private _parameters: Record; - constructor(model: LanguageModelV2, parameters: Record, logger?: LDLogger) { + constructor(model: LanguageModel, parameters: Record, logger?: LDLogger) { super(logger); this._model = model; this._parameters = parameters; @@ -47,6 +46,8 @@ export class VercelProvider extends AIProvider { */ async invokeModel(messages: LDMessage[]): Promise { // Call Vercel AI generateText + // Type assertion: our MinLanguageModel is compatible with the expected LanguageModel interface + // The generateText function will work with any object that has the required properties const result = await generateText({ model: this._model, messages, @@ -71,7 +72,7 @@ export class VercelProvider extends AIProvider { /** * Get the underlying Vercel AI model instance. */ - getModel(): LanguageModelV2 { + getModel(): LanguageModel { return this._model; } @@ -98,16 +99,18 @@ export class VercelProvider extends AIProvider { * Create AI metrics information from a Vercel AI response. * This method extracts token usage information and success status from Vercel AI responses * and returns a LaunchDarkly AIMetrics object. + * Supports both v4 and v5 field names for backward compatibility. */ static createAIMetrics(vercelResponse: any): LDAIMetrics { // Extract token usage if available let usage: LDTokenUsage | undefined; if (vercelResponse?.usage) { - const { promptTokens, completionTokens, totalTokens } = vercelResponse.usage; + const { totalTokens, inputTokens, promptTokens, outputTokens, completionTokens } = + vercelResponse.usage; usage = { total: totalTokens || 0, - input: promptTokens || 0, - output: completionTokens || 0, + input: inputTokens || promptTokens || 0, + output: outputTokens || completionTokens || 0, }; } @@ -125,7 +128,7 @@ export class VercelProvider extends AIProvider { * @param aiConfig The LaunchDarkly AI configuration * @returns A Promise that resolves to a configured Vercel AI model */ - static async createVercelModel(aiConfig: LDAIConfig): Promise { + static async createVercelModel(aiConfig: LDAIConfig): Promise { const providerName = VercelProvider.mapProvider(aiConfig.provider?.name || ''); const modelName = aiConfig.model?.name || ''; // Parameters are not used in model creation but kept for future use @@ -143,28 +146,28 @@ export class VercelProvider extends AIProvider { } case 'anthropic': try { - const { anthropic } = await import('@ai-sdk/anthropic' as any); + const { anthropic } = await import('@ai-sdk/anthropic'); return anthropic(modelName); } catch (error) { throw new Error(`Failed to load @ai-sdk/anthropic: ${error}`); } case 'google': try { - const { google } = await import('@ai-sdk/google' as any); + const { google } = await import('@ai-sdk/google'); return google(modelName); } catch (error) { throw new Error(`Failed to load @ai-sdk/google: ${error}`); } case 'cohere': try { - const { cohere } = await import('@ai-sdk/cohere' as any); + const { cohere } = await import('@ai-sdk/cohere'); return cohere(modelName); } catch (error) { throw new Error(`Failed to load @ai-sdk/cohere: ${error}`); } case 'mistral': try { - const { mistral } = await import('@ai-sdk/mistral' as any); + const { mistral } = await import('@ai-sdk/mistral'); return mistral(modelName); } catch (error) { throw new Error(`Failed to load @ai-sdk/mistral: ${error}`); diff --git a/packages/sdk/server-ai/src/index.ts b/packages/sdk/server-ai/src/index.ts index 2ca8209b53..3dfc66c0dd 100644 --- a/packages/sdk/server-ai/src/index.ts +++ b/packages/sdk/server-ai/src/index.ts @@ -19,4 +19,6 @@ export function initAi(ldClient: LDClientMin): LDAIClient { return new LDAIClientImpl(ldClient); } +export { LDLogger } from '@launchdarkly/js-server-sdk-common'; + export * from './api'; From fa3aa61b37746896b1db225bf83ab6227a060904 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 15 Oct 2025 20:48:53 +0000 Subject: [PATCH 2/3] count 0 as a valid value, only fallback if its undefined --- .../ai-providers/server-ai-vercel/src/VercelProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts b/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts index c89953224f..6687b76337 100644 --- a/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts +++ b/packages/ai-providers/server-ai-vercel/src/VercelProvider.ts @@ -108,9 +108,9 @@ export class VercelProvider extends AIProvider { const { totalTokens, inputTokens, promptTokens, outputTokens, completionTokens } = vercelResponse.usage; usage = { - total: totalTokens || 0, - input: inputTokens || promptTokens || 0, - output: outputTokens || completionTokens || 0, + total: totalTokens ?? 0, + input: inputTokens ?? promptTokens ?? 0, + output: outputTokens ?? completionTokens ?? 0, }; } From d94bbb7b87c46fadf0d70bc91c34eb4b0c0750a8 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 16 Oct 2025 13:31:55 +0000 Subject: [PATCH 3/3] remove dependency on ai-sdk/provider --- packages/ai-providers/server-ai-vercel/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ai-providers/server-ai-vercel/package.json b/packages/ai-providers/server-ai-vercel/package.json index 4d9e954991..4ba0efb36e 100644 --- a/packages/ai-providers/server-ai-vercel/package.json +++ b/packages/ai-providers/server-ai-vercel/package.json @@ -32,7 +32,6 @@ "@ai-sdk/google": "^2.0.0", "@ai-sdk/mistral": "^2.0.0", "@ai-sdk/openai": "^2.0.0", - "@ai-sdk/provider": "^2.0.0", "@launchdarkly/server-sdk-ai": "^0.12.2", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/jest": "^29.5.3", @@ -57,7 +56,6 @@ "@ai-sdk/google": "^2.0.0", "@ai-sdk/mistral": "^2.0.0", "@ai-sdk/openai": "^2.0.0", - "@ai-sdk/provider": "^2.0.0", "@launchdarkly/server-sdk-ai": "^0.12.2", "ai": "^4.0.0 || ^5.0.0" },