@launchdarkly/server-sdk-ai 0.14.0 → 0.15.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 (197) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.cjs +1117 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +1022 -0
  5. package/dist/index.d.ts +1022 -0
  6. package/dist/index.js +1071 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +22 -5
  9. package/__tests__/Judge.test.ts +0 -496
  10. package/__tests__/LDAIClientImpl.test.ts +0 -589
  11. package/__tests__/LDAIConfigTrackerImpl.test.ts +0 -815
  12. package/__tests__/TokenUsage.test.ts +0 -119
  13. package/__tests__/TrackedChat.test.ts +0 -230
  14. package/dist/package.json +0 -53
  15. package/dist/src/LDAIClientImpl.d.ts +0 -39
  16. package/dist/src/LDAIClientImpl.d.ts.map +0 -1
  17. package/dist/src/LDAIClientImpl.js +0 -164
  18. package/dist/src/LDAIClientImpl.js.map +0 -1
  19. package/dist/src/LDAIConfigTrackerImpl.d.ts +0 -73
  20. package/dist/src/LDAIConfigTrackerImpl.d.ts.map +0 -1
  21. package/dist/src/LDAIConfigTrackerImpl.js +0 -203
  22. package/dist/src/LDAIConfigTrackerImpl.js.map +0 -1
  23. package/dist/src/LDClientMin.d.ts +0 -11
  24. package/dist/src/LDClientMin.d.ts.map +0 -1
  25. package/dist/src/LDClientMin.js +0 -3
  26. package/dist/src/LDClientMin.js.map +0 -1
  27. package/dist/src/api/LDAIClient.d.ts +0 -258
  28. package/dist/src/api/LDAIClient.d.ts.map +0 -1
  29. package/dist/src/api/LDAIClient.js +0 -3
  30. package/dist/src/api/LDAIClient.js.map +0 -1
  31. package/dist/src/api/chat/TrackedChat.d.ts +0 -72
  32. package/dist/src/api/chat/TrackedChat.d.ts.map +0 -1
  33. package/dist/src/api/chat/TrackedChat.js +0 -125
  34. package/dist/src/api/chat/TrackedChat.js.map +0 -1
  35. package/dist/src/api/chat/index.d.ts +0 -3
  36. package/dist/src/api/chat/index.d.ts.map +0 -1
  37. package/dist/src/api/chat/index.js +0 -19
  38. package/dist/src/api/chat/index.js.map +0 -1
  39. package/dist/src/api/chat/types.d.ts +0 -22
  40. package/dist/src/api/chat/types.d.ts.map +0 -1
  41. package/dist/src/api/chat/types.js +0 -3
  42. package/dist/src/api/chat/types.js.map +0 -1
  43. package/dist/src/api/config/LDAIConfigTracker.d.ts +0 -203
  44. package/dist/src/api/config/LDAIConfigTracker.d.ts.map +0 -1
  45. package/dist/src/api/config/LDAIConfigTracker.js +0 -3
  46. package/dist/src/api/config/LDAIConfigTracker.js.map +0 -1
  47. package/dist/src/api/config/LDAIConfigUtils.d.ts +0 -2
  48. package/dist/src/api/config/LDAIConfigUtils.d.ts.map +0 -1
  49. package/dist/src/api/config/LDAIConfigUtils.js +0 -141
  50. package/dist/src/api/config/LDAIConfigUtils.js.map +0 -1
  51. package/dist/src/api/config/index.d.ts +0 -3
  52. package/dist/src/api/config/index.d.ts.map +0 -1
  53. package/dist/src/api/config/index.js +0 -18
  54. package/dist/src/api/config/index.js.map +0 -1
  55. package/dist/src/api/config/types.d.ts +0 -202
  56. package/dist/src/api/config/types.d.ts.map +0 -1
  57. package/dist/src/api/config/types.js +0 -3
  58. package/dist/src/api/config/types.js.map +0 -1
  59. package/dist/src/api/index.d.ts +0 -7
  60. package/dist/src/api/index.d.ts.map +0 -1
  61. package/dist/src/api/index.js +0 -23
  62. package/dist/src/api/index.js.map +0 -1
  63. package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts +0 -11
  64. package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts.map +0 -1
  65. package/dist/src/api/judge/EvaluationSchemaBuilder.js +0 -52
  66. package/dist/src/api/judge/EvaluationSchemaBuilder.js.map +0 -1
  67. package/dist/src/api/judge/Judge.d.ts +0 -63
  68. package/dist/src/api/judge/Judge.d.ts.map +0 -1
  69. package/dist/src/api/judge/Judge.js +0 -149
  70. package/dist/src/api/judge/Judge.js.map +0 -1
  71. package/dist/src/api/judge/index.d.ts +0 -3
  72. package/dist/src/api/judge/index.d.ts.map +0 -1
  73. package/dist/src/api/judge/index.js +0 -6
  74. package/dist/src/api/judge/index.js.map +0 -1
  75. package/dist/src/api/judge/types.d.ts +0 -35
  76. package/dist/src/api/judge/types.d.ts.map +0 -1
  77. package/dist/src/api/judge/types.js +0 -3
  78. package/dist/src/api/judge/types.js.map +0 -1
  79. package/dist/src/api/metrics/BedrockTokenUsage.d.ts +0 -7
  80. package/dist/src/api/metrics/BedrockTokenUsage.d.ts.map +0 -1
  81. package/dist/src/api/metrics/BedrockTokenUsage.js +0 -12
  82. package/dist/src/api/metrics/BedrockTokenUsage.js.map +0 -1
  83. package/dist/src/api/metrics/LDAIMetrics.d.ts +0 -17
  84. package/dist/src/api/metrics/LDAIMetrics.d.ts.map +0 -1
  85. package/dist/src/api/metrics/LDAIMetrics.js +0 -3
  86. package/dist/src/api/metrics/LDAIMetrics.js.map +0 -1
  87. package/dist/src/api/metrics/LDFeedbackKind.d.ts +0 -14
  88. package/dist/src/api/metrics/LDFeedbackKind.d.ts.map +0 -1
  89. package/dist/src/api/metrics/LDFeedbackKind.js +0 -18
  90. package/dist/src/api/metrics/LDFeedbackKind.js.map +0 -1
  91. package/dist/src/api/metrics/LDTokenUsage.d.ts +0 -18
  92. package/dist/src/api/metrics/LDTokenUsage.d.ts.map +0 -1
  93. package/dist/src/api/metrics/LDTokenUsage.js +0 -3
  94. package/dist/src/api/metrics/LDTokenUsage.js.map +0 -1
  95. package/dist/src/api/metrics/OpenAiUsage.d.ts +0 -7
  96. package/dist/src/api/metrics/OpenAiUsage.d.ts.map +0 -1
  97. package/dist/src/api/metrics/OpenAiUsage.js +0 -13
  98. package/dist/src/api/metrics/OpenAiUsage.js.map +0 -1
  99. package/dist/src/api/metrics/VercelAISDKTokenUsage.d.ts +0 -9
  100. package/dist/src/api/metrics/VercelAISDKTokenUsage.d.ts.map +0 -1
  101. package/dist/src/api/metrics/VercelAISDKTokenUsage.js +0 -13
  102. package/dist/src/api/metrics/VercelAISDKTokenUsage.js.map +0 -1
  103. package/dist/src/api/metrics/index.d.ts +0 -7
  104. package/dist/src/api/metrics/index.d.ts.map +0 -1
  105. package/dist/src/api/metrics/index.js +0 -23
  106. package/dist/src/api/metrics/index.js.map +0 -1
  107. package/dist/src/api/providers/AIProvider.d.ts +0 -52
  108. package/dist/src/api/providers/AIProvider.d.ts.map +0 -1
  109. package/dist/src/api/providers/AIProvider.js +0 -88
  110. package/dist/src/api/providers/AIProvider.js.map +0 -1
  111. package/dist/src/api/providers/AIProviderFactory.d.ts +0 -39
  112. package/dist/src/api/providers/AIProviderFactory.d.ts.map +0 -1
  113. package/dist/src/api/providers/AIProviderFactory.js +0 -102
  114. package/dist/src/api/providers/AIProviderFactory.js.map +0 -1
  115. package/dist/src/api/providers/index.d.ts +0 -3
  116. package/dist/src/api/providers/index.d.ts.map +0 -1
  117. package/dist/src/api/providers/index.js +0 -19
  118. package/dist/src/api/providers/index.js.map +0 -1
  119. package/dist/src/index.d.ts +0 -19
  120. package/dist/src/index.d.ts.map +0 -1
  121. package/dist/src/index.js +0 -29
  122. package/dist/src/index.js.map +0 -1
  123. package/docs/.nojekyll +0 -1
  124. package/docs/assets/highlight.css +0 -92
  125. package/docs/assets/main.js +0 -58
  126. package/docs/assets/search.js +0 -1
  127. package/docs/assets/style.css +0 -1379
  128. package/docs/classes/AIProvider.html +0 -210
  129. package/docs/classes/AIProviderFactory.html +0 -208
  130. package/docs/classes/Judge.html +0 -322
  131. package/docs/classes/TrackedChat.html +0 -322
  132. package/docs/enums/LDFeedbackKind.html +0 -115
  133. package/docs/functions/createBedrockTokenUsage.html +0 -94
  134. package/docs/functions/createOpenAiUsage.html +0 -94
  135. package/docs/functions/createVercelAISDKTokenUsage.html +0 -98
  136. package/docs/functions/initAi.html +0 -93
  137. package/docs/index.html +0 -136
  138. package/docs/interfaces/ChatResponse.html +0 -130
  139. package/docs/interfaces/EvalScore.html +0 -119
  140. package/docs/interfaces/JudgeResponse.html +0 -129
  141. package/docs/interfaces/LDAIAgentConfig.html +0 -167
  142. package/docs/interfaces/LDAIAgentConfigDefault.html +0 -155
  143. package/docs/interfaces/LDAIAgentRequestConfig.html +0 -129
  144. package/docs/interfaces/LDAIClient.html +0 -449
  145. package/docs/interfaces/LDAICompletionConfig.html +0 -167
  146. package/docs/interfaces/LDAICompletionConfigDefault.html +0 -155
  147. package/docs/interfaces/LDAIConfig.html +0 -148
  148. package/docs/interfaces/LDAIConfigDefault.html +0 -133
  149. package/docs/interfaces/LDAIConfigTracker.html +0 -510
  150. package/docs/interfaces/LDAIJudgeConfig.html +0 -167
  151. package/docs/interfaces/LDAIJudgeConfigDefault.html +0 -155
  152. package/docs/interfaces/LDAIMetrics.html +0 -121
  153. package/docs/interfaces/LDJudge.html +0 -119
  154. package/docs/interfaces/LDJudgeConfiguration.html +0 -109
  155. package/docs/interfaces/LDLogger.html +0 -189
  156. package/docs/interfaces/LDMessage.html +0 -119
  157. package/docs/interfaces/LDModelConfig.html +0 -139
  158. package/docs/interfaces/LDProviderConfig.html +0 -105
  159. package/docs/interfaces/LDTokenUsage.html +0 -129
  160. package/docs/interfaces/StructuredResponse.html +0 -129
  161. package/docs/types/LDAIConfigDefaultKind.html +0 -81
  162. package/docs/types/LDAIConfigKind.html +0 -81
  163. package/docs/types/LDAIConfigMode.html +0 -81
  164. package/docs/types/SupportedAIProvider.html +0 -81
  165. package/docs/variables/SUPPORTED_AI_PROVIDERS.html +0 -81
  166. package/jest.config.js +0 -7
  167. package/src/LDAIClientImpl.ts +0 -327
  168. package/src/LDAIConfigTrackerImpl.ts +0 -278
  169. package/src/LDClientMin.ts +0 -18
  170. package/src/api/LDAIClient.ts +0 -325
  171. package/src/api/chat/TrackedChat.ts +0 -159
  172. package/src/api/chat/index.ts +0 -2
  173. package/src/api/chat/types.ts +0 -24
  174. package/src/api/config/LDAIConfigTracker.ts +0 -231
  175. package/src/api/config/LDAIConfigUtils.ts +0 -201
  176. package/src/api/config/index.ts +0 -3
  177. package/src/api/config/types.ts +0 -256
  178. package/src/api/index.ts +0 -6
  179. package/src/api/judge/EvaluationSchemaBuilder.ts +0 -54
  180. package/src/api/judge/Judge.ts +0 -216
  181. package/src/api/judge/index.ts +0 -2
  182. package/src/api/judge/types.ts +0 -39
  183. package/src/api/metrics/BedrockTokenUsage.ts +0 -13
  184. package/src/api/metrics/LDAIMetrics.ts +0 -18
  185. package/src/api/metrics/LDFeedbackKind.ts +0 -13
  186. package/src/api/metrics/LDTokenUsage.ts +0 -19
  187. package/src/api/metrics/OpenAiUsage.ts +0 -13
  188. package/src/api/metrics/VercelAISDKTokenUsage.ts +0 -15
  189. package/src/api/metrics/index.ts +0 -6
  190. package/src/api/providers/AIProvider.ts +0 -94
  191. package/src/api/providers/AIProviderFactory.ts +0 -152
  192. package/src/api/providers/index.ts +0 -2
  193. package/src/index.ts +0 -24
  194. package/tsconfig.eslint.json +0 -5
  195. package/tsconfig.json +0 -21
  196. package/tsconfig.ref.json +0 -7
  197. package/typedoc.json +0 -5
@@ -1,496 +0,0 @@
1
- import { LDLogger } from '@launchdarkly/js-server-sdk-common';
2
-
3
- import { LDAIConfigTracker } from '../src/api/config/LDAIConfigTracker';
4
- import { LDAIJudgeConfig, LDMessage } from '../src/api/config/types';
5
- import { Judge } from '../src/api/judge/Judge';
6
- import { StructuredResponse } from '../src/api/judge/types';
7
- import { AIProvider } from '../src/api/providers/AIProvider';
8
-
9
- describe('Judge', () => {
10
- let mockProvider: jest.Mocked<AIProvider>;
11
- let mockTracker: jest.Mocked<LDAIConfigTracker>;
12
- let mockLogger: jest.Mocked<LDLogger>;
13
- let judgeConfig: LDAIJudgeConfig;
14
-
15
- const mockTrackData = {
16
- variationKey: 'test-variation',
17
- configKey: 'test-config',
18
- version: 1,
19
- };
20
-
21
- beforeEach(() => {
22
- // Mock the AIProvider - only mock what's actually used
23
- mockProvider = {
24
- invokeStructuredModel: jest.fn(),
25
- } as any;
26
-
27
- // Mock the LDAIConfigTracker - only mock what's actually used
28
- mockTracker = {
29
- trackMetricsOf: jest.fn(),
30
- getTrackData: jest.fn().mockReturnValue(mockTrackData),
31
- } as any;
32
-
33
- // Mock the logger - only mock what's actually used
34
- mockLogger = {
35
- debug: jest.fn(),
36
- warn: jest.fn(),
37
- error: jest.fn(),
38
- } as any;
39
-
40
- // Create a basic judge config
41
- judgeConfig = {
42
- enabled: true,
43
- messages: [
44
- { role: 'system', content: 'You are a helpful judge that evaluates AI responses.' },
45
- {
46
- role: 'user',
47
- content:
48
- 'Evaluate and report scores for important metrics: Input: {{message_history}}, Output: {{response_to_evaluate}}',
49
- },
50
- ],
51
- model: { name: 'gpt-4' },
52
- provider: { name: 'openai' },
53
- tracker: mockTracker,
54
- evaluationMetricKeys: ['relevance', 'accuracy', 'helpfulness'],
55
- };
56
- });
57
-
58
- describe('constructor', () => {
59
- it('initializes with proper configuration', () => {
60
- const judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
61
-
62
- expect(judge).toBeDefined();
63
- });
64
- });
65
-
66
- describe('evaluate', () => {
67
- let judge: Judge;
68
-
69
- beforeEach(() => {
70
- judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
71
- });
72
-
73
- it('evaluates AI response successfully', async () => {
74
- const mockStructuredResponse: StructuredResponse = {
75
- data: {
76
- evaluations: {
77
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
78
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
79
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
80
- },
81
- },
82
- rawResponse: JSON.stringify({
83
- evaluations: {
84
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
85
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
86
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
87
- },
88
- }),
89
- metrics: {
90
- success: true,
91
- usage: {
92
- total: 100,
93
- input: 50,
94
- output: 50,
95
- },
96
- },
97
- };
98
-
99
- mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
100
- mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
101
-
102
- const result = await judge.evaluate(
103
- 'What is the capital of France?',
104
- 'Paris is the capital of France.',
105
- );
106
-
107
- expect(result).toEqual({
108
- evals: {
109
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
110
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
111
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
112
- },
113
- success: true,
114
- });
115
-
116
- expect(mockProvider.invokeStructuredModel).toHaveBeenCalledWith(
117
- expect.arrayContaining([
118
- expect.objectContaining({
119
- role: 'system',
120
- content: 'You are a helpful judge that evaluates AI responses.',
121
- }),
122
- expect.objectContaining({
123
- role: 'user',
124
- content:
125
- 'Evaluate and report scores for important metrics: Input: What is the capital of France?, Output: Paris is the capital of France.',
126
- }),
127
- ]),
128
- expect.any(Object), // evaluation response structure
129
- );
130
- });
131
-
132
- it('handles sampling rate correctly', async () => {
133
- // Mock Math.random to return 0.3 (should be sampled with rate 0.5 since 0.3 <= 0.5)
134
- const originalRandom = Math.random;
135
- Math.random = jest.fn().mockReturnValue(0.3);
136
-
137
- // Mock the structured response
138
- const mockStructuredResponse: StructuredResponse = {
139
- data: {
140
- evaluations: {
141
- relevance: { score: 0.8, reasoning: 'Good' },
142
- accuracy: { score: 0.9, reasoning: 'Accurate' },
143
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
144
- },
145
- },
146
- rawResponse: JSON.stringify({
147
- evaluations: {
148
- relevance: { score: 0.8, reasoning: 'Good' },
149
- accuracy: { score: 0.9, reasoning: 'Accurate' },
150
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
151
- },
152
- }),
153
- metrics: {
154
- success: true,
155
- usage: { total: 100, input: 50, output: 50 },
156
- },
157
- };
158
-
159
- mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
160
- mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
161
-
162
- const result = await judge.evaluate('test input', 'test output', 0.5);
163
-
164
- expect(result).toBeDefined();
165
- expect(mockProvider.invokeStructuredModel).toHaveBeenCalled();
166
-
167
- Math.random = originalRandom;
168
- });
169
-
170
- it('returns undefined when not sampled', async () => {
171
- // Mock Math.random to return 0.8 (should not be sampled with rate 0.5 since 0.8 > 0.5)
172
- const originalRandom = Math.random;
173
- Math.random = jest.fn().mockReturnValue(0.8);
174
-
175
- const result = await judge.evaluate('test input', 'test output', 0.5);
176
-
177
- expect(result).toBeUndefined();
178
- expect(mockProvider.invokeStructuredModel).not.toHaveBeenCalled();
179
- expect(mockLogger.debug).toHaveBeenCalledWith(
180
- 'Judge evaluation skipped due to sampling rate: 0.5',
181
- );
182
-
183
- Math.random = originalRandom;
184
- });
185
-
186
- it('returns undefined when evaluationMetricKeys is empty', async () => {
187
- const configWithoutMetrics: LDAIJudgeConfig = {
188
- ...judgeConfig,
189
- evaluationMetricKeys: [],
190
- };
191
- const judgeWithoutMetrics = new Judge(
192
- configWithoutMetrics,
193
- mockTracker,
194
- mockProvider,
195
- mockLogger,
196
- );
197
-
198
- const result = await judgeWithoutMetrics.evaluate('test input', 'test output');
199
-
200
- expect(result).toBeUndefined();
201
- expect(mockLogger.warn).toHaveBeenCalledWith(
202
- 'Judge configuration is missing required evaluationMetricKeys',
203
- mockTrackData,
204
- );
205
- });
206
-
207
- it('returns undefined when messages are missing', async () => {
208
- const configWithoutMessages: LDAIJudgeConfig = {
209
- ...judgeConfig,
210
- messages: undefined,
211
- };
212
- const judgeWithoutMessages = new Judge(
213
- configWithoutMessages,
214
- mockTracker,
215
- mockProvider,
216
- mockLogger,
217
- );
218
-
219
- const result = await judgeWithoutMessages.evaluate('test input', 'test output');
220
-
221
- expect(result).toBeUndefined();
222
- expect(mockLogger.warn).toHaveBeenCalledWith(
223
- 'Judge configuration must include messages',
224
- mockTrackData,
225
- );
226
- });
227
-
228
- it('returns partial evaluations when some metrics are missing', async () => {
229
- const mockStructuredResponse: StructuredResponse = {
230
- data: {
231
- evaluations: {
232
- relevance: { score: 0.8, reasoning: 'Good' },
233
- // accuracy is missing
234
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
235
- },
236
- },
237
- rawResponse: JSON.stringify({
238
- evaluations: {
239
- relevance: { score: 0.8, reasoning: 'Good' },
240
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
241
- },
242
- }),
243
- metrics: {
244
- success: true,
245
- usage: { total: 100, input: 50, output: 50 },
246
- },
247
- };
248
-
249
- mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
250
- mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
251
-
252
- const result = await judge.evaluate('test input', 'test output');
253
-
254
- // When one metric is missing, it returns the partial evals it has with success: false
255
- expect(result).toEqual({
256
- evals: {
257
- relevance: { score: 0.8, reasoning: 'Good' },
258
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
259
- },
260
- success: false,
261
- });
262
- });
263
-
264
- it('returns empty evaluations when response structure is malformed', async () => {
265
- const mockStructuredResponse: StructuredResponse = {
266
- data: {
267
- // Missing 'evaluations' wrapper - malformed structure
268
- relevance: { score: 0.8, reasoning: 'Good' },
269
- accuracy: { score: 0.9, reasoning: 'Accurate' },
270
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
271
- },
272
- rawResponse: JSON.stringify({
273
- relevance: { score: 0.8, reasoning: 'Good' },
274
- accuracy: { score: 0.9, reasoning: 'Accurate' },
275
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
276
- }),
277
- metrics: {
278
- success: true,
279
- usage: { total: 100, input: 50, output: 50 },
280
- },
281
- };
282
-
283
- mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
284
- mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
285
-
286
- const result = await judge.evaluate('test input', 'test output');
287
-
288
- // When the structure is completely wrong, returns empty evals with success: false
289
- expect(result).toEqual({
290
- evals: {},
291
- success: false,
292
- });
293
- });
294
-
295
- it('handles provider errors gracefully', async () => {
296
- const error = new Error('Provider error');
297
- mockTracker.trackMetricsOf.mockRejectedValue(error);
298
-
299
- const result = await judge.evaluate('test input', 'test output');
300
-
301
- expect(result).toEqual({
302
- evals: {},
303
- success: false,
304
- error: 'Provider error',
305
- });
306
- expect(mockLogger.error).toHaveBeenCalledWith('Judge evaluation failed:', error);
307
- });
308
-
309
- it('handles non-Error exceptions', async () => {
310
- mockTracker.trackMetricsOf.mockRejectedValue('String error');
311
-
312
- const result = await judge.evaluate('test input', 'test output');
313
-
314
- expect(result).toEqual({
315
- evals: {},
316
- success: false,
317
- error: 'Unknown error',
318
- });
319
- });
320
- });
321
-
322
- describe('evaluateMessages', () => {
323
- let judge: Judge;
324
-
325
- beforeEach(() => {
326
- judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
327
- });
328
-
329
- it('evaluates messages and response successfully', async () => {
330
- const messages: LDMessage[] = [
331
- { role: 'user', content: 'What is the capital of France?' },
332
- { role: 'assistant', content: 'Paris is the capital of France.' },
333
- ];
334
- const response = {
335
- message: { role: 'assistant' as const, content: 'Paris is the capital of France.' },
336
- metrics: { success: true },
337
- };
338
-
339
- const mockStructuredResponse: StructuredResponse = {
340
- data: {
341
- evaluations: {
342
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
343
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
344
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
345
- },
346
- },
347
- rawResponse: JSON.stringify({
348
- evaluations: {
349
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
350
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
351
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
352
- },
353
- }),
354
- metrics: {
355
- success: true,
356
- usage: { total: 100, input: 50, output: 50 },
357
- },
358
- };
359
-
360
- mockTracker.trackMetricsOf.mockImplementation(async (extractor, func) => func());
361
- mockProvider.invokeStructuredModel.mockResolvedValue(mockStructuredResponse);
362
-
363
- const result = await judge.evaluateMessages(messages, response);
364
-
365
- expect(result).toEqual({
366
- evals: {
367
- relevance: { score: 0.8, reasoning: 'The response is relevant to the question' },
368
- accuracy: { score: 0.9, reasoning: 'The response is factually accurate' },
369
- helpfulness: { score: 0.7, reasoning: 'The response provides helpful information' },
370
- },
371
- success: true,
372
- });
373
-
374
- expect(mockProvider.invokeStructuredModel).toHaveBeenCalledWith(
375
- expect.arrayContaining([
376
- expect.objectContaining({
377
- role: 'system',
378
- content: 'You are a helpful judge that evaluates AI responses.',
379
- }),
380
- expect.objectContaining({
381
- role: 'user',
382
- content:
383
- 'Evaluate and report scores for important metrics: Input: What is the capital of France?\r\nParis is the capital of France., Output: Paris is the capital of France.',
384
- }),
385
- ]),
386
- expect.any(Object), // evaluation response structure
387
- );
388
- });
389
-
390
- it('handles sampling rate correctly', async () => {
391
- const messages: LDMessage[] = [{ role: 'user', content: 'test' }];
392
- const response = {
393
- message: { role: 'assistant' as const, content: 'test response' },
394
- metrics: { success: true },
395
- };
396
-
397
- // Mock Math.random to return 0.8 (should not be sampled with rate 0.5 since 0.8 > 0.5)
398
- const originalRandom = Math.random;
399
- Math.random = jest.fn().mockReturnValue(0.8);
400
-
401
- const result = await judge.evaluateMessages(messages, response, 0.5);
402
-
403
- expect(result).toBeUndefined();
404
- expect(mockProvider.invokeStructuredModel).not.toHaveBeenCalled();
405
-
406
- Math.random = originalRandom;
407
- });
408
- });
409
-
410
- describe('_constructEvaluationMessages', () => {
411
- let judge: Judge;
412
-
413
- beforeEach(() => {
414
- judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
415
- });
416
-
417
- it('constructs evaluation messages correctly', () => {
418
- // Access private method for testing
419
- // eslint-disable-next-line no-underscore-dangle
420
- const constructMessages = (judge as any)._constructEvaluationMessages.bind(judge);
421
- const messages = constructMessages('test input', 'test output');
422
-
423
- expect(messages).toHaveLength(2);
424
- expect(messages[0]).toEqual({
425
- role: 'system',
426
- content: 'You are a helpful judge that evaluates AI responses.',
427
- });
428
- expect(messages[1]).toEqual({
429
- role: 'user',
430
- content:
431
- 'Evaluate and report scores for important metrics: Input: test input, Output: test output',
432
- });
433
- });
434
- });
435
-
436
- describe('_parseEvaluationResponse', () => {
437
- let judge: Judge;
438
-
439
- beforeEach(() => {
440
- judge = new Judge(judgeConfig, mockTracker, mockProvider, mockLogger);
441
- });
442
-
443
- it('parses valid evaluation response correctly', () => {
444
- // eslint-disable-next-line no-underscore-dangle
445
- const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
446
- const responseData = {
447
- evaluations: {
448
- relevance: { score: 0.8, reasoning: 'Good' },
449
- accuracy: { score: 0.9, reasoning: 'Accurate' },
450
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
451
- },
452
- };
453
-
454
- const result = parseResponse(responseData);
455
-
456
- expect(result).toEqual({
457
- relevance: { score: 0.8, reasoning: 'Good' },
458
- accuracy: { score: 0.9, reasoning: 'Accurate' },
459
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
460
- });
461
- });
462
-
463
- it('returns empty object for invalid response data', () => {
464
- // eslint-disable-next-line no-underscore-dangle
465
- const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
466
- const responseData = {
467
- relevance: { score: 0.8, reasoning: 'Good' },
468
- // Missing evaluations wrapper - invalid structure
469
- };
470
-
471
- const result = parseResponse(responseData);
472
-
473
- // Returns empty object when evaluations structure is missing
474
- expect(result).toEqual({});
475
- });
476
-
477
- it('handles missing score or reasoning fields', () => {
478
- // eslint-disable-next-line no-underscore-dangle
479
- const parseResponse = (judge as any)._parseEvaluationResponse.bind(judge);
480
- const responseData = {
481
- evaluations: {
482
- relevance: { score: 0.8 }, // Missing reasoning
483
- accuracy: { reasoning: 'Accurate' }, // Missing score
484
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
485
- },
486
- };
487
-
488
- const result = parseResponse(responseData);
489
-
490
- // Only helpfulness passes validation, relevance and accuracy are skipped
491
- expect(result).toEqual({
492
- helpfulness: { score: 0.7, reasoning: 'Helpful' },
493
- });
494
- });
495
- });
496
- });