@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.
- package/CHANGELOG.md +18 -0
- package/dist/index.cjs +1117 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1022 -0
- package/dist/index.d.ts +1022 -0
- package/dist/index.js +1071 -0
- package/dist/index.js.map +1 -0
- package/package.json +22 -5
- package/__tests__/Judge.test.ts +0 -496
- package/__tests__/LDAIClientImpl.test.ts +0 -589
- package/__tests__/LDAIConfigTrackerImpl.test.ts +0 -815
- package/__tests__/TokenUsage.test.ts +0 -119
- package/__tests__/TrackedChat.test.ts +0 -230
- package/dist/package.json +0 -53
- package/dist/src/LDAIClientImpl.d.ts +0 -39
- package/dist/src/LDAIClientImpl.d.ts.map +0 -1
- package/dist/src/LDAIClientImpl.js +0 -164
- package/dist/src/LDAIClientImpl.js.map +0 -1
- package/dist/src/LDAIConfigTrackerImpl.d.ts +0 -73
- package/dist/src/LDAIConfigTrackerImpl.d.ts.map +0 -1
- package/dist/src/LDAIConfigTrackerImpl.js +0 -203
- package/dist/src/LDAIConfigTrackerImpl.js.map +0 -1
- package/dist/src/LDClientMin.d.ts +0 -11
- package/dist/src/LDClientMin.d.ts.map +0 -1
- package/dist/src/LDClientMin.js +0 -3
- package/dist/src/LDClientMin.js.map +0 -1
- package/dist/src/api/LDAIClient.d.ts +0 -258
- package/dist/src/api/LDAIClient.d.ts.map +0 -1
- package/dist/src/api/LDAIClient.js +0 -3
- package/dist/src/api/LDAIClient.js.map +0 -1
- package/dist/src/api/chat/TrackedChat.d.ts +0 -72
- package/dist/src/api/chat/TrackedChat.d.ts.map +0 -1
- package/dist/src/api/chat/TrackedChat.js +0 -125
- package/dist/src/api/chat/TrackedChat.js.map +0 -1
- package/dist/src/api/chat/index.d.ts +0 -3
- package/dist/src/api/chat/index.d.ts.map +0 -1
- package/dist/src/api/chat/index.js +0 -19
- package/dist/src/api/chat/index.js.map +0 -1
- package/dist/src/api/chat/types.d.ts +0 -22
- package/dist/src/api/chat/types.d.ts.map +0 -1
- package/dist/src/api/chat/types.js +0 -3
- package/dist/src/api/chat/types.js.map +0 -1
- package/dist/src/api/config/LDAIConfigTracker.d.ts +0 -203
- package/dist/src/api/config/LDAIConfigTracker.d.ts.map +0 -1
- package/dist/src/api/config/LDAIConfigTracker.js +0 -3
- package/dist/src/api/config/LDAIConfigTracker.js.map +0 -1
- package/dist/src/api/config/LDAIConfigUtils.d.ts +0 -2
- package/dist/src/api/config/LDAIConfigUtils.d.ts.map +0 -1
- package/dist/src/api/config/LDAIConfigUtils.js +0 -141
- package/dist/src/api/config/LDAIConfigUtils.js.map +0 -1
- package/dist/src/api/config/index.d.ts +0 -3
- package/dist/src/api/config/index.d.ts.map +0 -1
- package/dist/src/api/config/index.js +0 -18
- package/dist/src/api/config/index.js.map +0 -1
- package/dist/src/api/config/types.d.ts +0 -202
- package/dist/src/api/config/types.d.ts.map +0 -1
- package/dist/src/api/config/types.js +0 -3
- package/dist/src/api/config/types.js.map +0 -1
- package/dist/src/api/index.d.ts +0 -7
- package/dist/src/api/index.d.ts.map +0 -1
- package/dist/src/api/index.js +0 -23
- package/dist/src/api/index.js.map +0 -1
- package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts +0 -11
- package/dist/src/api/judge/EvaluationSchemaBuilder.d.ts.map +0 -1
- package/dist/src/api/judge/EvaluationSchemaBuilder.js +0 -52
- package/dist/src/api/judge/EvaluationSchemaBuilder.js.map +0 -1
- package/dist/src/api/judge/Judge.d.ts +0 -63
- package/dist/src/api/judge/Judge.d.ts.map +0 -1
- package/dist/src/api/judge/Judge.js +0 -149
- package/dist/src/api/judge/Judge.js.map +0 -1
- package/dist/src/api/judge/index.d.ts +0 -3
- package/dist/src/api/judge/index.d.ts.map +0 -1
- package/dist/src/api/judge/index.js +0 -6
- package/dist/src/api/judge/index.js.map +0 -1
- package/dist/src/api/judge/types.d.ts +0 -35
- package/dist/src/api/judge/types.d.ts.map +0 -1
- package/dist/src/api/judge/types.js +0 -3
- package/dist/src/api/judge/types.js.map +0 -1
- package/dist/src/api/metrics/BedrockTokenUsage.d.ts +0 -7
- package/dist/src/api/metrics/BedrockTokenUsage.d.ts.map +0 -1
- package/dist/src/api/metrics/BedrockTokenUsage.js +0 -12
- package/dist/src/api/metrics/BedrockTokenUsage.js.map +0 -1
- package/dist/src/api/metrics/LDAIMetrics.d.ts +0 -17
- package/dist/src/api/metrics/LDAIMetrics.d.ts.map +0 -1
- package/dist/src/api/metrics/LDAIMetrics.js +0 -3
- package/dist/src/api/metrics/LDAIMetrics.js.map +0 -1
- package/dist/src/api/metrics/LDFeedbackKind.d.ts +0 -14
- package/dist/src/api/metrics/LDFeedbackKind.d.ts.map +0 -1
- package/dist/src/api/metrics/LDFeedbackKind.js +0 -18
- package/dist/src/api/metrics/LDFeedbackKind.js.map +0 -1
- package/dist/src/api/metrics/LDTokenUsage.d.ts +0 -18
- package/dist/src/api/metrics/LDTokenUsage.d.ts.map +0 -1
- package/dist/src/api/metrics/LDTokenUsage.js +0 -3
- package/dist/src/api/metrics/LDTokenUsage.js.map +0 -1
- package/dist/src/api/metrics/OpenAiUsage.d.ts +0 -7
- package/dist/src/api/metrics/OpenAiUsage.d.ts.map +0 -1
- package/dist/src/api/metrics/OpenAiUsage.js +0 -13
- package/dist/src/api/metrics/OpenAiUsage.js.map +0 -1
- package/dist/src/api/metrics/VercelAISDKTokenUsage.d.ts +0 -9
- package/dist/src/api/metrics/VercelAISDKTokenUsage.d.ts.map +0 -1
- package/dist/src/api/metrics/VercelAISDKTokenUsage.js +0 -13
- package/dist/src/api/metrics/VercelAISDKTokenUsage.js.map +0 -1
- package/dist/src/api/metrics/index.d.ts +0 -7
- package/dist/src/api/metrics/index.d.ts.map +0 -1
- package/dist/src/api/metrics/index.js +0 -23
- package/dist/src/api/metrics/index.js.map +0 -1
- package/dist/src/api/providers/AIProvider.d.ts +0 -52
- package/dist/src/api/providers/AIProvider.d.ts.map +0 -1
- package/dist/src/api/providers/AIProvider.js +0 -88
- package/dist/src/api/providers/AIProvider.js.map +0 -1
- package/dist/src/api/providers/AIProviderFactory.d.ts +0 -39
- package/dist/src/api/providers/AIProviderFactory.d.ts.map +0 -1
- package/dist/src/api/providers/AIProviderFactory.js +0 -102
- package/dist/src/api/providers/AIProviderFactory.js.map +0 -1
- package/dist/src/api/providers/index.d.ts +0 -3
- package/dist/src/api/providers/index.d.ts.map +0 -1
- package/dist/src/api/providers/index.js +0 -19
- package/dist/src/api/providers/index.js.map +0 -1
- package/dist/src/index.d.ts +0 -19
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -29
- package/dist/src/index.js.map +0 -1
- package/docs/.nojekyll +0 -1
- package/docs/assets/highlight.css +0 -92
- package/docs/assets/main.js +0 -58
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1379
- package/docs/classes/AIProvider.html +0 -210
- package/docs/classes/AIProviderFactory.html +0 -208
- package/docs/classes/Judge.html +0 -322
- package/docs/classes/TrackedChat.html +0 -322
- package/docs/enums/LDFeedbackKind.html +0 -115
- package/docs/functions/createBedrockTokenUsage.html +0 -94
- package/docs/functions/createOpenAiUsage.html +0 -94
- package/docs/functions/createVercelAISDKTokenUsage.html +0 -98
- package/docs/functions/initAi.html +0 -93
- package/docs/index.html +0 -136
- package/docs/interfaces/ChatResponse.html +0 -130
- package/docs/interfaces/EvalScore.html +0 -119
- package/docs/interfaces/JudgeResponse.html +0 -129
- package/docs/interfaces/LDAIAgentConfig.html +0 -167
- package/docs/interfaces/LDAIAgentConfigDefault.html +0 -155
- package/docs/interfaces/LDAIAgentRequestConfig.html +0 -129
- package/docs/interfaces/LDAIClient.html +0 -449
- package/docs/interfaces/LDAICompletionConfig.html +0 -167
- package/docs/interfaces/LDAICompletionConfigDefault.html +0 -155
- package/docs/interfaces/LDAIConfig.html +0 -148
- package/docs/interfaces/LDAIConfigDefault.html +0 -133
- package/docs/interfaces/LDAIConfigTracker.html +0 -510
- package/docs/interfaces/LDAIJudgeConfig.html +0 -167
- package/docs/interfaces/LDAIJudgeConfigDefault.html +0 -155
- package/docs/interfaces/LDAIMetrics.html +0 -121
- package/docs/interfaces/LDJudge.html +0 -119
- package/docs/interfaces/LDJudgeConfiguration.html +0 -109
- package/docs/interfaces/LDLogger.html +0 -189
- package/docs/interfaces/LDMessage.html +0 -119
- package/docs/interfaces/LDModelConfig.html +0 -139
- package/docs/interfaces/LDProviderConfig.html +0 -105
- package/docs/interfaces/LDTokenUsage.html +0 -129
- package/docs/interfaces/StructuredResponse.html +0 -129
- package/docs/types/LDAIConfigDefaultKind.html +0 -81
- package/docs/types/LDAIConfigKind.html +0 -81
- package/docs/types/LDAIConfigMode.html +0 -81
- package/docs/types/SupportedAIProvider.html +0 -81
- package/docs/variables/SUPPORTED_AI_PROVIDERS.html +0 -81
- package/jest.config.js +0 -7
- package/src/LDAIClientImpl.ts +0 -327
- package/src/LDAIConfigTrackerImpl.ts +0 -278
- package/src/LDClientMin.ts +0 -18
- package/src/api/LDAIClient.ts +0 -325
- package/src/api/chat/TrackedChat.ts +0 -159
- package/src/api/chat/index.ts +0 -2
- package/src/api/chat/types.ts +0 -24
- package/src/api/config/LDAIConfigTracker.ts +0 -231
- package/src/api/config/LDAIConfigUtils.ts +0 -201
- package/src/api/config/index.ts +0 -3
- package/src/api/config/types.ts +0 -256
- package/src/api/index.ts +0 -6
- package/src/api/judge/EvaluationSchemaBuilder.ts +0 -54
- package/src/api/judge/Judge.ts +0 -216
- package/src/api/judge/index.ts +0 -2
- package/src/api/judge/types.ts +0 -39
- package/src/api/metrics/BedrockTokenUsage.ts +0 -13
- package/src/api/metrics/LDAIMetrics.ts +0 -18
- package/src/api/metrics/LDFeedbackKind.ts +0 -13
- package/src/api/metrics/LDTokenUsage.ts +0 -19
- package/src/api/metrics/OpenAiUsage.ts +0 -13
- package/src/api/metrics/VercelAISDKTokenUsage.ts +0 -15
- package/src/api/metrics/index.ts +0 -6
- package/src/api/providers/AIProvider.ts +0 -94
- package/src/api/providers/AIProviderFactory.ts +0 -152
- package/src/api/providers/index.ts +0 -2
- package/src/index.ts +0 -24
- package/tsconfig.eslint.json +0 -5
- package/tsconfig.json +0 -21
- package/tsconfig.ref.json +0 -7
- package/typedoc.json +0 -5
package/__tests__/Judge.test.ts
DELETED
|
@@ -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
|
-
});
|