@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.

Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/dist/annotation/index.d.mts +3 -0
  4. package/dist/annotation/index.d.ts +3 -0
  5. package/dist/annotation/index.js +630 -0
  6. package/dist/annotation/index.mjs +22 -0
  7. package/dist/chunk-5JRYKRSE.mjs +2791 -0
  8. package/dist/chunk-EUXXIZK3.mjs +676 -0
  9. package/dist/chunk-NBMUSATK.mjs +596 -0
  10. package/dist/chunk-PAQ2TTJJ.mjs +1105 -0
  11. package/dist/chunk-TUMNJN2S.mjs +416 -0
  12. package/dist/continuous/index.d.mts +2 -0
  13. package/dist/continuous/index.d.ts +2 -0
  14. package/dist/continuous/index.js +707 -0
  15. package/dist/continuous/index.mjs +16 -0
  16. package/dist/datasets/index.d.mts +1 -0
  17. package/dist/datasets/index.d.ts +1 -0
  18. package/dist/datasets/index.js +456 -0
  19. package/dist/datasets/index.mjs +14 -0
  20. package/dist/evaluation/index.d.mts +1 -0
  21. package/dist/evaluation/index.d.ts +1 -0
  22. package/dist/evaluation/index.js +2853 -0
  23. package/dist/evaluation/index.mjs +78 -0
  24. package/dist/feedback/index.d.mts +2 -0
  25. package/dist/feedback/index.d.ts +2 -0
  26. package/dist/feedback/index.js +1158 -0
  27. package/dist/feedback/index.mjs +40 -0
  28. package/dist/index-6Pbiq7ny.d.mts +234 -0
  29. package/dist/index-6Pbiq7ny.d.ts +234 -0
  30. package/dist/index-BNTycFEA.d.mts +479 -0
  31. package/dist/index-BNTycFEA.d.ts +479 -0
  32. package/dist/index-CTYCfWfH.d.mts +543 -0
  33. package/dist/index-CTYCfWfH.d.ts +543 -0
  34. package/dist/index-Cq5LwG_3.d.mts +322 -0
  35. package/dist/index-Cq5LwG_3.d.ts +322 -0
  36. package/dist/index-bPghFsfP.d.mts +315 -0
  37. package/dist/index-bPghFsfP.d.ts +315 -0
  38. package/dist/index.d.mts +81 -0
  39. package/dist/index.d.ts +81 -0
  40. package/dist/index.js +5962 -0
  41. package/dist/index.mjs +429 -0
  42. 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
+ };