@launchdarkly/server-sdk-ai 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/__tests__/LDAIClientImpl.test.ts +328 -1
- package/__tests__/LDAIConfigTrackerImpl.test.ts +94 -136
- package/dist/LDAIClientImpl.d.ts +7 -0
- package/dist/LDAIClientImpl.d.ts.map +1 -1
- package/dist/LDAIClientImpl.js +60 -11
- package/dist/LDAIClientImpl.js.map +1 -1
- package/dist/LDAIConfigTrackerImpl.d.ts +3 -1
- package/dist/LDAIConfigTrackerImpl.d.ts.map +1 -1
- package/dist/LDAIConfigTrackerImpl.js +5 -3
- package/dist/LDAIConfigTrackerImpl.js.map +1 -1
- package/dist/api/LDAIClient.d.ts +70 -0
- package/dist/api/LDAIClient.d.ts.map +1 -1
- package/dist/api/agents/LDAIAgent.d.ts +32 -0
- package/dist/api/agents/LDAIAgent.d.ts.map +1 -0
- package/dist/api/agents/LDAIAgent.js +3 -0
- package/dist/api/agents/LDAIAgent.js.map +1 -0
- package/dist/api/agents/index.d.ts +2 -0
- package/dist/api/agents/index.d.ts.map +1 -0
- package/dist/api/agents/index.js +18 -0
- package/dist/api/agents/index.js.map +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/docs/assets/highlight.css +15 -8
- package/docs/assets/search.js +1 -1
- package/docs/enums/LDFeedbackKind.html +11 -8
- package/docs/functions/createBedrockTokenUsage.html +9 -6
- package/docs/functions/createOpenAiUsage.html +9 -6
- package/docs/functions/createVercelAISDKTokenUsage.html +9 -6
- package/docs/functions/initAi.html +9 -6
- package/docs/index.html +12 -6
- package/docs/interfaces/LDAIAgent.html +177 -0
- package/docs/interfaces/LDAIAgentConfig.html +111 -0
- package/docs/interfaces/LDAIClient.html +96 -10
- package/docs/interfaces/LDAIConfig.html +15 -12
- package/docs/interfaces/LDAIConfigTracker.html +21 -18
- package/docs/interfaces/LDMessage.html +11 -8
- package/docs/interfaces/LDModelConfig.html +12 -9
- package/docs/interfaces/LDProviderConfig.html +10 -7
- package/docs/interfaces/LDTokenUsage.html +12 -9
- package/docs/interfaces/VercelAISDKConfig.html +19 -16
- package/docs/interfaces/VercelAISDKMapOptions.html +10 -7
- package/docs/types/LDAIAgentDefaults.html +63 -0
- package/docs/types/LDAIDefaults.html +9 -6
- package/docs/types/VercelAISDKProvider.html +9 -6
- package/package.json +1 -1
- package/src/LDAIClientImpl.ts +146 -12
- package/src/LDAIConfigTrackerImpl.ts +11 -3
- package/src/api/LDAIClient.ts +80 -0
- package/src/api/agents/LDAIAgent.ts +36 -0
- package/src/api/agents/index.ts +1 -0
- package/src/api/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.10.1...server-sdk-ai-v0.11.0) (2025-08-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* Adding agent support for AI Configs ([#893](https://github.com/launchdarkly/js-core/issues/893)) ([bf95b92](https://github.com/launchdarkly/js-core/commit/bf95b92946e93b54e1eda7ffef96039b2b42b9aa))
|
|
9
|
+
* Update AI tracker to include model & provider name for metrics generation ([#901](https://github.com/launchdarkly/js-core/issues/901)) ([9474862](https://github.com/launchdarkly/js-core/commit/94748621034ed6b1a74060ee0c536bf96a3cd43d))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* Remove deprecated track generation event ([#902](https://github.com/launchdarkly/js-core/issues/902)) ([40f8593](https://github.com/launchdarkly/js-core/commit/40f859386087a443948214e9b535527f125ffa39))
|
|
15
|
+
|
|
3
16
|
## [0.10.1](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.10.0...server-sdk-ai-v0.10.1) (2025-07-23)
|
|
4
17
|
|
|
5
18
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { LDContext } from '@launchdarkly/js-server-sdk-common';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { LDAIAgentDefaults } from '../src/api/agents';
|
|
4
|
+
import {
|
|
5
|
+
LDAIDefaults,
|
|
6
|
+
VercelAISDKConfig,
|
|
7
|
+
VercelAISDKMapOptions,
|
|
8
|
+
VercelAISDKProvider,
|
|
9
|
+
} from '../src/api/config';
|
|
4
10
|
import { LDAIClientImpl } from '../src/LDAIClientImpl';
|
|
5
11
|
import { LDClientMin } from '../src/LDClientMin';
|
|
6
12
|
|
|
@@ -79,6 +85,7 @@ it('includes context in variables for messages interpolation', async () => {
|
|
|
79
85
|
const result = await client.config(key, testContext, defaultValue);
|
|
80
86
|
|
|
81
87
|
expect(result.messages?.[0].content).toBe('User key: test-user');
|
|
88
|
+
expect(result.toVercelAISDK).toEqual(expect.any(Function));
|
|
82
89
|
});
|
|
83
90
|
|
|
84
91
|
it('handles missing metadata in variation', async () => {
|
|
@@ -132,3 +139,323 @@ it('passes the default value to the underlying client', async () => {
|
|
|
132
139
|
|
|
133
140
|
expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
|
|
134
141
|
});
|
|
142
|
+
|
|
143
|
+
// New agent-related tests
|
|
144
|
+
it('returns single agent config with interpolated instructions', async () => {
|
|
145
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
146
|
+
const key = 'test-agent';
|
|
147
|
+
const defaultValue: LDAIAgentDefaults = {
|
|
148
|
+
model: { name: 'test', parameters: { name: 'test-model' } },
|
|
149
|
+
instructions: 'You are a helpful assistant.',
|
|
150
|
+
enabled: true,
|
|
151
|
+
toVercelAISDK: <TMod>(
|
|
152
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
153
|
+
options?: VercelAISDKMapOptions,
|
|
154
|
+
): VercelAISDKConfig<TMod> => {
|
|
155
|
+
const modelProvider = typeof provider === 'function' ? provider : provider.test;
|
|
156
|
+
return {
|
|
157
|
+
model: modelProvider('test-model'),
|
|
158
|
+
messages: [],
|
|
159
|
+
...(options?.nonInterpolatedMessages
|
|
160
|
+
? {
|
|
161
|
+
messages: options.nonInterpolatedMessages,
|
|
162
|
+
}
|
|
163
|
+
: {}),
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const mockVariation = {
|
|
169
|
+
model: {
|
|
170
|
+
name: 'example-model',
|
|
171
|
+
parameters: { name: 'imagination', temperature: 0.7, maxTokens: 4096 },
|
|
172
|
+
},
|
|
173
|
+
provider: {
|
|
174
|
+
name: 'example-provider',
|
|
175
|
+
},
|
|
176
|
+
instructions: 'You are a helpful assistant. Your name is {{name}} and your score is {{score}}',
|
|
177
|
+
_ldMeta: {
|
|
178
|
+
variationKey: 'v1',
|
|
179
|
+
enabled: true,
|
|
180
|
+
mode: 'agent',
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
mockLdClient.variation.mockResolvedValue(mockVariation);
|
|
185
|
+
|
|
186
|
+
const variables = { name: 'John', score: 42 };
|
|
187
|
+
const result = await client.agent(key, testContext, defaultValue, variables);
|
|
188
|
+
|
|
189
|
+
expect(result).toEqual({
|
|
190
|
+
model: {
|
|
191
|
+
name: 'example-model',
|
|
192
|
+
parameters: { name: 'imagination', temperature: 0.7, maxTokens: 4096 },
|
|
193
|
+
},
|
|
194
|
+
provider: {
|
|
195
|
+
name: 'example-provider',
|
|
196
|
+
},
|
|
197
|
+
instructions: 'You are a helpful assistant. Your name is John and your score is 42',
|
|
198
|
+
tracker: expect.any(Object),
|
|
199
|
+
enabled: true,
|
|
200
|
+
toVercelAISDK: expect.any(Function),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Verify tracking was called
|
|
204
|
+
expect(mockLdClient.track).toHaveBeenCalledWith(
|
|
205
|
+
'$ld:ai:agent:function:single',
|
|
206
|
+
testContext,
|
|
207
|
+
key,
|
|
208
|
+
1,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('includes context in variables for agent instructions interpolation', async () => {
|
|
213
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
214
|
+
const key = 'test-agent';
|
|
215
|
+
const defaultValue: LDAIAgentDefaults = {
|
|
216
|
+
model: { name: 'test', parameters: { name: 'test-model' } },
|
|
217
|
+
instructions: 'You are a helpful assistant.',
|
|
218
|
+
enabled: true,
|
|
219
|
+
toVercelAISDK: <TMod>(
|
|
220
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
221
|
+
options?: VercelAISDKMapOptions,
|
|
222
|
+
): VercelAISDKConfig<TMod> => {
|
|
223
|
+
const modelProvider = typeof provider === 'function' ? provider : provider.test;
|
|
224
|
+
return {
|
|
225
|
+
model: modelProvider('test-model'),
|
|
226
|
+
messages: [],
|
|
227
|
+
...(options?.nonInterpolatedMessages
|
|
228
|
+
? {
|
|
229
|
+
messages: options.nonInterpolatedMessages,
|
|
230
|
+
}
|
|
231
|
+
: {}),
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const mockVariation = {
|
|
237
|
+
instructions: 'You are a helpful assistant. Your user key is {{ldctx.key}}',
|
|
238
|
+
_ldMeta: { variationKey: 'v1', enabled: true, mode: 'agent' },
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
mockLdClient.variation.mockResolvedValue(mockVariation);
|
|
242
|
+
|
|
243
|
+
const result = await client.agent(key, testContext, defaultValue);
|
|
244
|
+
|
|
245
|
+
expect(result.instructions).toBe('You are a helpful assistant. Your user key is test-user');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('handles missing metadata in agent variation', async () => {
|
|
249
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
250
|
+
const key = 'test-agent';
|
|
251
|
+
const defaultValue: LDAIAgentDefaults = {
|
|
252
|
+
model: { name: 'test', parameters: { name: 'test-model' } },
|
|
253
|
+
instructions: 'You are a helpful assistant.',
|
|
254
|
+
enabled: true,
|
|
255
|
+
toVercelAISDK: <TMod>(
|
|
256
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
257
|
+
options?: VercelAISDKMapOptions,
|
|
258
|
+
): VercelAISDKConfig<TMod> => {
|
|
259
|
+
const modelProvider = typeof provider === 'function' ? provider : provider.test;
|
|
260
|
+
return {
|
|
261
|
+
model: modelProvider('test-model'),
|
|
262
|
+
messages: [],
|
|
263
|
+
...(options?.nonInterpolatedMessages
|
|
264
|
+
? {
|
|
265
|
+
messages: options.nonInterpolatedMessages,
|
|
266
|
+
}
|
|
267
|
+
: {}),
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const mockVariation = {
|
|
273
|
+
model: { name: 'example-provider', parameters: { name: 'imagination' } },
|
|
274
|
+
instructions: 'Hello.',
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
mockLdClient.variation.mockResolvedValue(mockVariation);
|
|
278
|
+
|
|
279
|
+
const result = await client.agent(key, testContext, defaultValue);
|
|
280
|
+
|
|
281
|
+
expect(result).toEqual({
|
|
282
|
+
model: { name: 'example-provider', parameters: { name: 'imagination' } },
|
|
283
|
+
instructions: 'Hello.',
|
|
284
|
+
tracker: expect.any(Object),
|
|
285
|
+
enabled: false,
|
|
286
|
+
toVercelAISDK: expect.any(Function),
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('passes the default value to the underlying client for single agent', async () => {
|
|
291
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
292
|
+
const key = 'non-existent-agent';
|
|
293
|
+
const defaultValue: LDAIAgentDefaults = {
|
|
294
|
+
model: { name: 'default-model', parameters: { name: 'default' } },
|
|
295
|
+
provider: { name: 'default-provider' },
|
|
296
|
+
instructions: 'Default instructions',
|
|
297
|
+
enabled: true,
|
|
298
|
+
toVercelAISDK: <TMod>(
|
|
299
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
300
|
+
options?: VercelAISDKMapOptions,
|
|
301
|
+
): VercelAISDKConfig<TMod> => {
|
|
302
|
+
const modelProvider =
|
|
303
|
+
typeof provider === 'function' ? provider : provider['default-provider'];
|
|
304
|
+
return {
|
|
305
|
+
model: modelProvider('default-model'),
|
|
306
|
+
messages: [],
|
|
307
|
+
...(options?.nonInterpolatedMessages
|
|
308
|
+
? {
|
|
309
|
+
messages: options.nonInterpolatedMessages,
|
|
310
|
+
}
|
|
311
|
+
: {}),
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
mockLdClient.variation.mockResolvedValue(defaultValue);
|
|
317
|
+
|
|
318
|
+
const result = await client.agent(key, testContext, defaultValue);
|
|
319
|
+
|
|
320
|
+
expect(result).toEqual({
|
|
321
|
+
model: defaultValue.model,
|
|
322
|
+
instructions: defaultValue.instructions,
|
|
323
|
+
provider: defaultValue.provider,
|
|
324
|
+
tracker: expect.any(Object),
|
|
325
|
+
enabled: false,
|
|
326
|
+
toVercelAISDK: expect.any(Function),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('returns multiple agents config with interpolated instructions', async () => {
|
|
333
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
334
|
+
|
|
335
|
+
const agentConfigs = [
|
|
336
|
+
{
|
|
337
|
+
key: 'research-agent',
|
|
338
|
+
defaultValue: {
|
|
339
|
+
model: { name: 'test', parameters: { name: 'test-model' } },
|
|
340
|
+
instructions: 'You are a research assistant.',
|
|
341
|
+
enabled: true,
|
|
342
|
+
toVercelAISDK: <TMod>(
|
|
343
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
344
|
+
options?: VercelAISDKMapOptions,
|
|
345
|
+
): VercelAISDKConfig<TMod> => {
|
|
346
|
+
const modelProvider = typeof provider === 'function' ? provider : provider.test;
|
|
347
|
+
return {
|
|
348
|
+
model: modelProvider('test-model'),
|
|
349
|
+
messages: [],
|
|
350
|
+
...(options?.nonInterpolatedMessages
|
|
351
|
+
? {
|
|
352
|
+
messages: options.nonInterpolatedMessages,
|
|
353
|
+
}
|
|
354
|
+
: {}),
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
variables: { topic: 'climate change' },
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
key: 'writing-agent',
|
|
362
|
+
defaultValue: {
|
|
363
|
+
model: { name: 'test', parameters: { name: 'test-model' } },
|
|
364
|
+
instructions: 'You are a writing assistant.',
|
|
365
|
+
enabled: true,
|
|
366
|
+
toVercelAISDK: <TMod>(
|
|
367
|
+
provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
|
|
368
|
+
options?: VercelAISDKMapOptions,
|
|
369
|
+
): VercelAISDKConfig<TMod> => {
|
|
370
|
+
const modelProvider = typeof provider === 'function' ? provider : provider.test;
|
|
371
|
+
return {
|
|
372
|
+
model: modelProvider('test-model'),
|
|
373
|
+
messages: [],
|
|
374
|
+
...(options?.nonInterpolatedMessages
|
|
375
|
+
? {
|
|
376
|
+
messages: options.nonInterpolatedMessages,
|
|
377
|
+
}
|
|
378
|
+
: {}),
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
variables: { style: 'academic' },
|
|
383
|
+
},
|
|
384
|
+
] as const;
|
|
385
|
+
|
|
386
|
+
const mockVariations = {
|
|
387
|
+
'research-agent': {
|
|
388
|
+
model: {
|
|
389
|
+
name: 'research-model',
|
|
390
|
+
parameters: { temperature: 0.3, maxTokens: 2048 },
|
|
391
|
+
},
|
|
392
|
+
provider: { name: 'openai' },
|
|
393
|
+
instructions: 'You are a research assistant specializing in {{topic}}.',
|
|
394
|
+
_ldMeta: { variationKey: 'v1', enabled: true, mode: 'agent' },
|
|
395
|
+
},
|
|
396
|
+
'writing-agent': {
|
|
397
|
+
model: {
|
|
398
|
+
name: 'writing-model',
|
|
399
|
+
parameters: { temperature: 0.7, maxTokens: 1024 },
|
|
400
|
+
},
|
|
401
|
+
provider: { name: 'anthropic' },
|
|
402
|
+
instructions: 'You are a writing assistant with {{style}} style.',
|
|
403
|
+
_ldMeta: { variationKey: 'v2', enabled: true, mode: 'agent' },
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
mockLdClient.variation.mockImplementation((key) =>
|
|
408
|
+
Promise.resolve(mockVariations[key as keyof typeof mockVariations]),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const result = await client.agents(agentConfigs, testContext);
|
|
412
|
+
|
|
413
|
+
expect(result).toEqual({
|
|
414
|
+
'research-agent': {
|
|
415
|
+
model: {
|
|
416
|
+
name: 'research-model',
|
|
417
|
+
parameters: { temperature: 0.3, maxTokens: 2048 },
|
|
418
|
+
},
|
|
419
|
+
provider: { name: 'openai' },
|
|
420
|
+
instructions: 'You are a research assistant specializing in climate change.',
|
|
421
|
+
tracker: expect.any(Object),
|
|
422
|
+
enabled: true,
|
|
423
|
+
toVercelAISDK: expect.any(Function),
|
|
424
|
+
},
|
|
425
|
+
'writing-agent': {
|
|
426
|
+
model: {
|
|
427
|
+
name: 'writing-model',
|
|
428
|
+
parameters: { temperature: 0.7, maxTokens: 1024 },
|
|
429
|
+
},
|
|
430
|
+
provider: { name: 'anthropic' },
|
|
431
|
+
instructions: 'You are a writing assistant with academic style.',
|
|
432
|
+
tracker: expect.any(Object),
|
|
433
|
+
enabled: true,
|
|
434
|
+
toVercelAISDK: expect.any(Function),
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Verify tracking was called
|
|
439
|
+
expect(mockLdClient.track).toHaveBeenCalledWith(
|
|
440
|
+
'$ld:ai:agent:function:multiple',
|
|
441
|
+
testContext,
|
|
442
|
+
agentConfigs.length,
|
|
443
|
+
agentConfigs.length,
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('handles empty agent configs array', async () => {
|
|
448
|
+
const client = new LDAIClientImpl(mockLdClient);
|
|
449
|
+
|
|
450
|
+
const result = await client.agents([], testContext);
|
|
451
|
+
|
|
452
|
+
expect(result).toEqual({});
|
|
453
|
+
|
|
454
|
+
// Verify tracking was called with 0 agents
|
|
455
|
+
expect(mockLdClient.track).toHaveBeenCalledWith(
|
|
456
|
+
'$ld:ai:agent:function:multiple',
|
|
457
|
+
testContext,
|
|
458
|
+
0,
|
|
459
|
+
0,
|
|
460
|
+
);
|
|
461
|
+
});
|