@launchdarkly/server-sdk-ai 0.14.1 → 0.15.1

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