Skip to content

Commit 0befee0

Browse files
feat: Adding Vercel AI SDK mapper (#895)
1 parent a801353 commit 0befee0

13 files changed

+913
-10
lines changed

packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ it('returns config with interpolated messagess', async () => {
5757
],
5858
tracker: expect.any(Object),
5959
enabled: true,
60+
toVercelAISDK: expect.any(Function),
6061
});
6162
});
6263

@@ -102,6 +103,7 @@ it('handles missing metadata in variation', async () => {
102103
messages: [{ role: 'system', content: 'Hello' }],
103104
tracker: expect.any(Object),
104105
enabled: false,
106+
toVercelAISDK: expect.any(Function),
105107
});
106108
});
107109

@@ -125,6 +127,7 @@ it('passes the default value to the underlying client', async () => {
125127
provider: defaultValue.provider,
126128
tracker: expect.any(Object),
127129
enabled: false,
130+
toVercelAISDK: expect.any(Function),
128131
});
129132

130133
expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { LDMessage, VercelAISDKMapOptions } from '../src/api/config';
2+
import { LDAIConfigMapper } from '../src/LDAIConfigMapper';
3+
4+
describe('_findParameter', () => {
5+
it('handles undefined model and messages', () => {
6+
const mapper = new LDAIConfigMapper();
7+
// eslint-disable-next-line @typescript-eslint/dot-notation
8+
expect(mapper['_findParameter']<number>('test-param')).toBeUndefined();
9+
});
10+
11+
it('handles parameter not found', () => {
12+
const mapper = new LDAIConfigMapper({
13+
name: 'test-ai-model',
14+
parameters: {
15+
'test-param': 123,
16+
},
17+
custom: {
18+
'test-param': 456,
19+
},
20+
});
21+
// eslint-disable-next-line @typescript-eslint/dot-notation
22+
expect(mapper['_findParameter']<number>('other-param')).toBeUndefined();
23+
});
24+
25+
it('finds parameter from single model parameter', () => {
26+
const mapper = new LDAIConfigMapper({
27+
name: 'test-ai-model',
28+
parameters: {
29+
'test-param': 123,
30+
},
31+
});
32+
// eslint-disable-next-line @typescript-eslint/dot-notation
33+
expect(mapper['_findParameter']<number>('test-param')).toEqual(123);
34+
});
35+
36+
it('finds parameter from multiple model parameters', () => {
37+
const mapper = new LDAIConfigMapper({
38+
name: 'test-ai-model',
39+
parameters: {
40+
testParam: 123,
41+
},
42+
});
43+
// eslint-disable-next-line @typescript-eslint/dot-notation
44+
expect(mapper['_findParameter']<number>('test-param', 'testParam')).toEqual(123);
45+
});
46+
47+
it('finds parameter from single model custom parameter', () => {
48+
const mapper = new LDAIConfigMapper({
49+
name: 'test-ai-model',
50+
custom: {
51+
'test-param': 123,
52+
},
53+
});
54+
// eslint-disable-next-line @typescript-eslint/dot-notation
55+
expect(mapper['_findParameter']<number>('test-param')).toEqual(123);
56+
});
57+
58+
it('finds parameter from multiple model custom parameters', () => {
59+
const mapper = new LDAIConfigMapper({
60+
name: 'test-ai-model',
61+
custom: {
62+
testParam: 123,
63+
},
64+
});
65+
// eslint-disable-next-line @typescript-eslint/dot-notation
66+
expect(mapper['_findParameter']<number>('test-param', 'testParam')).toEqual(123);
67+
});
68+
69+
it('gives precedence to model parameters over model custom parameters', () => {
70+
const mapper = new LDAIConfigMapper({
71+
name: 'test-ai-model',
72+
parameters: {
73+
'test-param': 123,
74+
},
75+
custom: {
76+
'test-param': 456,
77+
},
78+
});
79+
// eslint-disable-next-line @typescript-eslint/dot-notation
80+
expect(mapper['_findParameter']<number>('test-param', 'testParam')).toEqual(123);
81+
});
82+
});
83+
84+
describe('toVercelAIAISDK', () => {
85+
const mockModel = { name: 'mockModel' };
86+
const mockMessages: LDMessage[] = [
87+
{ role: 'user', content: 'test prompt' },
88+
{ role: 'system', content: 'test instruction' },
89+
];
90+
const mockOptions: VercelAISDKMapOptions = {
91+
nonInterpolatedMessages: [{ role: 'assistant', content: 'test assistant instruction' }],
92+
};
93+
const mockProvider = jest.fn().mockReturnValue(mockModel);
94+
95+
beforeEach(() => {
96+
jest.clearAllMocks();
97+
});
98+
99+
it('handles undefined model and messages', () => {
100+
const mapper = new LDAIConfigMapper();
101+
const result = mapper.toVercelAISDK(mockProvider);
102+
103+
expect(mockProvider).toHaveBeenCalledWith('');
104+
expect(result).toEqual(
105+
expect.objectContaining({
106+
model: mockModel,
107+
messages: undefined,
108+
}),
109+
);
110+
});
111+
112+
it('uses additional messages', () => {
113+
const mapper = new LDAIConfigMapper({ name: 'test-ai-model' });
114+
const result = mapper.toVercelAISDK(mockProvider, mockOptions);
115+
116+
expect(mockProvider).toHaveBeenCalledWith('test-ai-model');
117+
expect(result).toEqual(
118+
expect.objectContaining({
119+
model: mockModel,
120+
messages: mockOptions.nonInterpolatedMessages,
121+
}),
122+
);
123+
});
124+
125+
it('combines config messages and additional messages', () => {
126+
const mapper = new LDAIConfigMapper({ name: 'test-ai-model' }, undefined, mockMessages);
127+
const result = mapper.toVercelAISDK(mockProvider, mockOptions);
128+
129+
expect(mockProvider).toHaveBeenCalledWith('test-ai-model');
130+
expect(result).toEqual(
131+
expect.objectContaining({
132+
model: mockModel,
133+
messages: [...mockMessages, ...(mockOptions.nonInterpolatedMessages ?? [])],
134+
}),
135+
);
136+
});
137+
138+
it('requests parameters correctly', () => {
139+
const mapper = new LDAIConfigMapper({ name: 'test-ai-model' }, undefined, mockMessages);
140+
const findParameterMock = jest.spyOn(mapper as any, '_findParameter');
141+
const result = mapper.toVercelAISDK(mockProvider);
142+
143+
expect(mockProvider).toHaveBeenCalledWith('test-ai-model');
144+
expect(result).toEqual(
145+
expect.objectContaining({
146+
model: mockModel,
147+
messages: mockMessages,
148+
}),
149+
);
150+
expect(findParameterMock).toHaveBeenCalledWith('max_tokens', 'maxTokens');
151+
expect(findParameterMock).toHaveBeenCalledWith('temperature');
152+
expect(findParameterMock).toHaveBeenCalledWith('top_p', 'topP');
153+
expect(findParameterMock).toHaveBeenCalledWith('top_k', 'topK');
154+
expect(findParameterMock).toHaveBeenCalledWith('presence_penalty', 'presencePenalty');
155+
expect(findParameterMock).toHaveBeenCalledWith('frequency_penalty', 'frequencyPenalty');
156+
expect(findParameterMock).toHaveBeenCalledWith('stop', 'stop_sequences', 'stopSequences');
157+
expect(findParameterMock).toHaveBeenCalledWith('seed');
158+
});
159+
});

0 commit comments

Comments
 (0)