peerbench 0.0.2-alpha.0 → 0.0.2-alpha.2
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/README.md +123 -99
- package/dist/aggregators/index.d.ts +67 -0
- package/dist/aggregators/index.js +46 -0
- package/dist/aggregators/index.js.map +1 -0
- package/dist/benchmarks/index.d.ts +615 -1271
- package/dist/benchmarks/index.js +358 -805
- package/dist/benchmarks/index.js.map +1 -1
- package/dist/{chunk-DUBKY73H.js → chunk-4UBK6452.js} +13 -13
- package/dist/chunk-4UBK6452.js.map +1 -0
- package/dist/chunk-ERALDEZY.js +112 -0
- package/dist/chunk-ERALDEZY.js.map +1 -0
- package/dist/{chunk-ZJWSK4VO.js → chunk-HMQYGCKI.js} +1 -1
- package/dist/chunk-HMQYGCKI.js.map +1 -0
- package/dist/chunk-NUEOE3K5.js +8 -0
- package/dist/chunk-NUEOE3K5.js.map +1 -0
- package/dist/chunk-OQE6TQXZ.js +42 -0
- package/dist/chunk-OQE6TQXZ.js.map +1 -0
- package/dist/chunk-QY5MPNNB.js +28 -0
- package/dist/chunk-QY5MPNNB.js.map +1 -0
- package/dist/chunk-R76XA2K6.js +229 -0
- package/dist/chunk-R76XA2K6.js.map +1 -0
- package/dist/chunk-TRNCF2BG.js +35 -0
- package/dist/chunk-TRNCF2BG.js.map +1 -0
- package/dist/chunk-UHHHSYVE.js +11 -0
- package/dist/chunk-UHHHSYVE.js.map +1 -0
- package/dist/{chunk-232PY7K3.js → chunk-YY33MNMV.js} +29 -14
- package/dist/chunk-YY33MNMV.js.map +1 -0
- package/dist/chunk-ZEWI24CV.js +365 -0
- package/dist/chunk-ZEWI24CV.js.map +1 -0
- package/dist/chunk-ZXTQJFGL.js +44 -0
- package/dist/chunk-ZXTQJFGL.js.map +1 -0
- package/dist/index-BAioQhp2.d.ts +27 -0
- package/dist/index.d.ts +51 -26
- package/dist/index.js +28 -25
- package/dist/index.js.map +1 -1
- package/dist/json-file-ZwzLUbje.d.ts +73 -0
- package/dist/llm-judge-QThCZ9TQ.d.ts +67 -0
- package/dist/providers/index.d.ts +16 -19
- package/dist/providers/index.js +8 -253
- package/dist/providers/index.js.map +1 -1
- package/dist/schemas/extensions/index.d.ts +16 -2
- package/dist/schemas/extensions/index.js +9 -3
- package/dist/schemas/extensions/index.js.map +1 -1
- package/dist/schemas/index.d.ts +108 -141
- package/dist/schemas/index.js +7 -10
- package/dist/schemas/llm/index.d.ts +100 -82
- package/dist/schemas/llm/index.js +7 -29
- package/dist/schemas/llm/index.js.map +1 -1
- package/dist/scorers/index.d.ts +3 -2
- package/dist/scorers/index.js +8 -486
- package/dist/scorers/index.js.map +1 -1
- package/dist/storages/index.d.ts +69 -0
- package/dist/storages/index.js +98 -0
- package/dist/storages/index.js.map +1 -0
- package/package.json +12 -6
- package/dist/catalogs/index.d.ts +0 -75
- package/dist/catalogs/index.js +0 -88
- package/dist/catalogs/index.js.map +0 -1
- package/dist/chunk-22HU24QF.js +0 -8
- package/dist/chunk-22HU24QF.js.map +0 -1
- package/dist/chunk-232PY7K3.js.map +0 -1
- package/dist/chunk-7TREBPSJ.js +0 -26
- package/dist/chunk-7TREBPSJ.js.map +0 -1
- package/dist/chunk-DUBKY73H.js.map +0 -1
- package/dist/chunk-GVF4YZF3.js +0 -15
- package/dist/chunk-GVF4YZF3.js.map +0 -1
- package/dist/chunk-HJH3SW3L.js +0 -103
- package/dist/chunk-HJH3SW3L.js.map +0 -1
- package/dist/chunk-IUN2IUCS.js +0 -58
- package/dist/chunk-IUN2IUCS.js.map +0 -1
- package/dist/chunk-VBOM2YEG.js +0 -47
- package/dist/chunk-VBOM2YEG.js.map +0 -1
- package/dist/chunk-ZJWSK4VO.js.map +0 -1
- package/dist/data-BmN5WjZ4.d.ts +0 -57
- package/dist/generic-array-DLHWSvf1.d.ts +0 -22
- package/dist/index-WiPjF2AL.d.ts +0 -15
- package/dist/llm-judge-DIG1f1Az.d.ts +0 -67
- package/dist/simple-system-prompt-CzPYuvo0.d.ts +0 -49
- package/dist/system-prompt--0FdPWqK.d.ts +0 -58
- package/dist/utilities-BrRH32rD.d.ts +0 -30
package/dist/scorers/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { A as AbstractScorer } from '../abstract-Dec9Sc5O.js';
|
|
2
2
|
export { B as BaseScorerResult } from '../abstract-Dec9Sc5O.js';
|
|
3
|
-
export { b as
|
|
3
|
+
export { b as LLMAsAJudgeCriterion, c as LLMAsAJudgeScoreParams, L as LLMAsAJudgeScorer, a as MCQScorer, M as MCQScorerParams } from '../llm-judge-QThCZ9TQ.js';
|
|
4
4
|
import '../llm-DNj_tp2T.js';
|
|
5
5
|
import '../provider-BDjGp2y-.js';
|
|
6
6
|
import 'openai/resources/shared';
|
|
7
7
|
import 'openai/resources/chat/completions';
|
|
8
8
|
import '../rate-limiter-CSmVIRsM.js';
|
|
9
|
+
import 'zod';
|
|
9
10
|
|
|
10
11
|
type RegexPattern = {
|
|
11
12
|
/**
|
|
@@ -51,7 +52,7 @@ type RegexScorerParams = {
|
|
|
51
52
|
* Generic Regex scorer. It scores the given input against a set of regex patterns.
|
|
52
53
|
*/
|
|
53
54
|
declare class RegexScorer extends AbstractScorer {
|
|
54
|
-
readonly kind
|
|
55
|
+
readonly kind: "peerbench.ai/regex";
|
|
55
56
|
score(params: RegexScorerParams): Promise<{
|
|
56
57
|
value: number;
|
|
57
58
|
extractedAnswers: {
|
package/dist/scorers/index.js
CHANGED
|
@@ -1,493 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AbstractScorer
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../chunk-
|
|
2
|
+
AbstractScorer,
|
|
3
|
+
LLMAsAJudgeScorer,
|
|
4
|
+
MCQScorer,
|
|
5
|
+
RegexScorer
|
|
6
|
+
} from "../chunk-ZEWI24CV.js";
|
|
7
|
+
import "../chunk-UHHHSYVE.js";
|
|
8
|
+
import "../chunk-4UBK6452.js";
|
|
7
9
|
import "../chunk-PZ5AY32C.js";
|
|
8
|
-
|
|
9
|
-
// src/scorers/regex.ts
|
|
10
|
-
var RegexScorer = class extends AbstractScorer {
|
|
11
|
-
kind = "regex";
|
|
12
|
-
async score(params) {
|
|
13
|
-
const allGroupNames = /* @__PURE__ */ new Set();
|
|
14
|
-
for (const pattern of params.patterns) {
|
|
15
|
-
const regexSource = pattern.regex.source;
|
|
16
|
-
const namedGroupRegex = /\(\?<(\w+)>/g;
|
|
17
|
-
while (true) {
|
|
18
|
-
const match = namedGroupRegex.exec(regexSource);
|
|
19
|
-
if (match === null) {
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
if (match[1]) {
|
|
23
|
-
allGroupNames.add(match[1]);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const extractedValues = {};
|
|
28
|
-
for (const groupName of allGroupNames) {
|
|
29
|
-
extractedValues[groupName] = null;
|
|
30
|
-
}
|
|
31
|
-
const matchPreference = params.matchPreference ?? "last";
|
|
32
|
-
for (const pattern of params.patterns) {
|
|
33
|
-
const matches = Array.from(params.input.matchAll(pattern.regex));
|
|
34
|
-
const match = matchPreference === "first" ? matches[0] : matches.at(-1);
|
|
35
|
-
if (match && match.groups) {
|
|
36
|
-
let hasExtractedValue = false;
|
|
37
|
-
for (const [groupName, groupValue] of Object.entries(match.groups)) {
|
|
38
|
-
if (groupValue !== void 0) {
|
|
39
|
-
let value = groupValue;
|
|
40
|
-
if (pattern.transform) {
|
|
41
|
-
const transformed = pattern.transform(value);
|
|
42
|
-
if (transformed === void 0) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
value = transformed;
|
|
46
|
-
}
|
|
47
|
-
extractedValues[groupName] = value;
|
|
48
|
-
hasExtractedValue = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (hasExtractedValue) {
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
} else if (match) {
|
|
55
|
-
const captureGroupIndex = pattern.captureGroupIndex ?? 1;
|
|
56
|
-
const extractedValue = match[captureGroupIndex];
|
|
57
|
-
if (extractedValue !== void 0) {
|
|
58
|
-
if (pattern.transform) {
|
|
59
|
-
const transformed = pattern.transform(extractedValue);
|
|
60
|
-
if (transformed === void 0) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const allowPartial = params.allowPartialScoring ?? false;
|
|
69
|
-
let score = 0;
|
|
70
|
-
if (typeof params.expectedValue === "function") {
|
|
71
|
-
const validator = params.expectedValue;
|
|
72
|
-
const extractedEntries = Object.entries(extractedValues).filter(
|
|
73
|
-
([, value]) => value !== null
|
|
74
|
-
);
|
|
75
|
-
if (extractedEntries.length === 0) {
|
|
76
|
-
score = 0;
|
|
77
|
-
} else {
|
|
78
|
-
if (allowPartial) {
|
|
79
|
-
const passingCount = extractedEntries.filter(
|
|
80
|
-
([groupName, extractedValue]) => validator(groupName, extractedValue)
|
|
81
|
-
).length;
|
|
82
|
-
score = passingCount / extractedEntries.length;
|
|
83
|
-
} else {
|
|
84
|
-
const allMatch = extractedEntries.every(
|
|
85
|
-
([groupName, extractedValue]) => validator(groupName, extractedValue)
|
|
86
|
-
);
|
|
87
|
-
score = allMatch ? 1 : 0;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
const expectedEntries = Object.entries(params.expectedValue);
|
|
92
|
-
const totalExpected = expectedEntries.length;
|
|
93
|
-
if (allowPartial) {
|
|
94
|
-
const matchingCount = expectedEntries.filter(([key, expectedValue]) => {
|
|
95
|
-
const extractedValue = extractedValues[key];
|
|
96
|
-
return extractedValue !== null && extractedValue === expectedValue;
|
|
97
|
-
}).length;
|
|
98
|
-
score = totalExpected > 0 ? matchingCount / totalExpected : 0;
|
|
99
|
-
} else {
|
|
100
|
-
const allMatch = expectedEntries.every(([key, expectedValue]) => {
|
|
101
|
-
const extractedValue = extractedValues[key];
|
|
102
|
-
return extractedValue !== null && extractedValue === expectedValue;
|
|
103
|
-
});
|
|
104
|
-
score = allMatch ? 1 : 0;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
value: score,
|
|
109
|
-
extractedAnswers: Object.fromEntries(
|
|
110
|
-
Object.entries(extractedValues).filter(
|
|
111
|
-
(entry) => entry[1] !== null
|
|
112
|
-
)
|
|
113
|
-
)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/scorers/mcq.ts
|
|
119
|
-
var MCQScorer = class extends AbstractScorer {
|
|
120
|
-
kind = "mcq";
|
|
121
|
-
regexScorer = new RegexScorer();
|
|
122
|
-
async score(params) {
|
|
123
|
-
const { response, choices, correctAnswers } = params;
|
|
124
|
-
const normalizedCorrectAnswers = correctAnswers.map(
|
|
125
|
-
(ca) => ca.toUpperCase()
|
|
126
|
-
);
|
|
127
|
-
const normalizedResponse = response.trim().toUpperCase();
|
|
128
|
-
if (normalizedCorrectAnswers.includes(normalizedResponse)) {
|
|
129
|
-
return {
|
|
130
|
-
value: 1,
|
|
131
|
-
extractedAnswers: [normalizedResponse]
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
const json = parseResponseAsJSON(response);
|
|
135
|
-
if (json !== void 0 && typeof json === "object") {
|
|
136
|
-
const extractedAnswer = json.answer !== void 0 ? getFirstLetter(json.answer) : void 0;
|
|
137
|
-
if (extractedAnswer !== void 0) {
|
|
138
|
-
const normalizedExtracted = extractedAnswer.trim().toUpperCase();
|
|
139
|
-
if (normalizedCorrectAnswers.includes(normalizedExtracted)) {
|
|
140
|
-
return {
|
|
141
|
-
value: 1,
|
|
142
|
-
extractedAnswers: [extractedAnswer]
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
value: 0,
|
|
147
|
-
extractedAnswers: json.answer === void 0 ? [] : [extractedAnswer ?? String(json.answer)]
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
const patterns = [];
|
|
152
|
-
for (const answer of Object.values(params.choices)) {
|
|
153
|
-
const answerPatterns = this.buildPatternsForAnswer(answer);
|
|
154
|
-
patterns.push(...answerPatterns);
|
|
155
|
-
}
|
|
156
|
-
const validateAnswer = (groupName, extracted) => {
|
|
157
|
-
const normalizedExtracted = extracted.trim().toUpperCase();
|
|
158
|
-
if (normalizedCorrectAnswers.includes(normalizedExtracted)) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
const answerOption = Object.entries(choices).find(
|
|
162
|
-
([, value]) => value.trim().toUpperCase() === extracted.trim().toUpperCase()
|
|
163
|
-
);
|
|
164
|
-
if (answerOption && normalizedCorrectAnswers.includes(answerOption[0].toUpperCase())) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
};
|
|
169
|
-
const regexParams = {
|
|
170
|
-
input: response,
|
|
171
|
-
patterns,
|
|
172
|
-
expectedValue: validateAnswer,
|
|
173
|
-
matchPreference: "last"
|
|
174
|
-
};
|
|
175
|
-
const result = await this.regexScorer.score(regexParams);
|
|
176
|
-
return {
|
|
177
|
-
value: result.value,
|
|
178
|
-
extractedAnswers: Object.entries(result.extractedAnswers).map(
|
|
179
|
-
([, value]) => value
|
|
180
|
-
)
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
buildPatternsForAnswer(answerText) {
|
|
184
|
-
const escapedAnswer = escapeRegex(answerText);
|
|
185
|
-
return [
|
|
186
|
-
{
|
|
187
|
-
// "<!NO ANSWER!>" - This pattern matches but has no capture group, so it won't extract anything
|
|
188
|
-
regex: /<!NO ANSWER!>/g
|
|
189
|
-
},
|
|
190
|
-
// Specific patterns for the full answer text (checked first)
|
|
191
|
-
{
|
|
192
|
-
// "Answer is $\boxed{answer text}$"
|
|
193
|
-
regex: new RegExp(
|
|
194
|
-
`[Aa]nswer is \\$\\\\boxed\\{(?<answer>${escapedAnswer})\\}\\$`,
|
|
195
|
-
"g"
|
|
196
|
-
),
|
|
197
|
-
transform: (value) => value.toUpperCase()
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
// "Answer is answer text"
|
|
201
|
-
regex: new RegExp(`[Aa]nswer is\\s+(?<answer>${escapedAnswer})`, "g"),
|
|
202
|
-
transform: (value) => value.toUpperCase()
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
// "Answer is **answer text**"
|
|
206
|
-
regex: new RegExp(
|
|
207
|
-
`[Aa]nswer is\\s+\\**(?<answer>${escapedAnswer})\\**`,
|
|
208
|
-
"g"
|
|
209
|
-
),
|
|
210
|
-
transform: (value) => value.toUpperCase()
|
|
211
|
-
},
|
|
212
|
-
// Generic patterns (checked after specific patterns)
|
|
213
|
-
{
|
|
214
|
-
// "Answer is $\boxed{A}$."
|
|
215
|
-
regex: /[Aa]nswer is \$\\boxed\{(?<answer>[A-Z])\}\$\.?/g
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
// "Answer is A" - match single letter only when it's a complete standalone answer.
|
|
219
|
-
// Pattern matches: "Answer is" + whitespace + single letter + end or punctuation
|
|
220
|
-
regex: /[Aa]nswer is\s+(?<answer>[A-Z])(?=\s*$|[.,;:!?])/g
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
// "Answer is **A**"
|
|
224
|
-
regex: /[Aa]nswer is\s+\**(?<answer>[A-Z])\**/g
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
// "A: answer text"
|
|
228
|
-
regex: /(?<answer>[A-Z]):.+/g
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
// "A) answer text"
|
|
232
|
-
regex: /(?<answer>[A-Z])\)\s*.+/g
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
// "A)"
|
|
236
|
-
regex: /(?<answer>[A-Z])\)/g
|
|
237
|
-
}
|
|
238
|
-
];
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
function getFirstLetter(text) {
|
|
242
|
-
const match = text.match(/[A-Za-z]/);
|
|
243
|
-
return match ? match[0].toUpperCase() : void 0;
|
|
244
|
-
}
|
|
245
|
-
function escapeRegex(str) {
|
|
246
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/scorers/llm-judge.ts
|
|
250
|
-
var LLMJudgeScorer = class extends AbstractScorer {
|
|
251
|
-
kind = "llmJudge";
|
|
252
|
-
provider;
|
|
253
|
-
constructor(provider) {
|
|
254
|
-
super();
|
|
255
|
-
this.provider = provider;
|
|
256
|
-
}
|
|
257
|
-
async score(params) {
|
|
258
|
-
const criteria = normalizeWeights(params.criteria ?? DEFAULT_CRITERIA);
|
|
259
|
-
const systemPrompt = params.systemPrompt ?? [
|
|
260
|
-
"You are a strict, fair evaluation judge.",
|
|
261
|
-
"Only use information provided in the task, reference, and candidate answer(s).",
|
|
262
|
-
"For each criterion, return an integer score within the provided scale and a very brief justification (\u22642 sentences).",
|
|
263
|
-
"Return only JSON that conforms to the requested schema.",
|
|
264
|
-
"Do not include chain-of-thought or internal reasoning; only concise justifications."
|
|
265
|
-
].join(" ");
|
|
266
|
-
const promptPrefix = params.promptPrefix ?? "";
|
|
267
|
-
const promptSuffix = params.promptSuffix ?? "";
|
|
268
|
-
return await scorePointwise(
|
|
269
|
-
{
|
|
270
|
-
task: params.task,
|
|
271
|
-
referenceAnswer: params.referenceAnswer,
|
|
272
|
-
candidateAnswer: params.candidateAnswer,
|
|
273
|
-
criteria,
|
|
274
|
-
meta: params.meta,
|
|
275
|
-
model: params.model,
|
|
276
|
-
rateLimiter: params.rateLimiter,
|
|
277
|
-
systemPrompt,
|
|
278
|
-
promptPrefix,
|
|
279
|
-
promptSuffix
|
|
280
|
-
},
|
|
281
|
-
this.provider
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
var DEFAULT_CRITERIA = [
|
|
286
|
-
{
|
|
287
|
-
id: "correctness",
|
|
288
|
-
description: "Factual correctness and alignment with the task/reference.",
|
|
289
|
-
weight: 0.5,
|
|
290
|
-
scale: { min: 0, max: 5 }
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
id: "instruction_following",
|
|
294
|
-
description: "Adheres to constraints, format, and intent of the task.",
|
|
295
|
-
weight: 0.3,
|
|
296
|
-
scale: { min: 0, max: 5 }
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
id: "clarity",
|
|
300
|
-
description: "Clear, concise, and well-structured.",
|
|
301
|
-
weight: 0.2,
|
|
302
|
-
scale: { min: 0, max: 5 }
|
|
303
|
-
}
|
|
304
|
-
];
|
|
305
|
-
async function executeProviderCall(call, rateLimiter) {
|
|
306
|
-
if (!rateLimiter) {
|
|
307
|
-
return await call();
|
|
308
|
-
}
|
|
309
|
-
return await rateLimiter.execute(call);
|
|
310
|
-
}
|
|
311
|
-
function normalizeWeights(criteria) {
|
|
312
|
-
const sum = criteria.reduce((a, c) => a + (c.weight ?? 1), 0) || 1;
|
|
313
|
-
return criteria.map((c) => ({ ...c, weight: (c.weight ?? 1) / sum }));
|
|
314
|
-
}
|
|
315
|
-
function renderCriteria(criteria) {
|
|
316
|
-
return criteria.map((c, i) => {
|
|
317
|
-
const min = c.scale?.min ?? 0;
|
|
318
|
-
const max = c.scale?.max ?? 5;
|
|
319
|
-
const weight = c.weight ?? 0;
|
|
320
|
-
return `${i + 1}. id="${c.id}" (weight=${weight}, scale=${min}..${max}) \u2014 ${c.description}`;
|
|
321
|
-
}).join("\n");
|
|
322
|
-
}
|
|
323
|
-
function computeOverallScore(perCriterion, criteria) {
|
|
324
|
-
let total = 0;
|
|
325
|
-
for (const pc of perCriterion) {
|
|
326
|
-
const criterion = criteria.find((c) => c.id === pc.id);
|
|
327
|
-
const min = criterion?.scale?.min ?? 0;
|
|
328
|
-
const max = criterion?.scale?.max ?? 5;
|
|
329
|
-
const weight = criterion?.weight ?? 0;
|
|
330
|
-
const score = Number(pc.score);
|
|
331
|
-
if (!Number.isFinite(score)) continue;
|
|
332
|
-
const clamped = Math.max(min, Math.min(max, score));
|
|
333
|
-
const normalized100 = max === min ? 0 : (clamped - min) / (max - min) * 100;
|
|
334
|
-
total += normalized100 * weight;
|
|
335
|
-
}
|
|
336
|
-
return Math.round(total);
|
|
337
|
-
}
|
|
338
|
-
function deriveVerdict(overall) {
|
|
339
|
-
if (overall >= 85) return "strong-pass";
|
|
340
|
-
if (overall >= 70) return "pass";
|
|
341
|
-
if (overall >= 60) return "borderline";
|
|
342
|
-
return "fail";
|
|
343
|
-
}
|
|
344
|
-
function mapVerdict(verdict) {
|
|
345
|
-
if (verdict === "borderline") return "borderline";
|
|
346
|
-
if (verdict === "fail") return "fail";
|
|
347
|
-
return "pass";
|
|
348
|
-
}
|
|
349
|
-
function buildPointwiseExplanation(json) {
|
|
350
|
-
let explanation = "";
|
|
351
|
-
if (json.notes && Array.isArray(json.notes)) {
|
|
352
|
-
explanation += json.notes.join(" ");
|
|
353
|
-
} else if (typeof json.notes === "string") {
|
|
354
|
-
explanation += json.notes;
|
|
355
|
-
}
|
|
356
|
-
for (const criterion of json.perCriterion ?? []) {
|
|
357
|
-
explanation += [
|
|
358
|
-
`
|
|
359
|
-
Criteria: ${criterion.id}`,
|
|
360
|
-
`Score: ${criterion.score}`,
|
|
361
|
-
`Justification: ${criterion.justification}`
|
|
362
|
-
].join("\n");
|
|
363
|
-
}
|
|
364
|
-
return explanation.trim() ? explanation.trim() : void 0;
|
|
365
|
-
}
|
|
366
|
-
function extractFirstJSON(text) {
|
|
367
|
-
const jsonText = extractFirstJSONObjectString(text);
|
|
368
|
-
if (!jsonText) return;
|
|
369
|
-
return parseResponseAsJSON(jsonText);
|
|
370
|
-
}
|
|
371
|
-
async function scorePointwise(args, provider) {
|
|
372
|
-
const user = [
|
|
373
|
-
args.promptPrefix ? `${args.promptPrefix}
|
|
374
|
-
` : "",
|
|
375
|
-
`TASK:
|
|
376
|
-
${args.task}`,
|
|
377
|
-
args.meta ? `
|
|
378
|
-
|
|
379
|
-
ADDITIONAL CONTEXT:
|
|
380
|
-
${JSON.stringify(args.meta, null, 2)}` : "",
|
|
381
|
-
`
|
|
382
|
-
|
|
383
|
-
RUBRIC:
|
|
384
|
-
${renderCriteria(args.criteria)}`,
|
|
385
|
-
args.referenceAnswer ? `
|
|
386
|
-
|
|
387
|
-
REFERENCE ANSWER:
|
|
388
|
-
${args.referenceAnswer}` : "\n\nREFERENCE ANSWER:\n<!NO REFERENCE PROVIDED!> (Judge based on task/context/rubric.)",
|
|
389
|
-
`
|
|
390
|
-
|
|
391
|
-
CANDIDATE ANSWER:
|
|
392
|
-
${args.candidateAnswer}`,
|
|
393
|
-
`
|
|
394
|
-
|
|
395
|
-
RESPONSE FORMAT (strict JSON):`,
|
|
396
|
-
JSON.stringify(
|
|
397
|
-
{
|
|
398
|
-
perCriterion: [
|
|
399
|
-
{
|
|
400
|
-
id: "<string>",
|
|
401
|
-
score: "<integer within scale>",
|
|
402
|
-
justification: "<\u22642 sentences>"
|
|
403
|
-
}
|
|
404
|
-
],
|
|
405
|
-
overall: "<integer 0-100>",
|
|
406
|
-
verdict: "<strong-pass|pass|borderline|fail>",
|
|
407
|
-
notes: "<optional list of short strings>"
|
|
408
|
-
},
|
|
409
|
-
null,
|
|
410
|
-
2
|
|
411
|
-
),
|
|
412
|
-
"\n\nINSTRUCTIONS:",
|
|
413
|
-
"- Compute per-criterion integer scores within each scale.",
|
|
414
|
-
"- Compute weighted overall as a 0-100 integer: normalize weights, map each score to 0-100 by its scale, then weighted average.",
|
|
415
|
-
"- Use verdict thresholds: \u226585 strong-pass, 70-84 pass, 60-69 borderline, <60 fail.",
|
|
416
|
-
"- Output valid JSON only.",
|
|
417
|
-
args.promptSuffix ? `
|
|
418
|
-
${args.promptSuffix}` : ""
|
|
419
|
-
].join("");
|
|
420
|
-
const messages = [
|
|
421
|
-
{ role: "system", content: args.systemPrompt },
|
|
422
|
-
{ role: "user", content: user }
|
|
423
|
-
];
|
|
424
|
-
const providerResponse = await executeProviderCall(
|
|
425
|
-
() => provider.forward({
|
|
426
|
-
model: args.model,
|
|
427
|
-
messages
|
|
428
|
-
}),
|
|
429
|
-
args.rateLimiter
|
|
430
|
-
);
|
|
431
|
-
const json = extractFirstJSON(providerResponse.data);
|
|
432
|
-
if (!json || !Array.isArray(json.perCriterion)) {
|
|
433
|
-
return null;
|
|
434
|
-
}
|
|
435
|
-
const computedOverall = computeOverallScore(json.perCriterion, args.criteria);
|
|
436
|
-
const overall = Number.isFinite(Number(json.overall)) && Number(json.overall) > 0 ? Math.round(Number(json.overall)) : computedOverall;
|
|
437
|
-
const verdict = json.verdict ?? deriveVerdict(overall);
|
|
438
|
-
const value = Math.min(1, Math.max(0, overall / 100));
|
|
439
|
-
const explanation = buildPointwiseExplanation(json);
|
|
440
|
-
return {
|
|
441
|
-
value,
|
|
442
|
-
verdict: mapVerdict(verdict),
|
|
443
|
-
explanation,
|
|
444
|
-
provider: provider.kind,
|
|
445
|
-
inputTokensUsed: providerResponse.inputTokensUsed,
|
|
446
|
-
outputTokensUsed: providerResponse.outputTokensUsed,
|
|
447
|
-
inputCost: providerResponse.inputCost,
|
|
448
|
-
outputCost: providerResponse.outputCost,
|
|
449
|
-
metadata: {
|
|
450
|
-
overall,
|
|
451
|
-
verdict,
|
|
452
|
-
perCriterion: json.perCriterion,
|
|
453
|
-
notes: json.notes,
|
|
454
|
-
scorePrompt: user,
|
|
455
|
-
systemPrompt: args.systemPrompt
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
function extractFirstJSONObjectString(text) {
|
|
460
|
-
const start = text.indexOf("{");
|
|
461
|
-
if (start === -1) return;
|
|
462
|
-
let depth = 0;
|
|
463
|
-
let inString = false;
|
|
464
|
-
let escape = false;
|
|
465
|
-
for (let i = start; i < text.length; i++) {
|
|
466
|
-
const ch = text[i];
|
|
467
|
-
if (inString) {
|
|
468
|
-
if (escape) {
|
|
469
|
-
escape = false;
|
|
470
|
-
} else if (ch === "\\") {
|
|
471
|
-
escape = true;
|
|
472
|
-
} else if (ch === '"') {
|
|
473
|
-
inString = false;
|
|
474
|
-
}
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (ch === '"') {
|
|
478
|
-
inString = true;
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
if (ch === "{") depth++;
|
|
482
|
-
if (ch === "}") depth--;
|
|
483
|
-
if (depth === 0) {
|
|
484
|
-
return text.slice(start, i + 1);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
10
|
export {
|
|
489
11
|
AbstractScorer,
|
|
490
|
-
|
|
12
|
+
LLMAsAJudgeScorer,
|
|
491
13
|
MCQScorer,
|
|
492
14
|
RegexScorer
|
|
493
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/scorers/regex.ts","../../src/scorers/mcq.ts","../../src/scorers/llm-judge.ts"],"sourcesContent":["import { AbstractScorer, BaseScorerResult } from \"./abstract\";\n\nexport type RegexPattern = {\n /**\n * The regex pattern to match against the response\n */\n regex: RegExp;\n\n /**\n * The index of the capture group to extract (1-based, like match[1])\n * If not provided, defaults to 1 (first capture group)\n */\n captureGroupIndex?: number;\n\n /**\n * Optional function to transform the extracted value before validation\n */\n transform?: (value: string) => string | undefined;\n};\n\nexport type RegexScorerParams = {\n /**\n * The input text to score\n */\n input: string;\n\n /**\n * Array of regex patterns to try (in order, first match wins)\n */\n patterns: RegexPattern[];\n\n /**\n * Expected value(s) to match against. Can be a record of expected values for named groups, or a validation function\n */\n expectedValue:\n | Record<string, string>\n | ((groupName: string, match: string) => boolean);\n\n /**\n * Optional: Which match to use when multiple matches are found\n * Defaults to \"last\" (uses the last match found)\n */\n matchPreference?: \"first\" | \"last\";\n\n /**\n * Optional: If true, allows partial scoring based on how many groups match\n * For example, if 2 groups are expected and only 1 matches, score would be 0.5\n * Defaults to false (all-or-nothing scoring)\n */\n allowPartialScoring?: boolean;\n};\n\n/**\n * Generic Regex scorer. It scores the given input against a set of regex patterns.\n */\nexport class RegexScorer extends AbstractScorer {\n override readonly kind = \"regex\";\n\n override async score(params: RegexScorerParams) {\n // Collect all named group names from all patterns\n const allGroupNames = new Set<string>();\n for (const pattern of params.patterns) {\n const regexSource = pattern.regex.source;\n const namedGroupRegex = /\\(\\?<(\\w+)>/g;\n\n while (true) {\n const match = namedGroupRegex.exec(regexSource);\n if (match === null) {\n break;\n }\n\n if (match[1]) {\n allGroupNames.add(match[1]);\n }\n }\n }\n\n // Initialize result object with all group names set to null (aka not found yet)\n const extractedValues: Record<string, string | null> = {};\n for (const groupName of allGroupNames) {\n extractedValues[groupName] = null;\n }\n\n // Try regex patterns in order, stop at first successful match\n const matchPreference = params.matchPreference ?? \"last\";\n for (const pattern of params.patterns) {\n const matches = Array.from(params.input.matchAll(pattern.regex));\n const match = matchPreference === \"first\" ? matches[0] : matches.at(-1);\n\n if (match && match.groups) {\n // Extract all named groups from this match\n let hasExtractedValue = false;\n for (const [groupName, groupValue] of Object.entries(match.groups)) {\n if (groupValue !== undefined) {\n let value = groupValue;\n\n // Apply transformation if provided\n if (pattern.transform) {\n const transformed = pattern.transform(value);\n if (transformed === undefined) {\n continue;\n }\n value = transformed;\n }\n\n extractedValues[groupName] = value;\n hasExtractedValue = true;\n }\n }\n // If we extracted at least one value, stop processing further patterns\n if (hasExtractedValue) {\n break;\n }\n } else if (match) {\n // Fallback to captureGroupIndex if no named groups\n // For unnamed groups, we can't add to extractedValues by name\n // They are only used for scoring if no named groups are found\n const captureGroupIndex = pattern.captureGroupIndex ?? 1;\n const extractedValue = match[captureGroupIndex];\n\n if (extractedValue !== undefined) {\n // Apply transformation if provided\n if (pattern.transform) {\n const transformed = pattern.transform(extractedValue);\n if (transformed === undefined) {\n continue;\n }\n // For unnamed groups, we can't store in extractedValues\n // but we can use it for scoring if no named groups were found\n }\n // If we have an extracted value (even if unnamed), stop processing\n // Note: This is a fallback case, so we break here too\n break;\n }\n }\n }\n\n // Calculate score based on matched value\n const allowPartial = params.allowPartialScoring ?? false;\n let score = 0;\n\n if (typeof params.expectedValue === \"function\") {\n // To get proper type inference, cast it using \"as\"\n const validator = params.expectedValue as typeof params.expectedValue;\n const extractedEntries = Object.entries(extractedValues).filter(\n ([, value]) => value !== null\n );\n\n // If no values were extracted, score is 0\n if (extractedEntries.length === 0) {\n score = 0;\n } else {\n if (allowPartial) {\n // Count how many extracted values pass validation\n const passingCount = extractedEntries.filter(\n ([groupName, extractedValue]) =>\n validator(groupName, extractedValue as string)\n ).length;\n score = passingCount / extractedEntries.length;\n } else {\n // All extracted values must pass validation\n const allMatch = extractedEntries.every(\n ([groupName, extractedValue]) =>\n validator(groupName, extractedValue as string)\n );\n score = allMatch ? 1 : 0;\n }\n }\n } else {\n const expectedEntries = Object.entries(params.expectedValue);\n const totalExpected = expectedEntries.length;\n\n if (allowPartial) {\n // Count how many expected values match their extracted values\n const matchingCount = expectedEntries.filter(([key, expectedValue]) => {\n const extractedValue = extractedValues[key];\n return extractedValue !== null && extractedValue === expectedValue;\n }).length;\n score = totalExpected > 0 ? matchingCount / totalExpected : 0;\n } else {\n // All expected values must match\n const allMatch = expectedEntries.every(([key, expectedValue]) => {\n const extractedValue = extractedValues[key];\n return extractedValue !== null && extractedValue === expectedValue;\n });\n score = allMatch ? 1 : 0;\n }\n }\n\n return {\n value: score,\n extractedAnswers: Object.fromEntries(\n Object.entries(extractedValues).filter(\n (entry): entry is [string, string] => entry[1] !== null\n )\n ),\n } satisfies BaseScorerResult;\n }\n}\n","import { parseResponseAsJSON } from \"@/utils\";\nimport { AbstractScorer, BaseScorerResult } from \"./abstract\";\nimport { RegexScorer, RegexPattern, RegexScorerParams } from \"./regex\";\n\nexport type MCQScorerParams = {\n response: string;\n choices: Record<string, string>;\n correctAnswers: string[];\n};\n\nexport class MCQScorer extends AbstractScorer {\n override readonly kind = \"mcq\";\n private regexScorer = new RegexScorer();\n\n async score(params: MCQScorerParams): Promise<BaseScorerResult & {\n extractedAnswers: string[];\n }> {\n const { response, choices, correctAnswers } = params;\n const normalizedCorrectAnswers = correctAnswers.map((ca) =>\n ca.toUpperCase()\n );\n\n // Direct answer comparison\n const normalizedResponse = response.trim().toUpperCase();\n if (normalizedCorrectAnswers.includes(normalizedResponse)) {\n return {\n value: 1,\n extractedAnswers: [normalizedResponse],\n };\n }\n\n // Try to parse the response as JSON (original behavior: returns early if parsed)\n const json = parseResponseAsJSON<{ answer: string }>(response);\n if (json !== undefined && typeof json === \"object\") {\n const extractedAnswer =\n json.answer !== undefined ? getFirstLetter(json.answer) : undefined;\n\n if (extractedAnswer !== undefined) {\n const normalizedExtracted = extractedAnswer.trim().toUpperCase();\n if (normalizedCorrectAnswers.includes(normalizedExtracted)) {\n return {\n value: 1,\n extractedAnswers: [extractedAnswer],\n };\n }\n\n // Response parsed as JSON but does not represent the correct answer\n return {\n value: 0,\n extractedAnswers:\n json.answer === undefined\n ? []\n : [extractedAnswer ?? String(json.answer)],\n };\n }\n }\n\n // Build patterns for all correctAnswers\n const patterns: RegexPattern[] = [];\n for (const answer of Object.values(params.choices)) {\n const answerPatterns = this.buildPatternsForAnswer(answer);\n patterns.push(...answerPatterns);\n }\n\n // Create validation function that handles choices matching\n // New RegexScorer API expects (groupName: string, match: string) => boolean\n const validateAnswer = (groupName: string, extracted: string): boolean => {\n const normalizedExtracted = extracted.trim().toUpperCase();\n\n // Check if extracted value is in correctAnswers\n if (normalizedCorrectAnswers.includes(normalizedExtracted)) {\n return true;\n }\n\n // Check if extracted text matches any choice value, and that choice key is correct\n // Note: When transform uppercases the value, we need to compare case-insensitively\n const answerOption = Object.entries(choices).find(\n ([, value]) =>\n value.trim().toUpperCase() === extracted.trim().toUpperCase()\n );\n\n if (\n answerOption &&\n normalizedCorrectAnswers.includes(answerOption[0].toUpperCase())\n ) {\n return true;\n }\n\n return false;\n };\n\n // Build regex scorer params\n const regexParams: RegexScorerParams = {\n input: response,\n patterns,\n expectedValue: validateAnswer,\n matchPreference: \"last\",\n };\n\n // Call regex scorer\n const result = await this.regexScorer.score(regexParams);\n\n return {\n value: result.value,\n extractedAnswers: Object.entries(result.extractedAnswers).map(\n ([, value]) => value\n ),\n };\n }\n\n private buildPatternsForAnswer(answerText: string): RegexPattern[] {\n const escapedAnswer = escapeRegex(answerText);\n\n return [\n {\n // \"<!NO ANSWER!>\" - This pattern matches but has no capture group, so it won't extract anything\n regex: /<!NO ANSWER!>/g,\n },\n // Specific patterns for the full answer text (checked first)\n {\n // \"Answer is $\\boxed{answer text}$\"\n regex: new RegExp(\n `[Aa]nswer is \\\\$\\\\\\\\boxed\\\\{(?<answer>${escapedAnswer})\\\\}\\\\$`,\n \"g\"\n ),\n transform: (value: string) => value.toUpperCase(),\n },\n {\n // \"Answer is answer text\"\n regex: new RegExp(`[Aa]nswer is\\\\s+(?<answer>${escapedAnswer})`, \"g\"),\n transform: (value: string) => value.toUpperCase(),\n },\n {\n // \"Answer is **answer text**\"\n regex: new RegExp(\n `[Aa]nswer is\\\\s+\\\\**(?<answer>${escapedAnswer})\\\\**`,\n \"g\"\n ),\n transform: (value: string) => value.toUpperCase(),\n },\n // Generic patterns (checked after specific patterns)\n {\n // \"Answer is $\\boxed{A}$.\"\n regex: /[Aa]nswer is \\$\\\\boxed\\{(?<answer>[A-Z])\\}\\$\\.?/g,\n },\n {\n // \"Answer is A\" - match single letter only when it's a complete standalone answer.\n // Pattern matches: \"Answer is\" + whitespace + single letter + end or punctuation\n regex: /[Aa]nswer is\\s+(?<answer>[A-Z])(?=\\s*$|[.,;:!?])/g,\n },\n {\n // \"Answer is **A**\"\n regex: /[Aa]nswer is\\s+\\**(?<answer>[A-Z])\\**/g,\n },\n {\n // \"A: answer text\"\n regex: /(?<answer>[A-Z]):.+/g,\n },\n {\n // \"A) answer text\"\n regex: /(?<answer>[A-Z])\\)\\s*.+/g,\n },\n {\n // \"A)\"\n regex: /(?<answer>[A-Z])\\)/g,\n },\n ];\n }\n}\n\nfunction getFirstLetter(text: string): string | undefined {\n const match = text.match(/[A-Za-z]/);\n return match ? match[0].toUpperCase() : undefined;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import { AbstractLLMProvider } from \"@/providers/abstract/llm\";\nimport { parseResponseAsJSON } from \"@/utils\";\nimport { RateLimiter } from \"@/utils/rate-limiter\";\nimport { ChatCompletionMessageParam } from \"openai/resources/index\";\nimport { AbstractScorer, BaseScorerResult } from \"./abstract\";\n\nexport class LLMJudgeScorer extends AbstractScorer {\n override readonly kind = \"llmJudge\";\n\n private provider: AbstractLLMProvider;\n\n constructor(provider: AbstractLLMProvider) {\n super();\n this.provider = provider;\n }\n\n override async score(\n params: LLMJudgeScorerParams\n ): Promise<LLMJudgeScorerResult | null> {\n const criteria = normalizeWeights(params.criteria ?? DEFAULT_CRITERIA);\n const systemPrompt =\n params.systemPrompt ??\n [\n \"You are a strict, fair evaluation judge.\",\n \"Only use information provided in the task, reference, and candidate answer(s).\",\n \"For each criterion, return an integer score within the provided scale and a very brief justification (≤2 sentences).\",\n \"Return only JSON that conforms to the requested schema.\",\n \"Do not include chain-of-thought or internal reasoning; only concise justifications.\",\n ].join(\" \");\n\n const promptPrefix = params.promptPrefix ?? \"\";\n const promptSuffix = params.promptSuffix ?? \"\";\n\n return await scorePointwise(\n {\n task: params.task,\n referenceAnswer: params.referenceAnswer,\n candidateAnswer: params.candidateAnswer,\n criteria,\n meta: params.meta,\n model: params.model,\n rateLimiter: params.rateLimiter,\n systemPrompt,\n promptPrefix,\n promptSuffix,\n },\n this.provider\n );\n }\n}\n\nexport type LLMJudgeCriterion = {\n id: string;\n description: string;\n weight?: number;\n scale?: {\n min: number;\n max: number;\n };\n};\n\nexport type LLMJudgeScorerResult = BaseScorerResult & {\n provider: string;\n inputTokensUsed?: number;\n outputTokensUsed?: number;\n inputCost?: string;\n outputCost?: string;\n verdict?: \"pass\" | \"borderline\" | \"fail\";\n};\n\nexport type LLMJudgeScorerParams = {\n task: string;\n candidateAnswer: string;\n referenceAnswer?: string;\n\n model: string;\n\n /**\n * The rubric used for judging (defaults to a generic set).\n */\n criteria?: LLMJudgeCriterion[];\n\n /**\n * Optional extra context that the judge can use (constraints, references, etc.).\n */\n meta?: Record<string, unknown>;\n\n /**\n * Optional rate limiter wrapper for provider calls.\n */\n rateLimiter?: RateLimiter;\n\n /**\n * Optional prompt tweaks.\n */\n systemPrompt?: string;\n promptPrefix?: string;\n promptSuffix?: string;\n};\n\ntype PointwiseResult = {\n perCriterion: Array<{ id: string; score: number; justification: string }>;\n overall?: number;\n verdict?: \"strong-pass\" | \"pass\" | \"borderline\" | \"fail\";\n notes?: string[] | string;\n};\n\nconst DEFAULT_CRITERIA: LLMJudgeCriterion[] = [\n {\n id: \"correctness\",\n description: \"Factual correctness and alignment with the task/reference.\",\n weight: 0.5,\n scale: { min: 0, max: 5 },\n },\n {\n id: \"instruction_following\",\n description: \"Adheres to constraints, format, and intent of the task.\",\n weight: 0.3,\n scale: { min: 0, max: 5 },\n },\n {\n id: \"clarity\",\n description: \"Clear, concise, and well-structured.\",\n weight: 0.2,\n scale: { min: 0, max: 5 },\n },\n];\n\n// Standalone functions extracted from LLMJudgeScorer1 class\nasync function executeProviderCall<T>(\n call: () => Promise<T>,\n rateLimiter?: RateLimiter\n): Promise<T> {\n if (!rateLimiter) {\n return await call();\n }\n return await rateLimiter.execute(call);\n}\n\nfunction normalizeWeights(criteria: LLMJudgeCriterion[]): LLMJudgeCriterion[] {\n const sum = criteria.reduce((a, c) => a + (c.weight ?? 1), 0) || 1;\n return criteria.map((c) => ({ ...c, weight: (c.weight ?? 1) / sum }));\n}\n\nfunction renderCriteria(criteria: LLMJudgeCriterion[]): string {\n return criteria\n .map((c, i) => {\n const min = c.scale?.min ?? 0;\n const max = c.scale?.max ?? 5;\n const weight = c.weight ?? 0;\n return `${i + 1}. id=\"${c.id}\" (weight=${weight}, scale=${min}..${max}) — ${c.description}`;\n })\n .join(\"\\n\");\n}\n\nfunction computeOverallScore(\n perCriterion: Array<{ id: string; score: number; justification: string }>,\n criteria: LLMJudgeCriterion[]\n): number {\n let total = 0;\n for (const pc of perCriterion) {\n const criterion = criteria.find((c) => c.id === pc.id);\n const min = criterion?.scale?.min ?? 0;\n const max = criterion?.scale?.max ?? 5;\n const weight = criterion?.weight ?? 0;\n\n const score = Number(pc.score);\n if (!Number.isFinite(score)) continue;\n\n const clamped = Math.max(min, Math.min(max, score));\n const normalized100 =\n max === min ? 0 : ((clamped - min) / (max - min)) * 100;\n total += normalized100 * weight;\n }\n return Math.round(total);\n}\n\nfunction deriveVerdict(\n overall: number\n): \"strong-pass\" | \"pass\" | \"borderline\" | \"fail\" {\n if (overall >= 85) return \"strong-pass\";\n if (overall >= 70) return \"pass\";\n if (overall >= 60) return \"borderline\";\n return \"fail\";\n}\n\nfunction mapVerdict(\n verdict: \"strong-pass\" | \"pass\" | \"borderline\" | \"fail\"\n): \"pass\" | \"borderline\" | \"fail\" {\n if (verdict === \"borderline\") return \"borderline\";\n if (verdict === \"fail\") return \"fail\";\n return \"pass\";\n}\n\nfunction buildPointwiseExplanation(json: PointwiseResult): string | undefined {\n let explanation = \"\";\n if (json.notes && Array.isArray(json.notes)) {\n explanation += json.notes.join(\" \");\n } else if (typeof json.notes === \"string\") {\n explanation += json.notes;\n }\n\n for (const criterion of json.perCriterion ?? []) {\n explanation += [\n `\\nCriteria: ${criterion.id}`,\n `Score: ${criterion.score}`,\n `Justification: ${criterion.justification}`,\n ].join(\"\\n\");\n }\n\n return explanation.trim() ? explanation.trim() : undefined;\n}\n\nfunction extractFirstJSON<T>(text: string): T | undefined {\n const jsonText = extractFirstJSONObjectString(text);\n if (!jsonText) return;\n return parseResponseAsJSON<T>(jsonText);\n}\n\nasync function scorePointwise(\n args: {\n task: string;\n referenceAnswer?: string;\n candidateAnswer: string;\n criteria: LLMJudgeCriterion[];\n meta?: Record<string, unknown>;\n model: string;\n rateLimiter?: RateLimiter;\n systemPrompt: string;\n promptPrefix: string;\n promptSuffix: string;\n },\n provider: AbstractLLMProvider\n): Promise<LLMJudgeScorerResult | null> {\n const user = [\n args.promptPrefix ? `${args.promptPrefix}\\n` : \"\",\n `TASK:\\n${args.task}`,\n args.meta\n ? `\\n\\nADDITIONAL CONTEXT:\\n${JSON.stringify(args.meta, null, 2)}`\n : \"\",\n `\\n\\nRUBRIC:\\n${renderCriteria(args.criteria)}`,\n args.referenceAnswer\n ? `\\n\\nREFERENCE ANSWER:\\n${args.referenceAnswer}`\n : \"\\n\\nREFERENCE ANSWER:\\n<!NO REFERENCE PROVIDED!> (Judge based on task/context/rubric.)\",\n `\\n\\nCANDIDATE ANSWER:\\n${args.candidateAnswer}`,\n `\\n\\nRESPONSE FORMAT (strict JSON):`,\n JSON.stringify(\n {\n perCriterion: [\n {\n id: \"<string>\",\n score: \"<integer within scale>\",\n justification: \"<≤2 sentences>\",\n },\n ],\n overall: \"<integer 0-100>\",\n verdict: \"<strong-pass|pass|borderline|fail>\",\n notes: \"<optional list of short strings>\",\n },\n null,\n 2\n ),\n \"\\n\\nINSTRUCTIONS:\",\n \"- Compute per-criterion integer scores within each scale.\",\n \"- Compute weighted overall as a 0-100 integer: normalize weights, map each score to 0-100 by its scale, then weighted average.\",\n \"- Use verdict thresholds: ≥85 strong-pass, 70-84 pass, 60-69 borderline, <60 fail.\",\n \"- Output valid JSON only.\",\n args.promptSuffix ? `\\n${args.promptSuffix}` : \"\",\n ].join(\"\");\n\n const messages: ChatCompletionMessageParam[] = [\n { role: \"system\", content: args.systemPrompt },\n { role: \"user\", content: user },\n ];\n\n const providerResponse = await executeProviderCall(\n () =>\n provider.forward({\n model: args.model,\n messages,\n }),\n args.rateLimiter\n );\n\n const json = extractFirstJSON<PointwiseResult>(providerResponse.data);\n if (!json || !Array.isArray(json.perCriterion)) {\n return null;\n }\n\n const computedOverall = computeOverallScore(json.perCriterion, args.criteria);\n const overall =\n Number.isFinite(Number(json.overall)) && Number(json.overall) > 0\n ? Math.round(Number(json.overall))\n : computedOverall;\n\n const verdict = json.verdict ?? deriveVerdict(overall);\n\n const value = Math.min(1, Math.max(0, overall / 100));\n const explanation = buildPointwiseExplanation(json);\n\n return {\n value,\n verdict: mapVerdict(verdict),\n explanation,\n provider: provider.kind,\n inputTokensUsed: providerResponse.inputTokensUsed,\n outputTokensUsed: providerResponse.outputTokensUsed,\n inputCost: providerResponse.inputCost,\n outputCost: providerResponse.outputCost,\n metadata: {\n overall,\n verdict,\n perCriterion: json.perCriterion,\n notes: json.notes,\n scorePrompt: user,\n systemPrompt: args.systemPrompt,\n },\n };\n}\n\nfunction extractFirstJSONObjectString(text: string): string | undefined {\n const start = text.indexOf(\"{\");\n if (start === -1) return;\n\n let depth = 0;\n let inString = false;\n let escape = false;\n\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escape) {\n escape = false;\n } else if (ch === \"\\\\\") {\n escape = true;\n } else if (ch === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (ch === '\"') {\n inString = true;\n continue;\n }\n\n if (ch === \"{\") depth++;\n if (ch === \"}\") depth--;\n\n if (depth === 0) {\n return text.slice(start, i + 1);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { A as AbstractStorage } from '../json-file-ZwzLUbje.js';
|
|
2
|
+
export { c as AbstractFileStorageCodec, F as FileStorage, b as FileStorageFileHandle, J as JSONFileStorage, a as JSONFileStorageCodec } from '../json-file-ZwzLUbje.js';
|
|
3
|
+
import * as better_sqlite3 from 'better-sqlite3';
|
|
4
|
+
import 'node:fs/promises';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
declare class HttpStorage<TObject> extends AbstractStorage<TObject> {
|
|
8
|
+
protected readonly url: string;
|
|
9
|
+
protected readonly codec: AbstractHttpStorageCodec<TObject>;
|
|
10
|
+
protected readonly fetchFn: typeof fetch;
|
|
11
|
+
constructor(config: {
|
|
12
|
+
url: string;
|
|
13
|
+
codec: AbstractHttpStorageCodec<TObject>;
|
|
14
|
+
fetchFn?: typeof fetch;
|
|
15
|
+
});
|
|
16
|
+
init(_params?: unknown): Promise<void>;
|
|
17
|
+
read(_key: string, _params?: unknown): Promise<TObject | null>;
|
|
18
|
+
readAll(params?: unknown): Promise<TObject[]>;
|
|
19
|
+
write(_key: string, _value: TObject, _params?: unknown): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
declare abstract class AbstractHttpStorageCodec<TObject> {
|
|
22
|
+
abstract readAll(params: {
|
|
23
|
+
url: string;
|
|
24
|
+
response: Response;
|
|
25
|
+
params?: unknown;
|
|
26
|
+
}): Promise<TObject[]>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class SQLiteStorage<TObject> extends AbstractStorage<TObject> {
|
|
30
|
+
protected readonly path: string;
|
|
31
|
+
protected readonly codec: AbstractSQLiteStorageCodec<TObject>;
|
|
32
|
+
protected db: SQLiteDatabase | undefined;
|
|
33
|
+
constructor(config: {
|
|
34
|
+
path: string;
|
|
35
|
+
codec: AbstractSQLiteStorageCodec<TObject>;
|
|
36
|
+
});
|
|
37
|
+
init(params?: unknown): Promise<void>;
|
|
38
|
+
read(key: string, params?: unknown): Promise<TObject | null>;
|
|
39
|
+
readAll(params?: unknown): Promise<TObject[]>;
|
|
40
|
+
write(key: string, value: TObject, params?: unknown): Promise<unknown>;
|
|
41
|
+
close(): void;
|
|
42
|
+
protected assertInitialized(): asserts this is this & {
|
|
43
|
+
db: SQLiteDatabase;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
declare abstract class AbstractSQLiteStorageCodec<TObject> {
|
|
47
|
+
init?(params: {
|
|
48
|
+
db: SQLiteDatabase;
|
|
49
|
+
params?: unknown;
|
|
50
|
+
}): Promise<void> | void;
|
|
51
|
+
abstract read(params: {
|
|
52
|
+
db: SQLiteDatabase;
|
|
53
|
+
key: string;
|
|
54
|
+
params?: unknown;
|
|
55
|
+
}): Promise<TObject | null>;
|
|
56
|
+
abstract readAll(params: {
|
|
57
|
+
db: SQLiteDatabase;
|
|
58
|
+
params?: unknown;
|
|
59
|
+
}): Promise<TObject[]>;
|
|
60
|
+
abstract write(params: {
|
|
61
|
+
db: SQLiteDatabase;
|
|
62
|
+
key: string;
|
|
63
|
+
value: TObject;
|
|
64
|
+
params?: unknown;
|
|
65
|
+
}): Promise<unknown>;
|
|
66
|
+
}
|
|
67
|
+
type SQLiteDatabase = better_sqlite3.Database;
|
|
68
|
+
|
|
69
|
+
export { AbstractHttpStorageCodec, AbstractSQLiteStorageCodec, AbstractStorage, HttpStorage, SQLiteStorage };
|