@lov3kaizen/agentsea-evaluate 0.5.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.
Potentially problematic release.
This version of @lov3kaizen/agentsea-evaluate might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/annotation/index.d.mts +3 -0
- package/dist/annotation/index.d.ts +3 -0
- package/dist/annotation/index.js +630 -0
- package/dist/annotation/index.mjs +22 -0
- package/dist/chunk-5JRYKRSE.mjs +2791 -0
- package/dist/chunk-EUXXIZK3.mjs +676 -0
- package/dist/chunk-NBMUSATK.mjs +596 -0
- package/dist/chunk-PAQ2TTJJ.mjs +1105 -0
- package/dist/chunk-TUMNJN2S.mjs +416 -0
- package/dist/continuous/index.d.mts +2 -0
- package/dist/continuous/index.d.ts +2 -0
- package/dist/continuous/index.js +707 -0
- package/dist/continuous/index.mjs +16 -0
- package/dist/datasets/index.d.mts +1 -0
- package/dist/datasets/index.d.ts +1 -0
- package/dist/datasets/index.js +456 -0
- package/dist/datasets/index.mjs +14 -0
- package/dist/evaluation/index.d.mts +1 -0
- package/dist/evaluation/index.d.ts +1 -0
- package/dist/evaluation/index.js +2853 -0
- package/dist/evaluation/index.mjs +78 -0
- package/dist/feedback/index.d.mts +2 -0
- package/dist/feedback/index.d.ts +2 -0
- package/dist/feedback/index.js +1158 -0
- package/dist/feedback/index.mjs +40 -0
- package/dist/index-6Pbiq7ny.d.mts +234 -0
- package/dist/index-6Pbiq7ny.d.ts +234 -0
- package/dist/index-BNTycFEA.d.mts +479 -0
- package/dist/index-BNTycFEA.d.ts +479 -0
- package/dist/index-CTYCfWfH.d.mts +543 -0
- package/dist/index-CTYCfWfH.d.ts +543 -0
- package/dist/index-Cq5LwG_3.d.mts +322 -0
- package/dist/index-Cq5LwG_3.d.ts +322 -0
- package/dist/index-bPghFsfP.d.mts +315 -0
- package/dist/index-bPghFsfP.d.ts +315 -0
- package/dist/index.d.mts +81 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +5962 -0
- package/dist/index.mjs +429 -0
- package/package.json +102 -0
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
// src/feedback/collectors/BaseCollector.ts
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
var BaseCollector = class {
|
|
4
|
+
store;
|
|
5
|
+
autoTimestamp;
|
|
6
|
+
generateId;
|
|
7
|
+
validateInput;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.store = options.store;
|
|
10
|
+
this.autoTimestamp = options.autoTimestamp ?? true;
|
|
11
|
+
this.generateId = options.generateId ?? (() => nanoid());
|
|
12
|
+
this.validateInput = options.validateInput ?? true;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Collect feedback
|
|
16
|
+
*/
|
|
17
|
+
async collect(input) {
|
|
18
|
+
if (this.validateInput) {
|
|
19
|
+
this.validate(input);
|
|
20
|
+
}
|
|
21
|
+
const entry = this.transform(input);
|
|
22
|
+
if (this.store) {
|
|
23
|
+
await this.store.save(entry);
|
|
24
|
+
}
|
|
25
|
+
return entry;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Collect multiple feedback entries
|
|
29
|
+
*/
|
|
30
|
+
async collectBatch(inputs) {
|
|
31
|
+
const entries = [];
|
|
32
|
+
for (const input of inputs) {
|
|
33
|
+
if (this.validateInput) {
|
|
34
|
+
this.validate(input);
|
|
35
|
+
}
|
|
36
|
+
const entry = this.transform(input);
|
|
37
|
+
entries.push(entry);
|
|
38
|
+
}
|
|
39
|
+
if (this.store && entries.length > 0) {
|
|
40
|
+
await this.store.saveBatch(entries);
|
|
41
|
+
}
|
|
42
|
+
return entries;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Set the feedback store
|
|
46
|
+
*/
|
|
47
|
+
setStore(store) {
|
|
48
|
+
this.store = store;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the feedback store
|
|
52
|
+
*/
|
|
53
|
+
getStore() {
|
|
54
|
+
return this.store;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/feedback/collectors/ThumbsCollector.ts
|
|
59
|
+
var ThumbsCollector = class extends BaseCollector {
|
|
60
|
+
allowComment;
|
|
61
|
+
requireComment;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
super(options);
|
|
64
|
+
this.allowComment = options.allowComment ?? true;
|
|
65
|
+
this.requireComment = options.requireComment ?? "never";
|
|
66
|
+
}
|
|
67
|
+
validate(input) {
|
|
68
|
+
if (!input.responseId) {
|
|
69
|
+
throw new Error("responseId is required");
|
|
70
|
+
}
|
|
71
|
+
if (!input.input) {
|
|
72
|
+
throw new Error("input is required");
|
|
73
|
+
}
|
|
74
|
+
if (!input.output) {
|
|
75
|
+
throw new Error("output is required");
|
|
76
|
+
}
|
|
77
|
+
if (!input.feedback) {
|
|
78
|
+
throw new Error("feedback is required");
|
|
79
|
+
}
|
|
80
|
+
if (!["up", "down"].includes(input.feedback.rating)) {
|
|
81
|
+
throw new Error('feedback.rating must be "up" or "down"');
|
|
82
|
+
}
|
|
83
|
+
if (this.requireComment === "always" && !input.feedback.comment) {
|
|
84
|
+
throw new Error("Comment is required");
|
|
85
|
+
}
|
|
86
|
+
if (this.requireComment === "on_down" && input.feedback.rating === "down" && !input.feedback.comment) {
|
|
87
|
+
throw new Error("Comment is required for negative feedback");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
transform(input) {
|
|
91
|
+
return {
|
|
92
|
+
id: this.generateId(),
|
|
93
|
+
type: "thumbs",
|
|
94
|
+
responseId: input.responseId,
|
|
95
|
+
conversationId: input.conversationId,
|
|
96
|
+
input: input.input,
|
|
97
|
+
output: input.output,
|
|
98
|
+
rating: input.feedback.rating,
|
|
99
|
+
comment: this.allowComment ? input.feedback.comment : void 0,
|
|
100
|
+
userId: input.userId,
|
|
101
|
+
timestamp: this.autoTimestamp ? Date.now() : 0,
|
|
102
|
+
metadata: input.metadata
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function createThumbsCollector(options) {
|
|
107
|
+
return new ThumbsCollector(options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/feedback/collectors/RatingCollector.ts
|
|
111
|
+
var RatingCollector = class extends BaseCollector {
|
|
112
|
+
allowComment;
|
|
113
|
+
minRating;
|
|
114
|
+
maxRating;
|
|
115
|
+
requireComment;
|
|
116
|
+
lowRatingThreshold;
|
|
117
|
+
constructor(options = {}) {
|
|
118
|
+
super(options);
|
|
119
|
+
this.allowComment = options.allowComment ?? true;
|
|
120
|
+
this.minRating = options.minRating ?? 1;
|
|
121
|
+
this.maxRating = options.maxRating ?? 5;
|
|
122
|
+
this.requireComment = options.requireComment ?? "never";
|
|
123
|
+
this.lowRatingThreshold = options.lowRatingThreshold ?? 3;
|
|
124
|
+
}
|
|
125
|
+
validate(input) {
|
|
126
|
+
if (!input.responseId) {
|
|
127
|
+
throw new Error("responseId is required");
|
|
128
|
+
}
|
|
129
|
+
if (!input.input) {
|
|
130
|
+
throw new Error("input is required");
|
|
131
|
+
}
|
|
132
|
+
if (!input.output) {
|
|
133
|
+
throw new Error("output is required");
|
|
134
|
+
}
|
|
135
|
+
if (!input.feedback) {
|
|
136
|
+
throw new Error("feedback is required");
|
|
137
|
+
}
|
|
138
|
+
const rating = input.feedback.rating;
|
|
139
|
+
if (typeof rating !== "number") {
|
|
140
|
+
throw new Error("feedback.rating must be a number");
|
|
141
|
+
}
|
|
142
|
+
if (rating < this.minRating || rating > this.maxRating) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`feedback.rating must be between ${this.minRating} and ${this.maxRating}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const isLowRating = rating <= this.lowRatingThreshold;
|
|
148
|
+
const hasComment = !!input.feedback.comment?.trim();
|
|
149
|
+
if (this.requireComment === "always" && !hasComment) {
|
|
150
|
+
throw new Error("Comment is required");
|
|
151
|
+
}
|
|
152
|
+
if (this.requireComment === "on_low" && isLowRating && !hasComment) {
|
|
153
|
+
throw new Error("Comment is required for low ratings");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
transform(input) {
|
|
157
|
+
return {
|
|
158
|
+
id: this.generateId(),
|
|
159
|
+
type: "rating",
|
|
160
|
+
responseId: input.responseId,
|
|
161
|
+
conversationId: input.conversationId,
|
|
162
|
+
input: input.input,
|
|
163
|
+
output: input.output,
|
|
164
|
+
rating: input.feedback.rating,
|
|
165
|
+
maxRating: this.maxRating,
|
|
166
|
+
comment: this.allowComment ? input.feedback.comment : void 0,
|
|
167
|
+
userId: input.userId,
|
|
168
|
+
timestamp: this.autoTimestamp ? Date.now() : 0,
|
|
169
|
+
metadata: input.metadata
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
function createRatingCollector(options) {
|
|
174
|
+
return new RatingCollector(options);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/feedback/collectors/PreferenceCollector.ts
|
|
178
|
+
var PreferenceCollector = class extends BaseCollector {
|
|
179
|
+
allowTie;
|
|
180
|
+
requireReason;
|
|
181
|
+
requireConfidence;
|
|
182
|
+
minConfidence;
|
|
183
|
+
constructor(options = {}) {
|
|
184
|
+
super(options);
|
|
185
|
+
this.allowTie = options.allowTie ?? true;
|
|
186
|
+
this.requireReason = options.requireReason ?? false;
|
|
187
|
+
this.requireConfidence = options.requireConfidence ?? false;
|
|
188
|
+
this.minConfidence = options.minConfidence ?? 0;
|
|
189
|
+
}
|
|
190
|
+
validate(input) {
|
|
191
|
+
if (!input.input) {
|
|
192
|
+
throw new Error("input is required");
|
|
193
|
+
}
|
|
194
|
+
if (!input.responseA || !input.responseA.id || !input.responseA.content) {
|
|
195
|
+
throw new Error("responseA with id and content is required");
|
|
196
|
+
}
|
|
197
|
+
if (!input.responseB || !input.responseB.id || !input.responseB.content) {
|
|
198
|
+
throw new Error("responseB with id and content is required");
|
|
199
|
+
}
|
|
200
|
+
const validPreferences = this.allowTie ? ["A", "B", "tie"] : ["A", "B"];
|
|
201
|
+
if (!validPreferences.includes(input.preference)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`preference must be ${this.allowTie ? '"A", "B", or "tie"' : '"A" or "B"'}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (this.requireReason && !input.reason) {
|
|
207
|
+
throw new Error("reason is required");
|
|
208
|
+
}
|
|
209
|
+
if (this.requireConfidence && input.confidence === void 0) {
|
|
210
|
+
throw new Error("confidence is required");
|
|
211
|
+
}
|
|
212
|
+
if (input.confidence !== void 0) {
|
|
213
|
+
if (input.confidence < 0 || input.confidence > 1) {
|
|
214
|
+
throw new Error("confidence must be between 0 and 1");
|
|
215
|
+
}
|
|
216
|
+
if (input.confidence < this.minConfidence) {
|
|
217
|
+
throw new Error(`confidence must be at least ${this.minConfidence}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
transform(input) {
|
|
222
|
+
return {
|
|
223
|
+
id: this.generateId(),
|
|
224
|
+
type: "preference",
|
|
225
|
+
responseId: `${input.responseA.id}_vs_${input.responseB.id}`,
|
|
226
|
+
input: input.input,
|
|
227
|
+
output: input.preference === "A" ? input.responseA.content : input.responseB.content,
|
|
228
|
+
responseA: input.responseA,
|
|
229
|
+
responseB: input.responseB,
|
|
230
|
+
preference: input.preference,
|
|
231
|
+
reason: input.reason,
|
|
232
|
+
confidence: input.confidence,
|
|
233
|
+
userId: input.userId,
|
|
234
|
+
timestamp: this.autoTimestamp ? Date.now() : 0,
|
|
235
|
+
metadata: input.metadata
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function createPreferenceCollector(options) {
|
|
240
|
+
return new PreferenceCollector(options);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/feedback/collectors/CorrectionCollector.ts
|
|
244
|
+
var CorrectionCollector = class extends BaseCollector {
|
|
245
|
+
constructor(options = {}) {
|
|
246
|
+
super(options);
|
|
247
|
+
}
|
|
248
|
+
validate(input) {
|
|
249
|
+
if (!input.responseId) {
|
|
250
|
+
throw new Error("responseId is required");
|
|
251
|
+
}
|
|
252
|
+
if (!input.input) {
|
|
253
|
+
throw new Error("input is required");
|
|
254
|
+
}
|
|
255
|
+
if (!input.output) {
|
|
256
|
+
throw new Error("output is required");
|
|
257
|
+
}
|
|
258
|
+
if (!input.correctedOutput) {
|
|
259
|
+
throw new Error("correctedOutput is required");
|
|
260
|
+
}
|
|
261
|
+
const validTypes = ["factual", "grammar", "style", "completeness", "other"];
|
|
262
|
+
if (!validTypes.includes(input.correctionType)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`correctionType must be one of: ${validTypes.join(", ")}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (input.output === input.correctedOutput) {
|
|
268
|
+
throw new Error("correctedOutput must be different from output");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
transform(input) {
|
|
272
|
+
return {
|
|
273
|
+
id: this.generateId(),
|
|
274
|
+
type: "correction",
|
|
275
|
+
responseId: input.responseId,
|
|
276
|
+
conversationId: input.conversationId,
|
|
277
|
+
input: input.input,
|
|
278
|
+
output: input.output,
|
|
279
|
+
correctedOutput: input.correctedOutput,
|
|
280
|
+
correctionType: input.correctionType,
|
|
281
|
+
explanation: input.explanation,
|
|
282
|
+
userId: input.userId,
|
|
283
|
+
timestamp: this.autoTimestamp ? Date.now() : 0,
|
|
284
|
+
metadata: input.metadata
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
function createCorrectionCollector(options) {
|
|
289
|
+
return new CorrectionCollector(options);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/feedback/collectors/MultiCriteriaCollector.ts
|
|
293
|
+
var MultiCriteriaCollector = class extends BaseCollector {
|
|
294
|
+
criteria;
|
|
295
|
+
requireAllCriteria;
|
|
296
|
+
allowCorrections;
|
|
297
|
+
constructor(options) {
|
|
298
|
+
super(options);
|
|
299
|
+
if (!options.criteria || options.criteria.length === 0) {
|
|
300
|
+
throw new Error("At least one criterion is required");
|
|
301
|
+
}
|
|
302
|
+
this.criteria = options.criteria;
|
|
303
|
+
this.requireAllCriteria = options.requireAllCriteria ?? true;
|
|
304
|
+
this.allowCorrections = options.allowCorrections ?? true;
|
|
305
|
+
}
|
|
306
|
+
validate(input) {
|
|
307
|
+
if (!input.responseId) {
|
|
308
|
+
throw new Error("responseId is required");
|
|
309
|
+
}
|
|
310
|
+
if (!input.input) {
|
|
311
|
+
throw new Error("input is required");
|
|
312
|
+
}
|
|
313
|
+
if (!input.output) {
|
|
314
|
+
throw new Error("output is required");
|
|
315
|
+
}
|
|
316
|
+
if (!input.ratings || Object.keys(input.ratings).length === 0) {
|
|
317
|
+
throw new Error("ratings is required");
|
|
318
|
+
}
|
|
319
|
+
const providedCriteria = Object.keys(input.ratings);
|
|
320
|
+
const definedCriteria = this.criteria.map((c) => c.name);
|
|
321
|
+
for (const name of providedCriteria) {
|
|
322
|
+
if (!definedCriteria.includes(name)) {
|
|
323
|
+
throw new Error(`Unknown criterion: ${name}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (this.requireAllCriteria) {
|
|
327
|
+
for (const name of definedCriteria) {
|
|
328
|
+
if (!(name in input.ratings)) {
|
|
329
|
+
throw new Error(`Missing required criterion: ${name}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const [name, rating] of Object.entries(input.ratings)) {
|
|
334
|
+
const criterion = this.criteria.find((c) => c.name === name);
|
|
335
|
+
if (criterion) {
|
|
336
|
+
const [min, max] = criterion.scale;
|
|
337
|
+
if (rating < min || rating > max) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Rating for ${name} must be between ${min} and ${max}`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (input.overallRating !== void 0) {
|
|
345
|
+
if (!Number.isInteger(input.overallRating) || input.overallRating < 1 || input.overallRating > 5) {
|
|
346
|
+
throw new Error("overallRating must be an integer between 1 and 5");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
transform(input) {
|
|
351
|
+
const criteriaRatings = [];
|
|
352
|
+
for (const [name, rating] of Object.entries(input.ratings)) {
|
|
353
|
+
const criterionRating = { name, rating };
|
|
354
|
+
if (this.allowCorrections && input.corrections?.[name]) {
|
|
355
|
+
criterionRating.correction = input.corrections[name];
|
|
356
|
+
}
|
|
357
|
+
criteriaRatings.push(criterionRating);
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
id: this.generateId(),
|
|
361
|
+
type: "multi_criteria",
|
|
362
|
+
responseId: input.responseId,
|
|
363
|
+
conversationId: input.conversationId,
|
|
364
|
+
input: input.input,
|
|
365
|
+
output: input.output,
|
|
366
|
+
criteria: criteriaRatings,
|
|
367
|
+
overallRating: input.overallRating,
|
|
368
|
+
comment: input.comment,
|
|
369
|
+
userId: input.userId,
|
|
370
|
+
timestamp: this.autoTimestamp ? Date.now() : 0,
|
|
371
|
+
metadata: input.metadata
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get criterion definitions
|
|
376
|
+
*/
|
|
377
|
+
getCriteria() {
|
|
378
|
+
return [...this.criteria];
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Add a criterion
|
|
382
|
+
*/
|
|
383
|
+
addCriterion(criterion) {
|
|
384
|
+
if (this.criteria.some((c) => c.name === criterion.name)) {
|
|
385
|
+
throw new Error(`Criterion ${criterion.name} already exists`);
|
|
386
|
+
}
|
|
387
|
+
this.criteria.push(criterion);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Remove a criterion
|
|
391
|
+
*/
|
|
392
|
+
removeCriterion(name) {
|
|
393
|
+
const index = this.criteria.findIndex((c) => c.name === name);
|
|
394
|
+
if (index >= 0) {
|
|
395
|
+
this.criteria.splice(index, 1);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
function createMultiCriteriaCollector(options) {
|
|
402
|
+
return new MultiCriteriaCollector(options);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/feedback/FeedbackStore.ts
|
|
406
|
+
var MemoryFeedbackStore = class {
|
|
407
|
+
entries = /* @__PURE__ */ new Map();
|
|
408
|
+
save(entry) {
|
|
409
|
+
this.entries.set(entry.id, entry);
|
|
410
|
+
return Promise.resolve(entry.id);
|
|
411
|
+
}
|
|
412
|
+
saveBatch(entries) {
|
|
413
|
+
const ids = [];
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
this.entries.set(entry.id, entry);
|
|
416
|
+
ids.push(entry.id);
|
|
417
|
+
}
|
|
418
|
+
return Promise.resolve(ids);
|
|
419
|
+
}
|
|
420
|
+
get(id) {
|
|
421
|
+
return Promise.resolve(this.entries.get(id) ?? null);
|
|
422
|
+
}
|
|
423
|
+
query(options) {
|
|
424
|
+
let entries = Array.from(this.entries.values());
|
|
425
|
+
if (options.type) {
|
|
426
|
+
const types = Array.isArray(options.type) ? options.type : [options.type];
|
|
427
|
+
entries = entries.filter((e) => types.includes(e.type));
|
|
428
|
+
}
|
|
429
|
+
if (options.userId) {
|
|
430
|
+
entries = entries.filter((e) => e.userId === options.userId);
|
|
431
|
+
}
|
|
432
|
+
if (options.conversationId) {
|
|
433
|
+
entries = entries.filter(
|
|
434
|
+
(e) => e.conversationId === options.conversationId
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (options.responseId) {
|
|
438
|
+
entries = entries.filter((e) => e.responseId === options.responseId);
|
|
439
|
+
}
|
|
440
|
+
if (options.startTime !== void 0) {
|
|
441
|
+
entries = entries.filter((e) => e.timestamp >= options.startTime);
|
|
442
|
+
}
|
|
443
|
+
if (options.endTime !== void 0) {
|
|
444
|
+
entries = entries.filter((e) => e.timestamp <= options.endTime);
|
|
445
|
+
}
|
|
446
|
+
if (options.metadata) {
|
|
447
|
+
entries = entries.filter((e) => {
|
|
448
|
+
if (!e.metadata) return false;
|
|
449
|
+
for (const [key, value] of Object.entries(options.metadata)) {
|
|
450
|
+
if (e.metadata[key] !== value) return false;
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const total = entries.length;
|
|
456
|
+
if (options.orderBy) {
|
|
457
|
+
entries.sort((a, b) => {
|
|
458
|
+
let aVal;
|
|
459
|
+
let bVal;
|
|
460
|
+
if (options.orderBy === "timestamp") {
|
|
461
|
+
aVal = a.timestamp;
|
|
462
|
+
bVal = b.timestamp;
|
|
463
|
+
} else if (options.orderBy === "rating") {
|
|
464
|
+
aVal = this.getRating(a);
|
|
465
|
+
bVal = this.getRating(b);
|
|
466
|
+
} else {
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
return options.orderDir === "desc" ? bVal - aVal : aVal - bVal;
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const offset = options.offset ?? 0;
|
|
473
|
+
const limit = options.limit ?? 100;
|
|
474
|
+
entries = entries.slice(offset, offset + limit);
|
|
475
|
+
return Promise.resolve({
|
|
476
|
+
entries,
|
|
477
|
+
total,
|
|
478
|
+
hasMore: offset + entries.length < total
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
delete(id) {
|
|
482
|
+
return Promise.resolve(this.entries.delete(id));
|
|
483
|
+
}
|
|
484
|
+
clear() {
|
|
485
|
+
this.entries.clear();
|
|
486
|
+
return Promise.resolve();
|
|
487
|
+
}
|
|
488
|
+
close() {
|
|
489
|
+
return Promise.resolve();
|
|
490
|
+
}
|
|
491
|
+
getRating(entry) {
|
|
492
|
+
switch (entry.type) {
|
|
493
|
+
case "thumbs":
|
|
494
|
+
return entry.rating === "up" ? 1 : 0;
|
|
495
|
+
case "rating":
|
|
496
|
+
return entry.rating;
|
|
497
|
+
case "multi_criteria":
|
|
498
|
+
return entry.overallRating ?? 0;
|
|
499
|
+
default:
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
var SQLiteFeedbackStore = class {
|
|
505
|
+
constructor(config) {
|
|
506
|
+
this.config = config;
|
|
507
|
+
this.tableName = config.tableName ?? "feedback";
|
|
508
|
+
}
|
|
509
|
+
db;
|
|
510
|
+
tableName;
|
|
511
|
+
initialized = false;
|
|
512
|
+
async init() {
|
|
513
|
+
if (this.initialized) return;
|
|
514
|
+
try {
|
|
515
|
+
const BetterSqlite3 = await import("better-sqlite3");
|
|
516
|
+
this.db = new BetterSqlite3.default(this.config.path);
|
|
517
|
+
this.db.exec(`
|
|
518
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
519
|
+
id TEXT PRIMARY KEY,
|
|
520
|
+
type TEXT NOT NULL,
|
|
521
|
+
response_id TEXT NOT NULL,
|
|
522
|
+
conversation_id TEXT,
|
|
523
|
+
input TEXT NOT NULL,
|
|
524
|
+
output TEXT NOT NULL,
|
|
525
|
+
user_id TEXT,
|
|
526
|
+
timestamp INTEGER NOT NULL,
|
|
527
|
+
data TEXT NOT NULL,
|
|
528
|
+
metadata TEXT
|
|
529
|
+
);
|
|
530
|
+
CREATE INDEX IF NOT EXISTS idx_type ON ${this.tableName}(type);
|
|
531
|
+
CREATE INDEX IF NOT EXISTS idx_timestamp ON ${this.tableName}(timestamp);
|
|
532
|
+
CREATE INDEX IF NOT EXISTS idx_user_id ON ${this.tableName}(user_id);
|
|
533
|
+
CREATE INDEX IF NOT EXISTS idx_response_id ON ${this.tableName}(response_id);
|
|
534
|
+
`);
|
|
535
|
+
this.initialized = true;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
`Failed to initialize SQLite store: ${error.message}`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async save(entry) {
|
|
543
|
+
await this.ensureInitialized();
|
|
544
|
+
const stmt = this.db.prepare(`
|
|
545
|
+
INSERT OR REPLACE INTO ${this.tableName}
|
|
546
|
+
(id, type, response_id, conversation_id, input, output, user_id, timestamp, data, metadata)
|
|
547
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
548
|
+
`);
|
|
549
|
+
stmt.run(
|
|
550
|
+
entry.id,
|
|
551
|
+
entry.type,
|
|
552
|
+
entry.responseId,
|
|
553
|
+
entry.conversationId ?? null,
|
|
554
|
+
entry.input,
|
|
555
|
+
entry.output,
|
|
556
|
+
entry.userId ?? null,
|
|
557
|
+
entry.timestamp,
|
|
558
|
+
JSON.stringify(entry),
|
|
559
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null
|
|
560
|
+
);
|
|
561
|
+
return entry.id;
|
|
562
|
+
}
|
|
563
|
+
async saveBatch(entries) {
|
|
564
|
+
const ids = [];
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
const id = await this.save(entry);
|
|
567
|
+
ids.push(id);
|
|
568
|
+
}
|
|
569
|
+
return ids;
|
|
570
|
+
}
|
|
571
|
+
async get(id) {
|
|
572
|
+
await this.ensureInitialized();
|
|
573
|
+
const stmt = this.db.prepare(`SELECT data FROM ${this.tableName} WHERE id = ?`);
|
|
574
|
+
const row = stmt.get(id);
|
|
575
|
+
return row ? JSON.parse(row.data) : null;
|
|
576
|
+
}
|
|
577
|
+
async query(options) {
|
|
578
|
+
await this.ensureInitialized();
|
|
579
|
+
const conditions = [];
|
|
580
|
+
const params = [];
|
|
581
|
+
if (options.type) {
|
|
582
|
+
const types = Array.isArray(options.type) ? options.type : [options.type];
|
|
583
|
+
conditions.push(`type IN (${types.map(() => "?").join(", ")})`);
|
|
584
|
+
params.push(...types);
|
|
585
|
+
}
|
|
586
|
+
if (options.userId) {
|
|
587
|
+
conditions.push("user_id = ?");
|
|
588
|
+
params.push(options.userId);
|
|
589
|
+
}
|
|
590
|
+
if (options.conversationId) {
|
|
591
|
+
conditions.push("conversation_id = ?");
|
|
592
|
+
params.push(options.conversationId);
|
|
593
|
+
}
|
|
594
|
+
if (options.responseId) {
|
|
595
|
+
conditions.push("response_id = ?");
|
|
596
|
+
params.push(options.responseId);
|
|
597
|
+
}
|
|
598
|
+
if (options.startTime !== void 0) {
|
|
599
|
+
conditions.push("timestamp >= ?");
|
|
600
|
+
params.push(options.startTime);
|
|
601
|
+
}
|
|
602
|
+
if (options.endTime !== void 0) {
|
|
603
|
+
conditions.push("timestamp <= ?");
|
|
604
|
+
params.push(options.endTime);
|
|
605
|
+
}
|
|
606
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
607
|
+
const countStmt = this.db.prepare(`SELECT COUNT(*) as count FROM ${this.tableName} ${whereClause}`);
|
|
608
|
+
const { count: total } = countStmt.get(...params);
|
|
609
|
+
const orderBy = options.orderBy === "rating" ? "timestamp" : options.orderBy ?? "timestamp";
|
|
610
|
+
const orderDir = options.orderDir ?? "desc";
|
|
611
|
+
const limit = options.limit ?? 100;
|
|
612
|
+
const offset = options.offset ?? 0;
|
|
613
|
+
const selectStmt = this.db.prepare(`
|
|
614
|
+
SELECT data FROM ${this.tableName}
|
|
615
|
+
${whereClause}
|
|
616
|
+
ORDER BY ${orderBy} ${orderDir}
|
|
617
|
+
LIMIT ? OFFSET ?
|
|
618
|
+
`);
|
|
619
|
+
const rows = selectStmt.all(...params, limit, offset);
|
|
620
|
+
const entries = rows.map((row) => JSON.parse(row.data));
|
|
621
|
+
return {
|
|
622
|
+
entries,
|
|
623
|
+
total,
|
|
624
|
+
hasMore: offset + entries.length < total
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
async delete(id) {
|
|
628
|
+
await this.ensureInitialized();
|
|
629
|
+
const stmt = this.db.prepare(`DELETE FROM ${this.tableName} WHERE id = ?`);
|
|
630
|
+
const result = stmt.run(id);
|
|
631
|
+
return result.changes > 0;
|
|
632
|
+
}
|
|
633
|
+
async clear() {
|
|
634
|
+
await this.ensureInitialized();
|
|
635
|
+
this.db.exec(
|
|
636
|
+
`DELETE FROM ${this.tableName}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
close() {
|
|
640
|
+
if (this.db) {
|
|
641
|
+
this.db.close();
|
|
642
|
+
}
|
|
643
|
+
this.initialized = false;
|
|
644
|
+
return Promise.resolve();
|
|
645
|
+
}
|
|
646
|
+
async ensureInitialized() {
|
|
647
|
+
if (!this.initialized) {
|
|
648
|
+
await this.init();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
function createFeedbackStore(config) {
|
|
653
|
+
switch (config.type) {
|
|
654
|
+
case "memory":
|
|
655
|
+
return new MemoryFeedbackStore();
|
|
656
|
+
case "sqlite":
|
|
657
|
+
if (!config.path) {
|
|
658
|
+
throw new Error("SQLite store requires a path");
|
|
659
|
+
}
|
|
660
|
+
return new SQLiteFeedbackStore({
|
|
661
|
+
path: config.path,
|
|
662
|
+
tableName: config.tableName
|
|
663
|
+
});
|
|
664
|
+
default:
|
|
665
|
+
throw new Error(`Unknown store type: ${config.type}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/feedback/FeedbackAggregator.ts
|
|
670
|
+
var FeedbackAggregator = class {
|
|
671
|
+
constructor(store) {
|
|
672
|
+
this.store = store;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Aggregate feedback data
|
|
676
|
+
*/
|
|
677
|
+
async aggregate(options) {
|
|
678
|
+
const { entries } = await this.store.query({
|
|
679
|
+
startTime: options.timeRange?.start,
|
|
680
|
+
endTime: options.timeRange?.end,
|
|
681
|
+
...options.filters,
|
|
682
|
+
limit: 1e5
|
|
683
|
+
// Large limit for aggregation
|
|
684
|
+
});
|
|
685
|
+
if (entries.length === 0) {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
const groups = this.groupEntries(entries, options.groupBy);
|
|
689
|
+
const results = [];
|
|
690
|
+
for (const [groupKey, groupEntries] of groups) {
|
|
691
|
+
const metrics = this.calculateMetrics(groupEntries, options.metrics);
|
|
692
|
+
results.push({
|
|
693
|
+
groupKey,
|
|
694
|
+
metrics,
|
|
695
|
+
count: groupEntries.length
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
return results;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get summary statistics
|
|
702
|
+
*/
|
|
703
|
+
async getSummary(options) {
|
|
704
|
+
const { entries } = await this.store.query({
|
|
705
|
+
startTime: options?.startTime,
|
|
706
|
+
endTime: options?.endTime,
|
|
707
|
+
limit: 1e5
|
|
708
|
+
});
|
|
709
|
+
const byType = {};
|
|
710
|
+
let thumbsUp = 0;
|
|
711
|
+
let thumbsTotal = 0;
|
|
712
|
+
let ratingSum = 0;
|
|
713
|
+
let ratingCount = 0;
|
|
714
|
+
const prefDist = { A: 0, B: 0, tie: 0 };
|
|
715
|
+
let corrections = 0;
|
|
716
|
+
for (const entry of entries) {
|
|
717
|
+
byType[entry.type] = (byType[entry.type] ?? 0) + 1;
|
|
718
|
+
switch (entry.type) {
|
|
719
|
+
case "thumbs": {
|
|
720
|
+
const thumbs = entry;
|
|
721
|
+
thumbsTotal++;
|
|
722
|
+
if (thumbs.rating === "up") thumbsUp++;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
case "rating": {
|
|
726
|
+
const rating = entry;
|
|
727
|
+
ratingSum += rating.rating;
|
|
728
|
+
ratingCount++;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
case "preference": {
|
|
732
|
+
const pref = entry;
|
|
733
|
+
prefDist[pref.preference]++;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
case "correction":
|
|
737
|
+
corrections++;
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
totalCount: entries.length,
|
|
743
|
+
byType,
|
|
744
|
+
thumbsUpRate: thumbsTotal > 0 ? thumbsUp / thumbsTotal : 0,
|
|
745
|
+
avgRating: ratingCount > 0 ? ratingSum / ratingCount : 0,
|
|
746
|
+
preferenceDistribution: prefDist,
|
|
747
|
+
correctionRate: entries.length > 0 ? corrections / entries.length : 0
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Get trending metrics over time
|
|
752
|
+
*/
|
|
753
|
+
async getTrends(options) {
|
|
754
|
+
const { entries } = await this.store.query({
|
|
755
|
+
startTime: options.startTime,
|
|
756
|
+
endTime: options.endTime,
|
|
757
|
+
limit: 1e5
|
|
758
|
+
});
|
|
759
|
+
const intervalMs = this.getIntervalMs(options.interval);
|
|
760
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
761
|
+
for (const entry of entries) {
|
|
762
|
+
const bucket = Math.floor(entry.timestamp / intervalMs) * intervalMs;
|
|
763
|
+
if (!buckets.has(bucket)) {
|
|
764
|
+
buckets.set(bucket, []);
|
|
765
|
+
}
|
|
766
|
+
buckets.get(bucket).push(entry);
|
|
767
|
+
}
|
|
768
|
+
const trends = [];
|
|
769
|
+
const sortedBuckets = Array.from(buckets.entries()).sort(
|
|
770
|
+
(a, b) => a[0] - b[0]
|
|
771
|
+
);
|
|
772
|
+
for (const [timestamp, bucketEntries] of sortedBuckets) {
|
|
773
|
+
const metrics = this.calculateMetrics(bucketEntries, [options.metric]);
|
|
774
|
+
trends.push({
|
|
775
|
+
timestamp,
|
|
776
|
+
value: metrics[options.metric] ?? 0,
|
|
777
|
+
count: bucketEntries.length
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return trends;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Group entries by field
|
|
784
|
+
*/
|
|
785
|
+
groupEntries(entries, groupBy) {
|
|
786
|
+
const groups = /* @__PURE__ */ new Map();
|
|
787
|
+
if (!groupBy) {
|
|
788
|
+
groups.set("all", entries);
|
|
789
|
+
return groups;
|
|
790
|
+
}
|
|
791
|
+
for (const entry of entries) {
|
|
792
|
+
let key;
|
|
793
|
+
switch (groupBy) {
|
|
794
|
+
case "model":
|
|
795
|
+
key = entry.metadata?.model ?? "unknown";
|
|
796
|
+
break;
|
|
797
|
+
case "userId":
|
|
798
|
+
key = entry.userId ?? "anonymous";
|
|
799
|
+
break;
|
|
800
|
+
case "hour":
|
|
801
|
+
key = new Date(entry.timestamp).toISOString().slice(0, 13);
|
|
802
|
+
break;
|
|
803
|
+
case "day":
|
|
804
|
+
key = new Date(entry.timestamp).toISOString().slice(0, 10);
|
|
805
|
+
break;
|
|
806
|
+
case "week":
|
|
807
|
+
key = this.getWeekKey(entry.timestamp);
|
|
808
|
+
break;
|
|
809
|
+
case "month":
|
|
810
|
+
key = new Date(entry.timestamp).toISOString().slice(0, 7);
|
|
811
|
+
break;
|
|
812
|
+
default:
|
|
813
|
+
key = "all";
|
|
814
|
+
}
|
|
815
|
+
if (!groups.has(key)) {
|
|
816
|
+
groups.set(key, []);
|
|
817
|
+
}
|
|
818
|
+
groups.get(key).push(entry);
|
|
819
|
+
}
|
|
820
|
+
return groups;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Calculate metrics for a group of entries
|
|
824
|
+
*/
|
|
825
|
+
calculateMetrics(entries, metrics) {
|
|
826
|
+
const result = {};
|
|
827
|
+
for (const metric of metrics) {
|
|
828
|
+
result[metric] = this.calculateSingleMetric(entries, metric);
|
|
829
|
+
}
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Calculate a single metric
|
|
834
|
+
*/
|
|
835
|
+
calculateSingleMetric(entries, metric) {
|
|
836
|
+
switch (metric) {
|
|
837
|
+
case "count":
|
|
838
|
+
return entries.length;
|
|
839
|
+
case "thumbsUpRate": {
|
|
840
|
+
const thumbs = entries.filter((e) => e.type === "thumbs");
|
|
841
|
+
if (thumbs.length === 0) return 0;
|
|
842
|
+
const ups = thumbs.filter((t) => t.rating === "up").length;
|
|
843
|
+
return ups / thumbs.length;
|
|
844
|
+
}
|
|
845
|
+
case "avgRating": {
|
|
846
|
+
const ratings = entries.filter((e) => e.type === "rating");
|
|
847
|
+
if (ratings.length === 0) return 0;
|
|
848
|
+
const sum = ratings.reduce((s, r) => s + r.rating, 0);
|
|
849
|
+
return sum / ratings.length;
|
|
850
|
+
}
|
|
851
|
+
case "correctionRate": {
|
|
852
|
+
if (entries.length === 0) return 0;
|
|
853
|
+
const corrections = entries.filter(
|
|
854
|
+
(e) => e.type === "correction"
|
|
855
|
+
).length;
|
|
856
|
+
return corrections / entries.length;
|
|
857
|
+
}
|
|
858
|
+
case "preferenceWinRate": {
|
|
859
|
+
const prefs = entries.filter((e) => e.type === "preference");
|
|
860
|
+
if (prefs.length === 0) return 0;
|
|
861
|
+
const wins = prefs.filter((p) => p.preference === "A").length;
|
|
862
|
+
return wins / prefs.length;
|
|
863
|
+
}
|
|
864
|
+
case "avgCriteriaRating": {
|
|
865
|
+
const multi = entries.filter((e) => e.type === "multi_criteria");
|
|
866
|
+
if (multi.length === 0) return 0;
|
|
867
|
+
let totalRatings = 0;
|
|
868
|
+
let totalCount = 0;
|
|
869
|
+
for (const m of multi) {
|
|
870
|
+
for (const c of m.criteria) {
|
|
871
|
+
totalRatings += c.rating;
|
|
872
|
+
totalCount++;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return totalCount > 0 ? totalRatings / totalCount : 0;
|
|
876
|
+
}
|
|
877
|
+
default:
|
|
878
|
+
return 0;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Get week key from timestamp
|
|
883
|
+
*/
|
|
884
|
+
getWeekKey(timestamp) {
|
|
885
|
+
const date = new Date(timestamp);
|
|
886
|
+
const year = date.getFullYear();
|
|
887
|
+
const week = this.getWeekNumber(date);
|
|
888
|
+
return `${year}-W${week.toString().padStart(2, "0")}`;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Get week number
|
|
892
|
+
*/
|
|
893
|
+
getWeekNumber(date) {
|
|
894
|
+
const d = new Date(
|
|
895
|
+
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
|
896
|
+
);
|
|
897
|
+
const dayNum = d.getUTCDay() || 7;
|
|
898
|
+
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
899
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
900
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Get interval in milliseconds
|
|
904
|
+
*/
|
|
905
|
+
getIntervalMs(interval) {
|
|
906
|
+
switch (interval) {
|
|
907
|
+
case "hour":
|
|
908
|
+
return 36e5;
|
|
909
|
+
case "day":
|
|
910
|
+
return 864e5;
|
|
911
|
+
case "week":
|
|
912
|
+
return 6048e5;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
function createFeedbackAggregator(store) {
|
|
917
|
+
return new FeedbackAggregator(store);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/feedback/FeedbackExporter.ts
|
|
921
|
+
import * as fs from "fs/promises";
|
|
922
|
+
var FeedbackExporter = class {
|
|
923
|
+
constructor(store) {
|
|
924
|
+
this.store = store;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Export feedback to string
|
|
928
|
+
*/
|
|
929
|
+
async exportToString(options) {
|
|
930
|
+
const entries = await this.getEntries(options.query);
|
|
931
|
+
const filtered = this.filterFields(
|
|
932
|
+
entries,
|
|
933
|
+
options.fields,
|
|
934
|
+
options.includeMetadata
|
|
935
|
+
);
|
|
936
|
+
switch (options.format) {
|
|
937
|
+
case "json":
|
|
938
|
+
return JSON.stringify(filtered, null, 2);
|
|
939
|
+
case "jsonl":
|
|
940
|
+
return filtered.map((e) => JSON.stringify(e)).join("\n");
|
|
941
|
+
case "csv":
|
|
942
|
+
return this.toCSV(filtered);
|
|
943
|
+
default:
|
|
944
|
+
throw new Error(`Unknown export format: ${String(options.format)}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Export feedback to file
|
|
949
|
+
*/
|
|
950
|
+
async exportToFile(path, options) {
|
|
951
|
+
const content = await this.exportToString(options);
|
|
952
|
+
await fs.writeFile(path, content, "utf-8");
|
|
953
|
+
const entries = await this.getEntries(options.query);
|
|
954
|
+
return entries.length;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Stream export for large datasets
|
|
958
|
+
*/
|
|
959
|
+
async *exportStream(options, batchSize = 1e3) {
|
|
960
|
+
let offset = 0;
|
|
961
|
+
let hasMore = true;
|
|
962
|
+
while (hasMore) {
|
|
963
|
+
const { entries, hasMore: more } = await this.store.query({
|
|
964
|
+
...options.query,
|
|
965
|
+
limit: batchSize,
|
|
966
|
+
offset
|
|
967
|
+
});
|
|
968
|
+
hasMore = more;
|
|
969
|
+
offset += entries.length;
|
|
970
|
+
const filtered = this.filterFields(
|
|
971
|
+
entries,
|
|
972
|
+
options.fields,
|
|
973
|
+
options.includeMetadata
|
|
974
|
+
);
|
|
975
|
+
if (options.format === "jsonl") {
|
|
976
|
+
for (const entry of filtered) {
|
|
977
|
+
yield JSON.stringify(entry) + "\n";
|
|
978
|
+
}
|
|
979
|
+
} else if (options.format === "csv") {
|
|
980
|
+
if (offset === batchSize) {
|
|
981
|
+
yield this.toCSV(filtered);
|
|
982
|
+
} else {
|
|
983
|
+
yield this.toCSVRows(filtered);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Get entries from store
|
|
990
|
+
*/
|
|
991
|
+
async getEntries(query) {
|
|
992
|
+
const { entries } = await this.store.query({
|
|
993
|
+
...query,
|
|
994
|
+
limit: query?.limit ?? 1e5
|
|
995
|
+
});
|
|
996
|
+
return entries;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Filter fields from entries
|
|
1000
|
+
*/
|
|
1001
|
+
filterFields(entries, fields, includeMetadata = true) {
|
|
1002
|
+
return entries.map((entry) => {
|
|
1003
|
+
const entryRecord = entry;
|
|
1004
|
+
if (fields && fields.length > 0) {
|
|
1005
|
+
const filtered = {};
|
|
1006
|
+
for (const field of fields) {
|
|
1007
|
+
if (field in entryRecord) {
|
|
1008
|
+
filtered[field] = entryRecord[field];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return filtered;
|
|
1012
|
+
}
|
|
1013
|
+
if (!includeMetadata) {
|
|
1014
|
+
const { metadata: _metadata, ...rest } = entryRecord;
|
|
1015
|
+
return rest;
|
|
1016
|
+
}
|
|
1017
|
+
return entryRecord;
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Convert entries to CSV
|
|
1022
|
+
*/
|
|
1023
|
+
toCSV(entries) {
|
|
1024
|
+
if (entries.length === 0) return "";
|
|
1025
|
+
const headers = this.getCSVHeaders(entries);
|
|
1026
|
+
const headerRow = headers.map((h) => this.escapeCSV(h)).join(",");
|
|
1027
|
+
const rows = entries.map((entry) => {
|
|
1028
|
+
return headers.map((header) => {
|
|
1029
|
+
const value = entry[header];
|
|
1030
|
+
return this.escapeCSV(this.formatCSVValue(value));
|
|
1031
|
+
}).join(",");
|
|
1032
|
+
});
|
|
1033
|
+
return [headerRow, ...rows].join("\n");
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Convert entries to CSV rows (no header)
|
|
1037
|
+
*/
|
|
1038
|
+
toCSVRows(entries) {
|
|
1039
|
+
if (entries.length === 0) return "";
|
|
1040
|
+
const headers = this.getCSVHeaders(entries);
|
|
1041
|
+
return entries.map((entry) => {
|
|
1042
|
+
return headers.map((header) => {
|
|
1043
|
+
const value = entry[header];
|
|
1044
|
+
return this.escapeCSV(this.formatCSVValue(value));
|
|
1045
|
+
}).join(",");
|
|
1046
|
+
}).join("\n");
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Get CSV headers from entries
|
|
1050
|
+
*/
|
|
1051
|
+
getCSVHeaders(entries) {
|
|
1052
|
+
const headers = /* @__PURE__ */ new Set();
|
|
1053
|
+
for (const entry of entries) {
|
|
1054
|
+
for (const key of Object.keys(entry)) {
|
|
1055
|
+
headers.add(key);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return Array.from(headers);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Format value for CSV
|
|
1062
|
+
*/
|
|
1063
|
+
formatCSVValue(value) {
|
|
1064
|
+
if (value === null || value === void 0) {
|
|
1065
|
+
return "";
|
|
1066
|
+
}
|
|
1067
|
+
if (typeof value === "object") {
|
|
1068
|
+
return JSON.stringify(value);
|
|
1069
|
+
}
|
|
1070
|
+
return String(value);
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Escape CSV value
|
|
1074
|
+
*/
|
|
1075
|
+
escapeCSV(value) {
|
|
1076
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
1077
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
1078
|
+
}
|
|
1079
|
+
return value;
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
function createFeedbackExporter(store) {
|
|
1083
|
+
return new FeedbackExporter(store);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export {
|
|
1087
|
+
BaseCollector,
|
|
1088
|
+
ThumbsCollector,
|
|
1089
|
+
createThumbsCollector,
|
|
1090
|
+
RatingCollector,
|
|
1091
|
+
createRatingCollector,
|
|
1092
|
+
PreferenceCollector,
|
|
1093
|
+
createPreferenceCollector,
|
|
1094
|
+
CorrectionCollector,
|
|
1095
|
+
createCorrectionCollector,
|
|
1096
|
+
MultiCriteriaCollector,
|
|
1097
|
+
createMultiCriteriaCollector,
|
|
1098
|
+
MemoryFeedbackStore,
|
|
1099
|
+
SQLiteFeedbackStore,
|
|
1100
|
+
createFeedbackStore,
|
|
1101
|
+
FeedbackAggregator,
|
|
1102
|
+
createFeedbackAggregator,
|
|
1103
|
+
FeedbackExporter,
|
|
1104
|
+
createFeedbackExporter
|
|
1105
|
+
};
|