@launchdarkly/server-sdk-ai 0.1.0 → 0.2.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +14 -4
  3. package/__tests__/LDAIClientImpl.test.ts +136 -0
  4. package/__tests__/LDAIConfigTrackerImpl.test.ts +270 -0
  5. package/__tests__/TokenUsage.test.ts +78 -0
  6. package/dist/LDAIClientImpl.d.ts.map +1 -1
  7. package/dist/LDAIClientImpl.js +4 -1
  8. package/dist/LDAIClientImpl.js.map +1 -1
  9. package/dist/api/LDAIClient.d.ts +37 -21
  10. package/dist/api/LDAIClient.d.ts.map +1 -1
  11. package/dist/api/config/LDAIConfig.d.ts +9 -0
  12. package/dist/api/config/LDAIConfig.d.ts.map +1 -1
  13. package/dist/api/metrics/index.d.ts +0 -1
  14. package/dist/api/metrics/index.d.ts.map +1 -1
  15. package/dist/api/metrics/index.js +0 -1
  16. package/dist/api/metrics/index.js.map +1 -1
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/docs/assets/navigation.js +1 -1
  21. package/docs/assets/search.js +1 -1
  22. package/docs/enums/LDFeedbackKind.html +4 -4
  23. package/docs/functions/createBedrockTokenUsage.html +1 -1
  24. package/docs/functions/initAi.html +2 -2
  25. package/docs/hierarchy.html +1 -0
  26. package/docs/index.html +6 -3
  27. package/docs/interfaces/LDAIClient.html +15 -14
  28. package/docs/interfaces/LDAIConfig.html +5 -5
  29. package/docs/interfaces/LDAIConfigTracker.html +9 -9
  30. package/docs/interfaces/LDAIDefaults.html +8 -0
  31. package/docs/interfaces/LDGenerationConfig.html +4 -4
  32. package/docs/interfaces/LDMessage.html +4 -4
  33. package/docs/interfaces/LDModelConfig.html +10 -5
  34. package/docs/interfaces/LDTokenUsage.html +5 -5
  35. package/jest.config.js +7 -0
  36. package/package.json +2 -2
  37. package/src/LDAIClientImpl.ts +4 -1
  38. package/src/api/LDAIClient.ts +38 -22
  39. package/src/api/config/LDAIConfig.ts +11 -0
  40. package/src/api/metrics/index.ts +0 -1
  41. package/src/index.ts +8 -0
  42. package/dist/api/metrics/UnderScoreTokenUsage.d.ts +0 -3
  43. package/dist/api/metrics/UnderScoreTokenUsage.d.ts.map +0 -1
  44. package/dist/api/metrics/UnderScoreTokenUsage.js +0 -12
  45. package/dist/api/metrics/UnderScoreTokenUsage.js.map +0 -1
  46. package/docs/functions/createUnderscoreTokenUsage.html +0 -1
  47. package/src/api/metrics/UnderScoreTokenUsage.ts +0 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.1.1...server-sdk-ai-v0.2.0) (2024-11-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * Include temperature and maxTokens in LDModelConfig. ([978dfa9](https://github.com/launchdarkly/js-core/commit/978dfa95d1c25f942d96b730b187f92af045f90f))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Update default typings to include enabled. ([978dfa9](https://github.com/launchdarkly/js-core/commit/978dfa95d1c25f942d96b730b187f92af045f90f))
14
+
15
+ ## [0.1.1](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.1.0...server-sdk-ai-v0.1.1) (2024-11-08)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Do not include _ldMeta in returned config. ([#668](https://github.com/launchdarkly/js-core/issues/668)) ([89ce6db](https://github.com/launchdarkly/js-core/commit/89ce6dbbb2889af66ca53dd546c5977953dea972))
21
+ * Remove underscore token usage. Improve documentation. ([#667](https://github.com/launchdarkly/js-core/issues/667)) ([5fe36fb](https://github.com/launchdarkly/js-core/commit/5fe36fbd5b7047428204427fe6849d49de6ee952))
22
+
3
23
  ## 0.1.0 (2024-11-06)
4
24
 
5
25
 
package/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # LaunchDarkly AI SDK for Server-Side JavaScript
2
2
 
3
+ [![NPM][server-ai-sdk-npm-badge]][server-ai-sdk-npm-link]
4
+ [![Actions Status][server-ai-sdk-ci-badge]][server-ai-sdk-ci]
5
+ [![Documentation][server-ai-sdk-ghp-badge]][server-ai-sdk-ghp-link]
6
+ [![NPM][server-ai-sdk-dm-badge]][server-ai-sdk-npm-link]
7
+ [![NPM][server-ai-sdk-dt-badge]][server-ai-sdk-npm-link]
8
+
3
9
  # ⛔️⛔️⛔️⛔️
4
10
 
5
11
  > [!CAUTION]
@@ -62,7 +68,11 @@ We encourage pull requests and other contributions from the community. Check out
62
68
  - [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation
63
69
  - [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates
64
70
 
65
- [node-otel-ci-badge]: https://github.com/launchdarkly/js-core/actions/workflows/node-otel.yml/badge.svg
66
- [node-otel-ci]: https://github.com/launchdarkly/js-core/actions/workflows/node-otel.yml
67
- [node-otel-npm-badge]: https://img.shields.io/npm/v/@launchdarkly/node-server-sdk-otel.svg?style=flat-square
68
- [node-otel-npm-link]: https://www.npmjs.com/package/@launchdarkly/node-server-sdk-otel
71
+ [server-ai-sdk-ci-badge]: https://github.com/launchdarkly/js-core/actions/workflows/server-ai.yml/badge.svg
72
+ [server-ai-sdk-ci]: https://github.com/launchdarkly/js-core/actions/workflows/server-ai.yml
73
+ [server-ai-sdk-npm-badge]: https://img.shields.io/npm/v/@launchdarkly/server-sdk-ai.svg?style=flat-square
74
+ [server-ai-sdk-npm-link]: https://www.npmjs.com/package/@launchdarkly/server-sdk-ai
75
+ [server-ai-sdk-ghp-badge]: https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8
76
+ [server-ai-sdk-ghp-link]: https://launchdarkly.github.io/js-core/packages/sdk/server-ai/docs/
77
+ [server-ai-sdk-dm-badge]: https://img.shields.io/npm/dm/@launchdarkly/server-sdk-ai.svg?style=flat-square
78
+ [server-ai-sdk-dt-badge]: https://img.shields.io/npm/dt/@launchdarkly/server-sdk-ai.svg?style=flat-square
@@ -0,0 +1,136 @@
1
+ import { LDContext } from '@launchdarkly/js-server-sdk-common';
2
+
3
+ import { LDGenerationConfig } from '../src/api/config';
4
+ import { LDAIClientImpl } from '../src/LDAIClientImpl';
5
+ import { LDClientMin } from '../src/LDClientMin';
6
+
7
+ const mockLdClient: jest.Mocked<LDClientMin> = {
8
+ variation: jest.fn(),
9
+ track: jest.fn(),
10
+ };
11
+
12
+ const testContext: LDContext = { kind: 'user', key: 'test-user' };
13
+
14
+ it('interpolates template variables', () => {
15
+ const client = new LDAIClientImpl(mockLdClient);
16
+ const template = 'Hello {{name}}, your score is {{score}}';
17
+ const variables = { name: 'John', score: 42 };
18
+
19
+ const result = client.interpolateTemplate(template, variables);
20
+ expect(result).toBe('Hello John, your score is 42');
21
+ });
22
+
23
+ it('handles empty variables in template interpolation', () => {
24
+ const client = new LDAIClientImpl(mockLdClient);
25
+ const template = 'Hello {{name}}';
26
+ const variables = {};
27
+
28
+ const result = client.interpolateTemplate(template, variables);
29
+ expect(result).toBe('Hello ');
30
+ });
31
+
32
+ it('returns model config with interpolated prompts', async () => {
33
+ const client = new LDAIClientImpl(mockLdClient);
34
+ const key = 'test-flag';
35
+ const defaultValue: LDGenerationConfig = {
36
+ model: { modelId: 'test', name: 'test-model' },
37
+ prompt: [],
38
+ };
39
+
40
+ const mockVariation = {
41
+ model: { modelId: 'example-provider', name: 'imagination' },
42
+ prompt: [
43
+ { role: 'system', content: 'Hello {{name}}' },
44
+ { role: 'user', content: 'Score: {{score}}' },
45
+ ],
46
+ _ldMeta: {
47
+ versionKey: 'v1',
48
+ enabled: true,
49
+ },
50
+ };
51
+
52
+ mockLdClient.variation.mockResolvedValue(mockVariation);
53
+
54
+ const variables = { name: 'John', score: 42 };
55
+ const result = await client.modelConfig(key, testContext, defaultValue, variables);
56
+
57
+ expect(result).toEqual({
58
+ config: {
59
+ model: { modelId: 'example-provider', name: 'imagination' },
60
+ prompt: [
61
+ { role: 'system', content: 'Hello John' },
62
+ { role: 'user', content: 'Score: 42' },
63
+ ],
64
+ },
65
+ tracker: expect.any(Object),
66
+ enabled: true,
67
+ });
68
+ });
69
+
70
+ it('includes context in variables for prompt interpolation', async () => {
71
+ const client = new LDAIClientImpl(mockLdClient);
72
+ const key = 'test-flag';
73
+ const defaultValue: LDGenerationConfig = {
74
+ model: { modelId: 'test', name: 'test-model' },
75
+ prompt: [],
76
+ };
77
+
78
+ const mockVariation = {
79
+ prompt: [{ role: 'system', content: 'User key: {{ldctx.key}}' }],
80
+ _ldMeta: { versionKey: 'v1', enabled: true },
81
+ };
82
+
83
+ mockLdClient.variation.mockResolvedValue(mockVariation);
84
+
85
+ const result = await client.modelConfig(key, testContext, defaultValue);
86
+
87
+ expect(result.config.prompt?.[0].content).toBe('User key: test-user');
88
+ });
89
+
90
+ it('handles missing metadata in variation', async () => {
91
+ const client = new LDAIClientImpl(mockLdClient);
92
+ const key = 'test-flag';
93
+ const defaultValue: LDGenerationConfig = {
94
+ model: { modelId: 'test', name: 'test-model' },
95
+ prompt: [],
96
+ };
97
+
98
+ const mockVariation = {
99
+ model: { modelId: 'example-provider', name: 'imagination' },
100
+ prompt: [{ role: 'system', content: 'Hello' }],
101
+ };
102
+
103
+ mockLdClient.variation.mockResolvedValue(mockVariation);
104
+
105
+ const result = await client.modelConfig(key, testContext, defaultValue);
106
+
107
+ expect(result).toEqual({
108
+ config: {
109
+ model: { modelId: 'example-provider', name: 'imagination' },
110
+ prompt: [{ role: 'system', content: 'Hello' }],
111
+ },
112
+ tracker: expect.any(Object),
113
+ enabled: false,
114
+ });
115
+ });
116
+
117
+ it('passes the default value to the underlying client', async () => {
118
+ const client = new LDAIClientImpl(mockLdClient);
119
+ const key = 'non-existent-flag';
120
+ const defaultValue: LDGenerationConfig = {
121
+ model: { modelId: 'default-model', name: 'default' },
122
+ prompt: [{ role: 'system', content: 'Default prompt' }],
123
+ };
124
+
125
+ mockLdClient.variation.mockResolvedValue(defaultValue);
126
+
127
+ const result = await client.modelConfig(key, testContext, defaultValue);
128
+
129
+ expect(result).toEqual({
130
+ config: defaultValue,
131
+ tracker: expect.any(Object),
132
+ enabled: false,
133
+ });
134
+
135
+ expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
136
+ });
@@ -0,0 +1,270 @@
1
+ import { LDContext } from '@launchdarkly/js-server-sdk-common';
2
+
3
+ import { LDFeedbackKind } from '../src/api/metrics';
4
+ import { LDAIConfigTrackerImpl } from '../src/LDAIConfigTrackerImpl';
5
+ import { LDClientMin } from '../src/LDClientMin';
6
+
7
+ const mockTrack = jest.fn();
8
+ const mockVariation = jest.fn();
9
+ const mockLdClient: LDClientMin = {
10
+ track: mockTrack,
11
+ variation: mockVariation,
12
+ };
13
+
14
+ const testContext: LDContext = { kind: 'user', key: 'test-user' };
15
+ const configKey = 'test-config';
16
+ const versionKey = 'v1';
17
+
18
+ beforeEach(() => {
19
+ jest.clearAllMocks();
20
+ });
21
+
22
+ it('tracks duration', () => {
23
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
24
+ tracker.trackDuration(1000);
25
+
26
+ expect(mockTrack).toHaveBeenCalledWith(
27
+ '$ld:ai:duration:total',
28
+ testContext,
29
+ { configKey, versionKey },
30
+ 1000,
31
+ );
32
+ });
33
+
34
+ it('tracks duration of async function', async () => {
35
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
36
+ jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000);
37
+
38
+ const result = await tracker.trackDurationOf(async () => 'test-result');
39
+
40
+ expect(result).toBe('test-result');
41
+ expect(mockTrack).toHaveBeenCalledWith(
42
+ '$ld:ai:duration:total',
43
+ testContext,
44
+ { configKey, versionKey },
45
+ 1000,
46
+ );
47
+ });
48
+
49
+ it('tracks positive feedback', () => {
50
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
51
+ tracker.trackFeedback({ kind: LDFeedbackKind.Positive });
52
+
53
+ expect(mockTrack).toHaveBeenCalledWith(
54
+ '$ld:ai:feedback:user:positive',
55
+ testContext,
56
+ { configKey, versionKey },
57
+ 1,
58
+ );
59
+ });
60
+
61
+ it('tracks negative feedback', () => {
62
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
63
+ tracker.trackFeedback({ kind: LDFeedbackKind.Negative });
64
+
65
+ expect(mockTrack).toHaveBeenCalledWith(
66
+ '$ld:ai:feedback:user:negative',
67
+ testContext,
68
+ { configKey, versionKey },
69
+ 1,
70
+ );
71
+ });
72
+
73
+ it('tracks success', () => {
74
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
75
+ tracker.trackSuccess();
76
+
77
+ expect(mockTrack).toHaveBeenCalledWith(
78
+ '$ld:ai:generation',
79
+ testContext,
80
+ { configKey, versionKey },
81
+ 1,
82
+ );
83
+ });
84
+
85
+ it('tracks OpenAI usage', async () => {
86
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
87
+ jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000);
88
+
89
+ const TOTAL_TOKENS = 100;
90
+ const PROMPT_TOKENS = 49;
91
+ const COMPLETION_TOKENS = 51;
92
+
93
+ await tracker.trackOpenAI(async () => ({
94
+ usage: {
95
+ total_tokens: TOTAL_TOKENS,
96
+ prompt_tokens: PROMPT_TOKENS,
97
+ completion_tokens: COMPLETION_TOKENS,
98
+ },
99
+ }));
100
+
101
+ expect(mockTrack).toHaveBeenCalledWith(
102
+ '$ld:ai:duration:total',
103
+ testContext,
104
+ { configKey, versionKey },
105
+ 1000,
106
+ );
107
+
108
+ expect(mockTrack).toHaveBeenCalledWith(
109
+ '$ld:ai:generation',
110
+ testContext,
111
+ { configKey, versionKey },
112
+ 1,
113
+ );
114
+
115
+ expect(mockTrack).toHaveBeenCalledWith(
116
+ '$ld:ai:tokens:total',
117
+ testContext,
118
+ { configKey, versionKey },
119
+ TOTAL_TOKENS,
120
+ );
121
+
122
+ expect(mockTrack).toHaveBeenCalledWith(
123
+ '$ld:ai:tokens:input',
124
+ testContext,
125
+ { configKey, versionKey },
126
+ PROMPT_TOKENS,
127
+ );
128
+
129
+ expect(mockTrack).toHaveBeenCalledWith(
130
+ '$ld:ai:tokens:output',
131
+ testContext,
132
+ { configKey, versionKey },
133
+ COMPLETION_TOKENS,
134
+ );
135
+ });
136
+
137
+ it('tracks Bedrock conversation with successful response', () => {
138
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
139
+
140
+ const TOTAL_TOKENS = 100;
141
+ const PROMPT_TOKENS = 49;
142
+ const COMPLETION_TOKENS = 51;
143
+
144
+ const response = {
145
+ $metadata: { httpStatusCode: 200 },
146
+ metrics: { latencyMs: 500 },
147
+ usage: {
148
+ inputTokens: PROMPT_TOKENS,
149
+ outputTokens: COMPLETION_TOKENS,
150
+ totalTokens: TOTAL_TOKENS,
151
+ },
152
+ };
153
+
154
+ tracker.trackBedrockConverse(response);
155
+
156
+ expect(mockTrack).toHaveBeenCalledWith(
157
+ '$ld:ai:generation',
158
+ testContext,
159
+ { configKey, versionKey },
160
+ 1,
161
+ );
162
+
163
+ expect(mockTrack).toHaveBeenCalledWith(
164
+ '$ld:ai:duration:total',
165
+ testContext,
166
+ { configKey, versionKey },
167
+ 500,
168
+ );
169
+
170
+ expect(mockTrack).toHaveBeenCalledWith(
171
+ '$ld:ai:tokens:total',
172
+ testContext,
173
+ { configKey, versionKey },
174
+ TOTAL_TOKENS,
175
+ );
176
+
177
+ expect(mockTrack).toHaveBeenCalledWith(
178
+ '$ld:ai:tokens:input',
179
+ testContext,
180
+ { configKey, versionKey },
181
+ PROMPT_TOKENS,
182
+ );
183
+
184
+ expect(mockTrack).toHaveBeenCalledWith(
185
+ '$ld:ai:tokens:output',
186
+ testContext,
187
+ { configKey, versionKey },
188
+ COMPLETION_TOKENS,
189
+ );
190
+ });
191
+
192
+ it('tracks Bedrock conversation with error response', () => {
193
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
194
+
195
+ const response = {
196
+ $metadata: { httpStatusCode: 400 },
197
+ };
198
+
199
+ // TODO: We may want a track failure.
200
+
201
+ tracker.trackBedrockConverse(response);
202
+
203
+ expect(mockTrack).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it('tracks tokens', () => {
207
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
208
+
209
+ const TOTAL_TOKENS = 100;
210
+ const PROMPT_TOKENS = 49;
211
+ const COMPLETION_TOKENS = 51;
212
+
213
+ tracker.trackTokens({
214
+ total: TOTAL_TOKENS,
215
+ input: PROMPT_TOKENS,
216
+ output: COMPLETION_TOKENS,
217
+ });
218
+
219
+ expect(mockTrack).toHaveBeenCalledWith(
220
+ '$ld:ai:tokens:total',
221
+ testContext,
222
+ { configKey, versionKey },
223
+ TOTAL_TOKENS,
224
+ );
225
+
226
+ expect(mockTrack).toHaveBeenCalledWith(
227
+ '$ld:ai:tokens:input',
228
+ testContext,
229
+ { configKey, versionKey },
230
+ PROMPT_TOKENS,
231
+ );
232
+
233
+ expect(mockTrack).toHaveBeenCalledWith(
234
+ '$ld:ai:tokens:output',
235
+ testContext,
236
+ { configKey, versionKey },
237
+ COMPLETION_TOKENS,
238
+ );
239
+ });
240
+
241
+ it('only tracks non-zero token counts', () => {
242
+ const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, versionKey, testContext);
243
+
244
+ tracker.trackTokens({
245
+ total: 0,
246
+ input: 50,
247
+ output: 0,
248
+ });
249
+
250
+ expect(mockTrack).not.toHaveBeenCalledWith(
251
+ '$ld:ai:tokens:total',
252
+ expect.anything(),
253
+ expect.anything(),
254
+ expect.anything(),
255
+ );
256
+
257
+ expect(mockTrack).toHaveBeenCalledWith(
258
+ '$ld:ai:tokens:input',
259
+ testContext,
260
+ { configKey, versionKey },
261
+ 50,
262
+ );
263
+
264
+ expect(mockTrack).not.toHaveBeenCalledWith(
265
+ '$ld:ai:tokens:output',
266
+ expect.anything(),
267
+ expect.anything(),
268
+ expect.anything(),
269
+ );
270
+ });
@@ -0,0 +1,78 @@
1
+ import { createBedrockTokenUsage } from '../src/api/metrics/BedrockTokenUsage';
2
+ import { createOpenAiUsage } from '../src/api/metrics/OpenAiUsage';
3
+
4
+ it('createBedrockTokenUsage should create token usage with all values provided', () => {
5
+ const usage = createBedrockTokenUsage({
6
+ totalTokens: 100,
7
+ inputTokens: 40,
8
+ outputTokens: 60,
9
+ });
10
+
11
+ expect(usage).toEqual({
12
+ total: 100,
13
+ input: 40,
14
+ output: 60,
15
+ });
16
+ });
17
+
18
+ it('createBedrockTokenUsage should default to 0 for missing values', () => {
19
+ const usage = createBedrockTokenUsage({});
20
+
21
+ expect(usage).toEqual({
22
+ total: 0,
23
+ input: 0,
24
+ output: 0,
25
+ });
26
+ });
27
+
28
+ it('createBedrockTokenUsage should handle explicitly undefined values', () => {
29
+ const usage = createBedrockTokenUsage({
30
+ totalTokens: undefined,
31
+ inputTokens: 40,
32
+ outputTokens: undefined,
33
+ });
34
+
35
+ expect(usage).toEqual({
36
+ total: 0,
37
+ input: 40,
38
+ output: 0,
39
+ });
40
+ });
41
+
42
+ it('createOpenAiUsage should create token usage with all values provided', () => {
43
+ const usage = createOpenAiUsage({
44
+ total_tokens: 100,
45
+ prompt_tokens: 40,
46
+ completion_tokens: 60,
47
+ });
48
+
49
+ expect(usage).toEqual({
50
+ total: 100,
51
+ input: 40,
52
+ output: 60,
53
+ });
54
+ });
55
+
56
+ it('createOpenAiUsage should default to 0 for missing values', () => {
57
+ const usage = createOpenAiUsage({});
58
+
59
+ expect(usage).toEqual({
60
+ total: 0,
61
+ input: 0,
62
+ output: 0,
63
+ });
64
+ });
65
+
66
+ it('createOpenAiUsage should handle explicitly undefined values', () => {
67
+ const usage = createOpenAiUsage({
68
+ total_tokens: undefined,
69
+ prompt_tokens: 40,
70
+ completion_tokens: undefined,
71
+ });
72
+
73
+ expect(usage).toEqual({
74
+ total: 0,
75
+ input: 40,
76
+ output: 0,
77
+ });
78
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"LDAIClientImpl.d.ts","sourceRoot":"","sources":["../src/LDAIClientImpl.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAA4B,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAoB5C,qBAAa,cAAe,YAAW,UAAU;IACnC,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,WAAW;IAE1C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAI3E,WAAW,CAAC,QAAQ,SAAS,kBAAkB,EACnD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,YAAY,EAAE,QAAQ,EACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC;CA4BvB"}
1
+ {"version":3,"file":"LDAIClientImpl.d.ts","sourceRoot":"","sources":["../src/LDAIClientImpl.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAA4B,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAoB5C,qBAAa,cAAe,YAAW,UAAU;IACnC,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,WAAW;IAE1C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAI3E,WAAW,CAAC,QAAQ,SAAS,kBAAkB,EACnD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,YAAY,EAAE,QAAQ,EACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC;CA+BvB"}
@@ -15,7 +15,10 @@ class LDAIClientImpl {
15
15
  const value = await this._ldClient.variation(key, context, defaultValue);
16
16
  // We are going to modify the contents before returning them, so we make a copy.
17
17
  // This isn't a deep copy and the application developer should not modify the returned content.
18
- const config = Object.assign({}, value);
18
+ const config = {};
19
+ if (value.model) {
20
+ config.model = Object.assign({}, value.model);
21
+ }
19
22
  const allVariables = Object.assign(Object.assign({}, variables), { ldctx: context });
20
23
  if (value.prompt) {
21
24
  config.prompt = value.prompt.map((entry) => (Object.assign(Object.assign({}, entry), { content: this.interpolateTemplate(entry.content, allVariables) })));
@@ -1 +1 @@
1
- {"version":3,"file":"LDAIClientImpl.js","sourceRoot":"","sources":["../src/LDAIClientImpl.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAMrC,mEAAgE;AAqBhE,MAAa,cAAc;IACzB,YAAoB,SAAsB;QAAtB,cAAS,GAAT,SAAS,CAAa;IAAG,CAAC;IAE9C,mBAAmB,CAAC,QAAgB,EAAE,SAAkC;QACtE,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,WAAW,CACf,GAAW,EACX,OAAkB,EAClB,YAAsB,EACtB,SAAmC;;QAEnC,MAAM,KAAK,GAAqB,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QAC3F,gFAAgF;QAChF,+FAA+F;QAC/F,MAAM,MAAM,qBAA4B,KAAK,CAAE,CAAC;QAChD,MAAM,YAAY,mCAAQ,SAAS,KAAE,KAAK,EAAE,OAAO,GAAE,CAAC;QAEtD,IAAI,KAAK,CAAC,MAAM,EAAE;YAChB,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,iCAC5C,KAAK,KACR,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC,IAC9D,CAAC,CAAC;SACL;QAED,OAAO;YACL,MAAM;YACN,gDAAgD;YAChD,OAAO,EAAE,IAAI,6CAAqB,CAChC,IAAI,CAAC,SAAS,EACd,GAAG;YACH,gDAAgD;YAChD,MAAA,MAAA,KAAK,CAAC,OAAO,0CAAE,UAAU,mCAAI,EAAE,EAC/B,OAAO,CACR;YACD,gDAAgD;YAChD,OAAO,EAAE,CAAC,CAAC,CAAA,MAAA,KAAK,CAAC,OAAO,0CAAE,OAAO,CAAA;SAClC,CAAC;IACJ,CAAC;CACF;AAxCD,wCAwCC"}
1
+ {"version":3,"file":"LDAIClientImpl.js","sourceRoot":"","sources":["../src/LDAIClientImpl.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAMrC,mEAAgE;AAqBhE,MAAa,cAAc;IACzB,YAAoB,SAAsB;QAAtB,cAAS,GAAT,SAAS,CAAa;IAAG,CAAC;IAE9C,mBAAmB,CAAC,QAAgB,EAAE,SAAkC;QACtE,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,WAAW,CACf,GAAW,EACX,OAAkB,EAClB,YAAsB,EACtB,SAAmC;;QAEnC,MAAM,KAAK,GAAqB,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QAC3F,gFAAgF;QAChF,+FAA+F;QAC/F,MAAM,MAAM,GAAuB,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,KAAK,EAAE;YACf,MAAM,CAAC,KAAK,qBAAQ,KAAK,CAAC,KAAK,CAAE,CAAC;SACnC;QACD,MAAM,YAAY,mCAAQ,SAAS,KAAE,KAAK,EAAE,OAAO,GAAE,CAAC;QAEtD,IAAI,KAAK,CAAC,MAAM,EAAE;YAChB,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,iCAC5C,KAAK,KACR,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC,IAC9D,CAAC,CAAC;SACL;QAED,OAAO;YACL,MAAM;YACN,gDAAgD;YAChD,OAAO,EAAE,IAAI,6CAAqB,CAChC,IAAI,CAAC,SAAS,EACd,GAAG;YACH,gDAAgD;YAChD,MAAA,MAAA,KAAK,CAAC,OAAO,0CAAE,UAAU,mCAAI,EAAE,EAC/B,OAAO,CACR;YACD,gDAAgD;YAChD,OAAO,EAAE,CAAC,CAAC,CAAA,MAAA,KAAK,CAAC,OAAO,0CAAE,OAAO,CAAA;SAClC,CAAC;IACJ,CAAC;CACF;AA3CD,wCA2CC"}
@@ -1,5 +1,14 @@
1
1
  import { LDContext } from '@launchdarkly/js-server-sdk-common';
2
2
  import { LDAIConfig, LDGenerationConfig } from './config/LDAIConfig';
3
+ /**
4
+ * Interface for default model configuration.
5
+ */
6
+ export interface LDAIDefaults extends LDGenerationConfig {
7
+ /**
8
+ * Whether the configuration is enabled.
9
+ */
10
+ enabled?: boolean;
11
+ }
3
12
  /**
4
13
  * Interface for performing AI operations using LaunchDarkly.
5
14
  */
@@ -7,43 +16,49 @@ export interface LDAIClient {
7
16
  /**
8
17
  * Parses and interpolates a template string with the provided variables.
9
18
  *
10
- * @param template - The template string to be parsed and interpolated.
11
- * @param variables - An object containing the variables to be used for interpolation.
19
+ * @param template The template string to be parsed and interpolated.
20
+ * @param variables An object containing the variables to be used for interpolation.
12
21
  * @returns The interpolated string.
13
22
  */
14
23
  interpolateTemplate(template: string, variables: Record<string, unknown>): string;
15
24
  /**
16
- * Retrieves and processes a prompt template based on the provided key, LaunchDarkly context, and
17
- * variables.
25
+ * Retrieves and processes an AI configuration based on the provided key, LaunchDarkly context,
26
+ * and variables. This includes the model configuration and the processed prompts.
18
27
  *
19
- * @param key - A unique identifier for the prompt template. This key is used to fetch the correct
20
- * prompt from storage or configuration.
21
- * @param context - The LaunchDarkly context object that contains relevant information about the
22
- * current environment, user, or session. This context may influence how the prompt is processed
23
- * or personalized.
24
- * @param variables - A map of key-value pairs representing dynamic variables to be injected into
28
+ * @param key The key of the AI configuration.
29
+ * @param context The LaunchDarkly context object that contains relevant information about the
30
+ * current environment, user, or session. This context may influence how the configuration is
31
+ * processed or personalized.
32
+ * @param variables A map of key-value pairs representing dynamic variables to be injected into
25
33
  * the prompt template. The keys correspond to placeholders within the template, and the values
26
34
  * are the corresponding replacements.
27
- * @param defaultValue - A fallback value to be used if the prompt template associated with the
28
- * key is not found or if any errors occur during processing.
35
+ * @param defaultValue A fallback value containing model configuration and prompts. This will
36
+ * be used if the configurationuration is not available from launchdarkly.
29
37
  *
30
- * @returns The processed prompt after all variables have been substituted in the stored prompt
31
- * template. If the prompt cannot be retrieved or processed, the `defaultValue` is returned.
38
+ * @returns The AI configurationuration including a processed prompt after all variables have been
39
+ * substituted in the stored prompt template. This will also include a `tracker` used to track
40
+ * the state of the AI operation. If the configuration cannot be accessed from LaunchDarkly, then
41
+ * the return value will include information from the defaultValue.
32
42
  *
33
43
  * @example
34
44
  * ```
35
45
  * const key = "welcome_prompt";
36
46
  * const context = {...};
37
47
  * const variables = {username: 'john'};
38
- * const defaultValue = {};
48
+ * const defaultValue = {
49
+ * enabled: false,
50
+ * };
39
51
  *
40
52
  * const result = modelConfig(key, context, defaultValue, variables);
41
53
  * // Output:
42
54
  * {
43
- * modelId: "gpt-4o",
44
- * temperature: 0.2,
45
- * maxTokens: 4096,
46
- * userDefinedKey: "myValue",
55
+ * enabled: true,
56
+ * config: {
57
+ * modelId: "gpt-4o",
58
+ * temperature: 0.2,
59
+ * maxTokens: 4096,
60
+ * userDefinedKey: "myValue",
61
+ * },
47
62
  * prompt: [
48
63
  * {
49
64
  * role: "system",
@@ -53,10 +68,11 @@ export interface LDAIClient {
53
68
  * role: "user",
54
69
  * content: "Explain how you're an amazing GPT."
55
70
  * }
56
- * ]
71
+ * ],
72
+ * tracker: ...
57
73
  * }
58
74
  * ```
59
75
  */
60
- modelConfig<TDefault extends LDGenerationConfig>(key: string, context: LDContext, defaultValue: TDefault, variables?: Record<string, unknown>): Promise<LDAIConfig>;
76
+ modelConfig<TDefault extends LDAIDefaults>(key: string, context: LDContext, defaultValue: TDefault, variables?: Record<string, unknown>): Promise<LDAIConfig>;
61
77
  }
62
78
  //# sourceMappingURL=LDAIClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LDAIClient.d.ts","sourceRoot":"","sources":["../../src/api/LDAIClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAErE;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CG;IACH,WAAW,CAAC,QAAQ,SAAS,kBAAkB,EAC7C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,YAAY,EAAE,QAAQ,EACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC,CAAC;CACxB"}
1
+ {"version":3,"file":"LDAIClient.d.ts","sourceRoot":"","sources":["../../src/api/LDAIClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;IACH,WAAW,CAAC,QAAQ,SAAS,YAAY,EACvC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,YAAY,EAAE,QAAQ,EACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC,CAAC;CACxB"}