diff --git a/packages/sdk/server-ai/__tests__/LDAIConfigTrackerImpl.test.ts b/packages/sdk/server-ai/__tests__/LDAIConfigTrackerImpl.test.ts index 0c26f0029..483d09788 100644 --- a/packages/sdk/server-ai/__tests__/LDAIConfigTrackerImpl.test.ts +++ b/packages/sdk/server-ai/__tests__/LDAIConfigTrackerImpl.test.ts @@ -14,25 +14,38 @@ const mockLdClient: LDClientMin = { const testContext: LDContext = { kind: 'user', key: 'test-user' }; const configKey = 'test-config'; const variationKey = 'v1'; +const version = 1; beforeEach(() => { jest.clearAllMocks(); }); it('tracks duration', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackDuration(1000); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); }); it('tracks duration of async function', async () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000); const result = await tracker.trackDurationOf(async () => 'test-result'); @@ -41,61 +54,91 @@ it('tracks duration of async function', async () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); }); it('tracks time to first token', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackTimeToFirstToken(1000); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:ttf', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); }); it('tracks positive feedback', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackFeedback({ kind: LDFeedbackKind.Positive }); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:feedback:user:positive', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); it('tracks negative feedback', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackFeedback({ kind: LDFeedbackKind.Negative }); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:feedback:user:negative', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); it('tracks success', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackSuccess(); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); it('tracks OpenAI usage', async () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000); const TOTAL_TOKENS = 100; @@ -113,41 +156,47 @@ it('tracks OpenAI usage', async () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, TOTAL_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:input', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, PROMPT_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:output', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, COMPLETION_TOKENS, ); }); it('tracks error when OpenAI metrics function throws', async () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000); const error = new Error('OpenAI API error'); @@ -160,27 +209,33 @@ it('tracks error when OpenAI metrics function throws', async () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation:error', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); it('tracks Bedrock conversation with successful response', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); const TOTAL_TOKENS = 100; const PROMPT_TOKENS = 49; @@ -201,41 +256,47 @@ it('tracks Bedrock conversation with successful response', () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 500, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, TOTAL_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:input', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, PROMPT_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:output', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, COMPLETION_TOKENS, ); }); it('tracks Bedrock conversation with error response', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); const response = { $metadata: { httpStatusCode: 400 }, @@ -247,20 +308,26 @@ it('tracks Bedrock conversation with error response', () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation:error', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); it('tracks tokens', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); const TOTAL_TOKENS = 100; const PROMPT_TOKENS = 49; @@ -275,27 +342,33 @@ it('tracks tokens', () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, TOTAL_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:input', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, PROMPT_TOKENS, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:output', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, COMPLETION_TOKENS, ); }); it('only tracks non-zero token counts', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackTokens({ total: 0, @@ -313,7 +386,7 @@ it('only tracks non-zero token counts', () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:tokens:input', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 50, ); @@ -326,7 +399,13 @@ it('only tracks non-zero token counts', () => { }); it('returns empty summary when no metrics tracked', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); const summary = tracker.getSummary(); @@ -334,7 +413,13 @@ it('returns empty summary when no metrics tracked', () => { }); it('summarizes tracked metrics', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackDuration(1000); tracker.trackTokens({ @@ -362,7 +447,13 @@ it('summarizes tracked metrics', () => { }); it('tracks duration when async function throws', async () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000); const error = new Error('test error'); @@ -375,26 +466,32 @@ it('tracks duration when async function throws', async () => { expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:duration:total', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1000, ); }); it('tracks error', () => { - const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext); + const tracker = new LDAIConfigTrackerImpl( + mockLdClient, + configKey, + variationKey, + version, + testContext, + ); tracker.trackError(); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); expect(mockTrack).toHaveBeenCalledWith( '$ld:ai:generation:error', testContext, - { configKey, variationKey }, + { configKey, variationKey, version }, 1, ); }); diff --git a/packages/sdk/server-ai/src/LDAIClientImpl.ts b/packages/sdk/server-ai/src/LDAIClientImpl.ts index 965c49ea7..bca8431cc 100644 --- a/packages/sdk/server-ai/src/LDAIClientImpl.ts +++ b/packages/sdk/server-ai/src/LDAIClientImpl.ts @@ -13,6 +13,7 @@ import { LDClientMin } from './LDClientMin'; interface LDMeta { variationKey: string; enabled: boolean; + version?: number; } /** @@ -45,6 +46,8 @@ export class LDAIClientImpl implements LDAIClient { key, // eslint-disable-next-line no-underscore-dangle value._ldMeta?.variationKey ?? '', + // eslint-disable-next-line no-underscore-dangle + value._ldMeta?.version ?? 1, context, ); // eslint-disable-next-line no-underscore-dangle diff --git a/packages/sdk/server-ai/src/LDAIConfigTrackerImpl.ts b/packages/sdk/server-ai/src/LDAIConfigTrackerImpl.ts index b7884ff70..0972a5eee 100644 --- a/packages/sdk/server-ai/src/LDAIConfigTrackerImpl.ts +++ b/packages/sdk/server-ai/src/LDAIConfigTrackerImpl.ts @@ -13,13 +13,15 @@ export class LDAIConfigTrackerImpl implements LDAIConfigTracker { private _ldClient: LDClientMin, private _configKey: string, private _variationKey: string, + private _version: number, private _context: LDContext, ) {} - private _getTrackData(): { variationKey: string; configKey: string } { + private _getTrackData(): { variationKey: string; configKey: string; version: number } { return { variationKey: this._variationKey, configKey: this._configKey, + version: this._version, }; }