@crowandraven/evalkeeper 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +6 -2
- package/bin/evalkeeper.mjs +9 -0
- package/dist/attention.d.ts +49 -0
- package/dist/attention.js +49 -0
- package/dist/attention.js.map +1 -0
- package/dist/board.d.ts +57 -0
- package/dist/board.js +2 -0
- package/dist/board.js.map +1 -0
- package/dist/cli/addPages.d.ts +79 -0
- package/dist/cli/addPages.js +181 -0
- package/dist/cli/addPages.js.map +1 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +115 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/common.d.ts +72 -0
- package/dist/common.js +276 -0
- package/dist/common.js.map +1 -0
- package/dist/compare.d.ts +85 -0
- package/dist/compare.js +185 -0
- package/dist/compare.js.map +1 -0
- package/dist/decisions.d.ts +84 -0
- package/dist/decisions.js +146 -0
- package/dist/decisions.js.map +1 -0
- package/dist/feedback.d.ts +114 -0
- package/dist/feedback.js +116 -0
- package/dist/feedback.js.map +1 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.js +22 -0
- package/dist/format.js.map +1 -0
- package/dist/glossary.d.ts +46 -0
- package/dist/glossary.js +45 -0
- package/dist/glossary.js.map +1 -0
- package/dist/health.d.ts +40 -0
- package/dist/health.js +101 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +15 -0
- package/dist/labels.js.map +1 -0
- package/dist/legs.d.ts +23 -0
- package/dist/legs.js +22 -0
- package/dist/legs.js.map +1 -0
- package/dist/memoryStore.d.ts +21 -0
- package/dist/memoryStore.js +228 -0
- package/dist/memoryStore.js.map +1 -0
- package/dist/models.d.ts +76 -0
- package/dist/models.js +56 -0
- package/dist/models.js.map +1 -0
- package/dist/prune.d.ts +48 -0
- package/dist/prune.js +77 -0
- package/dist/prune.js.map +1 -0
- package/dist/quickSet.d.ts +26 -0
- package/dist/quickSet.js +44 -0
- package/dist/quickSet.js.map +1 -0
- package/dist/runner.d.ts +202 -0
- package/dist/runner.js +734 -0
- package/dist/runner.js.map +1 -0
- package/dist/settings.d.ts +196 -0
- package/dist/settings.js +272 -0
- package/dist/settings.js.map +1 -0
- package/dist/stableJson.d.ts +11 -0
- package/dist/stableJson.js +15 -0
- package/dist/stableJson.js.map +1 -0
- package/dist/stats.d.ts +98 -0
- package/dist/stats.js +119 -0
- package/dist/stats.js.map +1 -0
- package/dist/store.d.ts +151 -0
- package/dist/store.js +3 -0
- package/dist/store.js.map +1 -0
- package/dist/suggest.d.ts +75 -0
- package/dist/suggest.js +152 -0
- package/dist/suggest.js.map +1 -0
- package/dist/summary.d.ts +51 -0
- package/dist/summary.js +61 -0
- package/dist/summary.js.map +1 -0
- package/dist/testing.d.ts +26 -0
- package/dist/testing.js +362 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +2 -0
- package/dist/timeout.js +15 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/upkeepRules.d.ts +28 -0
- package/dist/upkeepRules.js +40 -0
- package/dist/upkeepRules.js.map +1 -0
- package/dist/util.d.ts +8 -0
- package/dist/util.js +18 -0
- package/dist/util.js.map +1 -0
- package/dist/weekly.d.ts +80 -0
- package/dist/weekly.js +113 -0
- package/dist/weekly.js.map +1 -0
- package/package.json +81 -4
- package/src/attention.ts +89 -0
- package/src/board.ts +58 -0
- package/src/cli/addPages.ts +256 -0
- package/src/cli/main.ts +125 -0
- package/src/common.ts +338 -0
- package/src/compare.ts +261 -0
- package/src/decisions.ts +230 -0
- package/src/feedback.ts +225 -0
- package/src/format.ts +19 -0
- package/src/glossary.ts +75 -0
- package/src/health.ts +149 -0
- package/src/index.ts +33 -0
- package/src/labels.ts +18 -0
- package/src/legs.ts +38 -0
- package/src/memoryStore.ts +268 -0
- package/src/models.ts +127 -0
- package/src/prune.ts +95 -0
- package/src/quickSet.ts +55 -0
- package/src/runner.ts +1010 -0
- package/src/settings.ts +441 -0
- package/src/stableJson.ts +24 -0
- package/src/stats.ts +215 -0
- package/src/store.ts +172 -0
- package/src/suggest.ts +247 -0
- package/src/summary.ts +109 -0
- package/src/testing.ts +415 -0
- package/src/timeout.ts +14 -0
- package/src/types.ts +258 -0
- package/src/upkeepRules.ts +46 -0
- package/src/util.ts +17 -0
- package/src/weekly.ts +169 -0
- package/starter/index.ts +86 -0
- package/templates/app/api/cron/evals-weekly/route.ts +24 -0
- package/templates/app/evals/[suite]/page.tsx +449 -0
- package/templates/app/evals/[suite]/runs/[runId]/page.tsx +95 -0
- package/templates/app/evals/feedback/page.tsx +219 -0
- package/templates/app/evals/page.tsx +313 -0
- package/templates/components/evalkeeper/AddCase.tsx +62 -0
- package/templates/components/evalkeeper/AnswerKey.tsx +360 -0
- package/templates/components/evalkeeper/CaseMenu.tsx +122 -0
- package/templates/components/evalkeeper/Fields.tsx +110 -0
- package/templates/components/evalkeeper/RefreshWhile.tsx +25 -0
- package/templates/components/evalkeeper/SettingsHistory.tsx +191 -0
- package/templates/components/evalkeeper/Submit.tsx +58 -0
- package/templates/components/evalkeeper/Thumbs.tsx +117 -0
- package/templates/components/evalkeeper/Tip.tsx +98 -0
- package/templates/components/evalkeeper/WeeklyCheck.tsx +124 -0
- package/templates/components/evalkeeper/evalkeeper.css +71 -0
- package/templates/components/evalkeeper/ui.tsx +103 -0
- package/templates/lib/evalkeeper/actions.ts +263 -0
- package/templates/lib/evalkeeper/cronAuth.ts +18 -0
- package/templates/lib/evalkeeper/paths.ts +11 -0
- package/templates/lib/evalkeeper/time.ts +4 -0
- package/templates/lib/evalkeeper/weekly.ts +51 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CheckMeasures } from "./stats.js";
|
|
2
|
+
import type { CheckResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Scoring for decision models: models that answer typed questions about a
|
|
5
|
+
* state (pick one option, say true or false, rate on a scale) with
|
|
6
|
+
* probabilities, like the AI SDK's `experimental_decide`. A decision is
|
|
7
|
+
* graded twice: did it pick right, and how good were its probabilities?
|
|
8
|
+
* Two models that pick right equally often can differ a lot in the second:
|
|
9
|
+
* one says 90% and is right 90% of the time, the other says 99% and is
|
|
10
|
+
* right 80%. Pure.
|
|
11
|
+
*/
|
|
12
|
+
/** A decision model's answer to one question, as the AI SDK returns it. */
|
|
13
|
+
export type DecisionAnswerLike = {
|
|
14
|
+
type: "choice";
|
|
15
|
+
choice: string;
|
|
16
|
+
probabilities?: Record<string, number> | undefined;
|
|
17
|
+
} | {
|
|
18
|
+
type: "boolean";
|
|
19
|
+
probability: number;
|
|
20
|
+
} | {
|
|
21
|
+
type: "score";
|
|
22
|
+
score: number;
|
|
23
|
+
probabilities?: Record<string, number> | undefined;
|
|
24
|
+
} | {
|
|
25
|
+
type: "refusal";
|
|
26
|
+
};
|
|
27
|
+
/** The right answer to one question: an option, true or false, or a level. */
|
|
28
|
+
export type DecisionExpected = string | boolean | number;
|
|
29
|
+
/** How one decision did. Probability measures are null when the model gave none. */
|
|
30
|
+
export type DecisionScore = {
|
|
31
|
+
/** Picked the right option, side, or level. */
|
|
32
|
+
correct: boolean;
|
|
33
|
+
/** The model declined to answer. */
|
|
34
|
+
refused: boolean;
|
|
35
|
+
/** The probability the model gave the right answer. */
|
|
36
|
+
probabilityOfTruth: number | null;
|
|
37
|
+
/** The probability the model gave its own pick: how sure it was. */
|
|
38
|
+
confidence: number | null;
|
|
39
|
+
/**
|
|
40
|
+
* Brier score, from 0 (all probability on the right answer) to 1 (all on
|
|
41
|
+
* a wrong one): squared error, scaled to 0–1 for any number of options.
|
|
42
|
+
*/
|
|
43
|
+
brier: number | null;
|
|
44
|
+
/** Log loss of the right answer, capped so one sure miss can't dominate. */
|
|
45
|
+
logLoss: number | null;
|
|
46
|
+
};
|
|
47
|
+
/** Probabilities below this count as this, so log loss stays finite. */
|
|
48
|
+
export declare const MIN_PROBABILITY = 0.0001;
|
|
49
|
+
/** Scores one decision against its right answer. */
|
|
50
|
+
export declare function scoreDecision(answer: DecisionAnswerLike, expected: DecisionExpected, opts?: {
|
|
51
|
+
/** A score question's number of levels, for the answer's level scale. */
|
|
52
|
+
levels?: number;
|
|
53
|
+
/** A boolean answer counts as true at or above this. Default 0.5. */
|
|
54
|
+
threshold?: number;
|
|
55
|
+
}): DecisionScore;
|
|
56
|
+
/**
|
|
57
|
+
* One question's check: passes when the model picked right, and carries
|
|
58
|
+
* its probability measures (brier, logLoss, confidence, correct), which a
|
|
59
|
+
* test averages and turns into a calibration error. Doesn't apply (null)
|
|
60
|
+
* when there's no right answer to compare with.
|
|
61
|
+
*/
|
|
62
|
+
export declare function decisionCheck(key: string, answer: DecisionAnswerLike | undefined, expected: DecisionExpected | undefined, opts?: {
|
|
63
|
+
levels?: number;
|
|
64
|
+
threshold?: number;
|
|
65
|
+
describe?: (v: DecisionExpected) => string;
|
|
66
|
+
}): CheckResult;
|
|
67
|
+
/**
|
|
68
|
+
* Expected calibration error: answers grouped by how sure the model was
|
|
69
|
+
* (10 equal bins), and the average gap between how sure it was and how
|
|
70
|
+
* often it was right, weighted by bin size. 0 is perfectly calibrated.
|
|
71
|
+
* Null with no answers that carry a confidence.
|
|
72
|
+
*/
|
|
73
|
+
export declare function calibrationError(answers: {
|
|
74
|
+
confidence: number;
|
|
75
|
+
correct: boolean;
|
|
76
|
+
}[], bins?: number): number | null;
|
|
77
|
+
/**
|
|
78
|
+
* A test's probability quality: 1 minus the mean Brier score over its
|
|
79
|
+
* checks that measured one. 1 is perfect; null when no check had
|
|
80
|
+
* probabilities (a prompted model's choices, for instance).
|
|
81
|
+
*/
|
|
82
|
+
export declare function probabilityQuality(measures: Record<string, CheckMeasures>): number | null;
|
|
83
|
+
/** A test's calibration error: the mean over its checks that had one. */
|
|
84
|
+
export declare function meanCalibrationError(measures: Record<string, CheckMeasures>): number | null;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/** Probabilities below this count as this, so log loss stays finite. */
|
|
2
|
+
export const MIN_PROBABILITY = 1e-4;
|
|
3
|
+
const logLossOf = (p) => -Math.log(Math.max(p, MIN_PROBABILITY));
|
|
4
|
+
/** The option with the most probability. */
|
|
5
|
+
function topOf(probabilities) {
|
|
6
|
+
let best = null;
|
|
7
|
+
let max = -Infinity;
|
|
8
|
+
for (const [k, p] of Object.entries(probabilities))
|
|
9
|
+
if (p > max) {
|
|
10
|
+
max = p;
|
|
11
|
+
best = k;
|
|
12
|
+
}
|
|
13
|
+
return best;
|
|
14
|
+
}
|
|
15
|
+
/** Scores one decision against its right answer. */
|
|
16
|
+
export function scoreDecision(answer, expected, opts = {}) {
|
|
17
|
+
const none = {
|
|
18
|
+
probabilityOfTruth: null,
|
|
19
|
+
confidence: null,
|
|
20
|
+
brier: null,
|
|
21
|
+
logLoss: null,
|
|
22
|
+
};
|
|
23
|
+
if (answer.type === "refusal")
|
|
24
|
+
return { correct: false, refused: true, ...none };
|
|
25
|
+
if (answer.type === "boolean") {
|
|
26
|
+
const p = answer.probability;
|
|
27
|
+
const truth = expected === true;
|
|
28
|
+
const pTruth = truth ? p : 1 - p;
|
|
29
|
+
return {
|
|
30
|
+
correct: p >= (opts.threshold ?? 0.5) === truth,
|
|
31
|
+
refused: false,
|
|
32
|
+
probabilityOfTruth: pTruth,
|
|
33
|
+
confidence: Math.max(p, 1 - p),
|
|
34
|
+
brier: (p - (truth ? 1 : 0)) ** 2,
|
|
35
|
+
logLoss: logLossOf(pTruth),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const right = String(expected);
|
|
39
|
+
const picked = answer.type === "choice" ? answer.choice : String(Math.round(answer.score));
|
|
40
|
+
const correct = picked === right;
|
|
41
|
+
const probs = answer.probabilities;
|
|
42
|
+
if (!probs || !Object.keys(probs).length)
|
|
43
|
+
return { correct, refused: false, ...none };
|
|
44
|
+
// Brier over every option, scaled to 0–1 (its most is 2).
|
|
45
|
+
const keys = new Set([...Object.keys(probs), right]);
|
|
46
|
+
let squared = 0;
|
|
47
|
+
for (const k of keys)
|
|
48
|
+
squared += ((probs[k] ?? 0) - (k === right ? 1 : 0)) ** 2;
|
|
49
|
+
const pTruth = probs[right] ?? 0;
|
|
50
|
+
const top = topOf(probs);
|
|
51
|
+
return {
|
|
52
|
+
correct,
|
|
53
|
+
refused: false,
|
|
54
|
+
probabilityOfTruth: pTruth,
|
|
55
|
+
confidence: top === null ? null : (probs[top] ?? null),
|
|
56
|
+
brier: squared / 2,
|
|
57
|
+
logLoss: logLossOf(pTruth),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One question's check: passes when the model picked right, and carries
|
|
62
|
+
* its probability measures (brier, logLoss, confidence, correct), which a
|
|
63
|
+
* test averages and turns into a calibration error. Doesn't apply (null)
|
|
64
|
+
* when there's no right answer to compare with.
|
|
65
|
+
*/
|
|
66
|
+
export function decisionCheck(key, answer, expected, opts = {}) {
|
|
67
|
+
if (expected === undefined)
|
|
68
|
+
return { key, pass: null };
|
|
69
|
+
const say = opts.describe ?? ((v) => String(v));
|
|
70
|
+
if (!answer)
|
|
71
|
+
return {
|
|
72
|
+
key,
|
|
73
|
+
pass: false,
|
|
74
|
+
note: "No answer to this question",
|
|
75
|
+
measures: { correct: 0 },
|
|
76
|
+
};
|
|
77
|
+
const s = scoreDecision(answer, expected, opts);
|
|
78
|
+
const measures = { correct: s.correct ? 1 : 0 };
|
|
79
|
+
if (s.brier !== null)
|
|
80
|
+
measures.brier = s.brier;
|
|
81
|
+
if (s.logLoss !== null)
|
|
82
|
+
measures.logLoss = s.logLoss;
|
|
83
|
+
if (s.confidence !== null)
|
|
84
|
+
measures.confidence = s.confidence;
|
|
85
|
+
const given = answer.type === "refusal"
|
|
86
|
+
? "refused"
|
|
87
|
+
: answer.type === "boolean"
|
|
88
|
+
? `${Math.round(answer.probability * 100)}% true`
|
|
89
|
+
: answer.type === "choice"
|
|
90
|
+
? say(answer.choice)
|
|
91
|
+
: `level ${answer.score.toFixed(1)}`;
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
pass: s.correct,
|
|
95
|
+
...(s.correct ? {} : { note: `Expected ${say(expected)}, got ${given}` }),
|
|
96
|
+
measures,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Expected calibration error: answers grouped by how sure the model was
|
|
101
|
+
* (10 equal bins), and the average gap between how sure it was and how
|
|
102
|
+
* often it was right, weighted by bin size. 0 is perfectly calibrated.
|
|
103
|
+
* Null with no answers that carry a confidence.
|
|
104
|
+
*/
|
|
105
|
+
export function calibrationError(answers, bins = 10) {
|
|
106
|
+
if (!answers.length)
|
|
107
|
+
return null;
|
|
108
|
+
const groups = Array.from({ length: bins }, () => ({
|
|
109
|
+
n: 0,
|
|
110
|
+
sure: 0,
|
|
111
|
+
right: 0,
|
|
112
|
+
}));
|
|
113
|
+
for (const a of answers) {
|
|
114
|
+
const i = Math.min(bins - 1, Math.max(0, Math.floor(a.confidence * bins)));
|
|
115
|
+
const g = groups[i];
|
|
116
|
+
if (!g)
|
|
117
|
+
continue;
|
|
118
|
+
g.n++;
|
|
119
|
+
g.sure += a.confidence;
|
|
120
|
+
g.right += a.correct ? 1 : 0;
|
|
121
|
+
}
|
|
122
|
+
let gap = 0;
|
|
123
|
+
for (const g of groups)
|
|
124
|
+
if (g.n)
|
|
125
|
+
gap += (g.n / answers.length) * Math.abs(g.sure / g.n - g.right / g.n);
|
|
126
|
+
return gap;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A test's probability quality: 1 minus the mean Brier score over its
|
|
130
|
+
* checks that measured one. 1 is perfect; null when no check had
|
|
131
|
+
* probabilities (a prompted model's choices, for instance).
|
|
132
|
+
*/
|
|
133
|
+
export function probabilityQuality(measures) {
|
|
134
|
+
const briers = Object.values(measures).flatMap((m) => m.mean.brier === undefined ? [] : [m.mean.brier]);
|
|
135
|
+
return briers.length
|
|
136
|
+
? 1 - briers.reduce((t, b) => t + b, 0) / briers.length
|
|
137
|
+
: null;
|
|
138
|
+
}
|
|
139
|
+
/** A test's calibration error: the mean over its checks that had one. */
|
|
140
|
+
export function meanCalibrationError(measures) {
|
|
141
|
+
const errors = Object.values(measures).flatMap((m) => m.calibrationError === null ? [] : [m.calibrationError]);
|
|
142
|
+
return errors.length
|
|
143
|
+
? errors.reduce((t, e) => t + e, 0) / errors.length
|
|
144
|
+
: null;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=decisions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../src/decisions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { NewCase } from "./store.js";
|
|
2
|
+
import type { AnySuite } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Thumbs up and down on AI answers in your product, and turning them into
|
|
5
|
+
* draft cases. A rating says how a real answer landed; a case made from it
|
|
6
|
+
* tests the next model on the same input. Pure, except the store.
|
|
7
|
+
*/
|
|
8
|
+
/** A thumbs up or a thumbs down. */
|
|
9
|
+
export type Rating = "UP" | "DOWN";
|
|
10
|
+
/** OPEN: waiting for a look. CASED: made into a case. DISMISSED: not useful. */
|
|
11
|
+
export type FeedbackStatus = "OPEN" | "CASED" | "DISMISSED";
|
|
12
|
+
/** A person's rating of one AI answer in your product, as stored. */
|
|
13
|
+
export type FeedbackRecord = {
|
|
14
|
+
id: string;
|
|
15
|
+
/** The AI feature that wrote the answer. */
|
|
16
|
+
operation: string;
|
|
17
|
+
/** What was rated, in your app: a message id, a summary id, … */
|
|
18
|
+
targetId: string;
|
|
19
|
+
/** Who rated it, in your app; one rating per person per answer. */
|
|
20
|
+
raterId: string;
|
|
21
|
+
rating: Rating;
|
|
22
|
+
/** What was wrong, in their words (a thumbs down). */
|
|
23
|
+
reason: string | null;
|
|
24
|
+
/** The text they rated, when your app doesn't keep it elsewhere. */
|
|
25
|
+
excerpt: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The suite input that produced the answer, de-identified, kept only
|
|
28
|
+
* with the person's consent. Without it a rating can't become a case.
|
|
29
|
+
*/
|
|
30
|
+
input: unknown;
|
|
31
|
+
/** The answer as the operation returned it. */
|
|
32
|
+
output: unknown;
|
|
33
|
+
status: FeedbackStatus;
|
|
34
|
+
/** The case made from it. */
|
|
35
|
+
caseId: string | null;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
updatedAt: Date;
|
|
38
|
+
};
|
|
39
|
+
/** A rating to save; `cleanRating` trims and bounds it. */
|
|
40
|
+
export type NewRating = {
|
|
41
|
+
operation: string;
|
|
42
|
+
targetId: string;
|
|
43
|
+
raterId: string;
|
|
44
|
+
rating: Rating;
|
|
45
|
+
reason?: string | null;
|
|
46
|
+
excerpt?: string | null;
|
|
47
|
+
input?: unknown;
|
|
48
|
+
output?: unknown;
|
|
49
|
+
};
|
|
50
|
+
/** Where ratings live. `@crowandraven/evalkeeper-prisma` and `memoryStore` implement it. */
|
|
51
|
+
export interface FeedbackStore {
|
|
52
|
+
/**
|
|
53
|
+
* Saves a person's rating of an answer, replacing their earlier one. A
|
|
54
|
+
* changed rating reopens it.
|
|
55
|
+
*/
|
|
56
|
+
rate(rating: NewRating): Promise<FeedbackRecord>;
|
|
57
|
+
/** Takes a person's rating back. */
|
|
58
|
+
clearRating(raterId: string, operation: string, targetId: string): Promise<void>;
|
|
59
|
+
/** Newest first. */
|
|
60
|
+
listFeedback(opts?: {
|
|
61
|
+
operation?: string;
|
|
62
|
+
rating?: Rating;
|
|
63
|
+
status?: FeedbackStatus;
|
|
64
|
+
since?: Date;
|
|
65
|
+
limit?: number;
|
|
66
|
+
}): Promise<FeedbackRecord[]>;
|
|
67
|
+
setFeedbackStatus(id: string, status: FeedbackStatus, caseId?: string | null): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/** Longest reason and excerpt kept, so one rating can't fill a table. */
|
|
70
|
+
export declare const REASON_MAX = 1000;
|
|
71
|
+
/** The longest rated excerpt kept. */
|
|
72
|
+
export declare const EXCERPT_MAX = 2000;
|
|
73
|
+
/** A rating as it should be stored: trimmed, bounded, a reason only on a thumbs down. */
|
|
74
|
+
export declare function cleanRating(r: NewRating): Required<NewRating>;
|
|
75
|
+
/**
|
|
76
|
+
* Why a rating can't become a case for this suite, or null when it can. A
|
|
77
|
+
* case needs the input that produced the answer, in the suite's shape.
|
|
78
|
+
*/
|
|
79
|
+
export declare function whyNotACase(f: FeedbackRecord, suite: AnySuite | undefined): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* A draft case from a rating, for a person to finish. A thumbs down has no
|
|
82
|
+
* answer yet: the person writes what a good one is. A thumbs up proposes
|
|
83
|
+
* the model's own answer, pending until a person confirms it. Never
|
|
84
|
+
* confirmed on anyone's behalf. Null when it can't become a case.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const suite = SUITES.find((s) => s.operation === rating.operation);
|
|
89
|
+
* const draft = suite ? caseFromFeedback(rating, suite) : null;
|
|
90
|
+
* if (draft) await store.createCase(draft);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function caseFromFeedback(f: FeedbackRecord, suite: AnySuite): NewCase | null;
|
|
94
|
+
/**
|
|
95
|
+
* One feature's ratings over a period: up, down, the share down, whether it needs a look,
|
|
96
|
+
* and how many thumbs down nobody has looked at.
|
|
97
|
+
*/
|
|
98
|
+
export type ThumbsSummary = {
|
|
99
|
+
operation: string;
|
|
100
|
+
up: number;
|
|
101
|
+
down: number;
|
|
102
|
+
/** Share of ratings that were thumbs down; null with none. */
|
|
103
|
+
downShare: number | null;
|
|
104
|
+
/** Enough ratings, and enough of them down, to look at. */
|
|
105
|
+
alert: boolean;
|
|
106
|
+
/** Thumbs down nobody has looked at yet. */
|
|
107
|
+
open: number;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Ratings per feature since a date, most thumbs down first. A feature is
|
|
111
|
+
* flagged when it has at least THUMBS_MIN ratings and at least
|
|
112
|
+
* THUMBS_DOWN_SHARE of them are down.
|
|
113
|
+
*/
|
|
114
|
+
export declare function thumbsSummary(feedback: FeedbackRecord[], since: Date): ThumbsSummary[];
|
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { thumbsAlert } from "./upkeepRules.js";
|
|
2
|
+
/** Longest reason and excerpt kept, so one rating can't fill a table. */
|
|
3
|
+
export const REASON_MAX = 1_000;
|
|
4
|
+
/** The longest rated excerpt kept. */
|
|
5
|
+
export const EXCERPT_MAX = 2_000;
|
|
6
|
+
/** A rating as it should be stored: trimmed, bounded, a reason only on a thumbs down. */
|
|
7
|
+
export function cleanRating(r) {
|
|
8
|
+
const trim = (s, max) => {
|
|
9
|
+
const t = s?.trim().slice(0, max);
|
|
10
|
+
return t ? t : null;
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
operation: r.operation,
|
|
14
|
+
targetId: r.targetId.slice(0, 200),
|
|
15
|
+
raterId: r.raterId,
|
|
16
|
+
rating: r.rating,
|
|
17
|
+
reason: r.rating === "DOWN" ? trim(r.reason, REASON_MAX) : null,
|
|
18
|
+
excerpt: trim(r.excerpt, EXCERPT_MAX),
|
|
19
|
+
input: r.input ?? null,
|
|
20
|
+
output: r.output ?? null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Why a rating can't become a case for this suite, or null when it can. A
|
|
25
|
+
* case needs the input that produced the answer, in the suite's shape.
|
|
26
|
+
*/
|
|
27
|
+
export function whyNotACase(f, suite) {
|
|
28
|
+
if (!suite)
|
|
29
|
+
return "No suite tests this feature yet.";
|
|
30
|
+
if (f.input === null || f.input === undefined)
|
|
31
|
+
return "The input behind this answer wasn't kept, so there's nothing to test.";
|
|
32
|
+
if (!suite.input.safeParse(f.input).success)
|
|
33
|
+
return "The kept input doesn't match the suite's input any more.";
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A draft case from a rating, for a person to finish. A thumbs down has no
|
|
38
|
+
* answer yet: the person writes what a good one is. A thumbs up proposes
|
|
39
|
+
* the model's own answer, pending until a person confirms it. Never
|
|
40
|
+
* confirmed on anyone's behalf. Null when it can't become a case.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const suite = SUITES.find((s) => s.operation === rating.operation);
|
|
45
|
+
* const draft = suite ? caseFromFeedback(rating, suite) : null;
|
|
46
|
+
* if (draft) await store.createCase(draft);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function caseFromFeedback(f, suite) {
|
|
50
|
+
if (whyNotACase(f, suite))
|
|
51
|
+
return null;
|
|
52
|
+
const proposed = f.rating === "UP" && suite.labelFromOutput && f.output !== null
|
|
53
|
+
? suite.labelFromOutput(f.output)
|
|
54
|
+
: null;
|
|
55
|
+
const note = f.rating === "DOWN"
|
|
56
|
+
? `From a thumbs down${f.reason ? `: "${f.reason}"` : "."} Write what a good answer is.`
|
|
57
|
+
: "From a thumbs up: the model's answer is proposed. Confirm it if it's right.";
|
|
58
|
+
return {
|
|
59
|
+
suite: suite.key,
|
|
60
|
+
input: suite.input.parse(f.input),
|
|
61
|
+
tags: ["feedback", f.rating === "DOWN" ? "thumbs down" : "thumbs up"],
|
|
62
|
+
split: "DEV",
|
|
63
|
+
status: "DRAFT",
|
|
64
|
+
source: "FEEDBACK",
|
|
65
|
+
sourceRef: f.id,
|
|
66
|
+
note,
|
|
67
|
+
labels: proposed === null
|
|
68
|
+
? []
|
|
69
|
+
: [
|
|
70
|
+
{
|
|
71
|
+
kind: "EXPECTED",
|
|
72
|
+
status: "PENDING",
|
|
73
|
+
value: proposed,
|
|
74
|
+
raterRole: "FEEDBACK",
|
|
75
|
+
raterId: null,
|
|
76
|
+
note: "The model's answer, rated thumbs up.",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Ratings per feature since a date, most thumbs down first. A feature is
|
|
83
|
+
* flagged when it has at least THUMBS_MIN ratings and at least
|
|
84
|
+
* THUMBS_DOWN_SHARE of them are down.
|
|
85
|
+
*/
|
|
86
|
+
export function thumbsSummary(feedback, since) {
|
|
87
|
+
const by = new Map();
|
|
88
|
+
for (const f of feedback) {
|
|
89
|
+
if (f.updatedAt < since)
|
|
90
|
+
continue;
|
|
91
|
+
const s = by.get(f.operation) ?? {
|
|
92
|
+
operation: f.operation,
|
|
93
|
+
up: 0,
|
|
94
|
+
down: 0,
|
|
95
|
+
downShare: null,
|
|
96
|
+
alert: false,
|
|
97
|
+
open: 0,
|
|
98
|
+
};
|
|
99
|
+
if (f.rating === "UP")
|
|
100
|
+
s.up++;
|
|
101
|
+
else {
|
|
102
|
+
s.down++;
|
|
103
|
+
if (f.status === "OPEN")
|
|
104
|
+
s.open++;
|
|
105
|
+
}
|
|
106
|
+
by.set(f.operation, s);
|
|
107
|
+
}
|
|
108
|
+
return [...by.values()]
|
|
109
|
+
.map((s) => ({
|
|
110
|
+
...s,
|
|
111
|
+
downShare: s.up + s.down ? s.down / (s.up + s.down) : null,
|
|
112
|
+
alert: thumbsAlert(s.up, s.down),
|
|
113
|
+
}))
|
|
114
|
+
.sort((a, b) => b.down - a.down || a.operation.localeCompare(b.operation));
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Eval costs in dollars, for admin pages; server and client both use it. */
|
|
2
|
+
export declare function money(usd: number): string;
|
|
3
|
+
/** One answer's cost: in cents below a dollar, so cheap models still differ. */
|
|
4
|
+
export declare function perAnswer(usd: number | null): string;
|
|
5
|
+
/** Seconds per answer. */
|
|
6
|
+
export declare function seconds(ms: number | null): string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Eval costs in dollars, for admin pages; server and client both use it. */
|
|
2
|
+
export function money(usd) {
|
|
3
|
+
if (usd === 0)
|
|
4
|
+
return "$0";
|
|
5
|
+
if (usd < 0.01)
|
|
6
|
+
return "under 1¢";
|
|
7
|
+
return `$${usd.toFixed(usd < 10 ? 2 : 0)}`;
|
|
8
|
+
}
|
|
9
|
+
/** One answer's cost: in cents below a dollar, so cheap models still differ. */
|
|
10
|
+
export function perAnswer(usd) {
|
|
11
|
+
if (usd === null)
|
|
12
|
+
return "–";
|
|
13
|
+
if (usd >= 1)
|
|
14
|
+
return `$${usd.toFixed(2)}`;
|
|
15
|
+
const cents = usd * 100;
|
|
16
|
+
return `${cents >= 10 ? cents.toFixed(0) : cents.toPrecision(2)}¢`;
|
|
17
|
+
}
|
|
18
|
+
/** Seconds per answer. */
|
|
19
|
+
export function seconds(ms) {
|
|
20
|
+
return ms === null ? "–" : `${(ms / 1000).toFixed(1)}s`;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,GAAG,GAAG,IAAI;QAAE,OAAO,UAAU,CAAC;IAClC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,GAAkB;IAC1C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,GAAG,CAAC;IAC7B,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IACxB,OAAO,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;AACrE,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,OAAO,CAAC,EAAiB;IACvC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each word on an eval page means, in plain words: one source for
|
|
3
|
+
* tooltips, pages, and docs, so they never disagree. Keep each entry to a
|
|
4
|
+
* sentence or two a person can read while hovering.
|
|
5
|
+
*/
|
|
6
|
+
export declare const GLOSSARY: {
|
|
7
|
+
readonly score: "How much the model got right on every case in use. With partial credit, parts right of parts expected; otherwise answers with every check passed.";
|
|
8
|
+
readonly seriousMistakes: "Failed serious checks: mistakes that change what your product does. Models are compared on these first.";
|
|
9
|
+
readonly time: "Average seconds per answer, from answers the model actually made (reused answers keep their original time).";
|
|
10
|
+
readonly cost: "Average cost per answer on this model, from your catalog's prices.";
|
|
11
|
+
readonly againstLive: "A paired comparison with the live model on the same cases: more accurate, about as accurate, less accurate, or too few cases to tell.";
|
|
12
|
+
readonly live: "The model this feature runs on now.";
|
|
13
|
+
readonly fallback: "The model that answers when the live model's provider is down. It comes from another provider.";
|
|
14
|
+
readonly retiring: "The provider has announced a date it stops serving this model. It's never suggested, and a retiring live model gets replaced.";
|
|
15
|
+
readonly outOfDate: "Cases, answers, or instructions changed since this test ran. Test again, or rescore for free if only answers changed.";
|
|
16
|
+
readonly serious: "A mistake that changes what the product does. Serious checks should pass 95% of the time unless the check sets its own bar.";
|
|
17
|
+
readonly minor: "A fair difference of judgment. Minor checks should pass 75% of the time unless the check sets its own bar.";
|
|
18
|
+
readonly bar: "The pass rate this check should reach.";
|
|
19
|
+
readonly confirmed: "A person said this answer is right. Tests grade against it.";
|
|
20
|
+
readonly provisional: "Put to use before anyone reviewed it. Tests use it, and every score says it rests on unreviewed answers.";
|
|
21
|
+
readonly pending: "Proposed and not reviewed yet. Tests never grade against it until a person confirms it.";
|
|
22
|
+
readonly dev: "Cases you tune against: change prompts and checks while looking at these.";
|
|
23
|
+
readonly test: "Cases you only check: never tune against them, so they show how a change does on cases it wasn't fitted to.";
|
|
24
|
+
readonly suggestion: "Fixed rules pick it, so the reason given is the real one: accuracy first, then the suite's priority. Nothing changes until you choose.";
|
|
25
|
+
readonly priority: "What this suite cares about after accuracy: speed (someone waits), cost (it runs often in the background), or quality (only accuracy counts).";
|
|
26
|
+
readonly rescore: "Regrades stored answers with today's checks and answers. No model calls, so it's free.";
|
|
27
|
+
readonly tags: "What a case tests. The quick set picks cases that cover the most tags.";
|
|
28
|
+
readonly improve: "The suite whose best tested model scores lowest: where better cases, checks, or instructions would help most.";
|
|
29
|
+
readonly slowest: "The live model with the longest average answer time, across suites.";
|
|
30
|
+
readonly costliest: "The live model with the highest cost per answer, across suites.";
|
|
31
|
+
readonly thumbs: "Thumbs up and down people gave the feature's answers in your product, over the last 7 days.";
|
|
32
|
+
readonly keepInput: "Keeps what you typed, without your name, so it can become a test case for future models. Without it, a rating still counts but can't become a case.";
|
|
33
|
+
readonly makeCase: "Turns this rating into a draft case on its real input. A thumbs down needs a person to write the right answer; a thumbs up proposes the model's answer for a person to confirm.";
|
|
34
|
+
readonly needsAnswer: "A draft case with no answer yet. Write what a good answer is; until then, tests leave it out.";
|
|
35
|
+
readonly weeklyCheck: "Each suite's quick set, answered again on its live model every week. A drop of 15 points or more against last week's check of the same model, instructions, and grading is drift.";
|
|
36
|
+
readonly versions: "Each numbered version of this feature's settings (model, fallback, reasoning, output limit, temperature), with who changed it, when, why, and its evidence. Any version can be made live again.";
|
|
37
|
+
readonly suites: "One suite per AI feature: its cases, the answers people confirmed, and the checks that grade any model against them. Open one to compare models and choose the live one.";
|
|
38
|
+
readonly answerKey: "The cases and the answers tests grade against. Only confirmed answers count: a model can draft answers, but they stay pending until a person confirms them. Open a case to see how each model did on it.";
|
|
39
|
+
readonly checks: "What a good answer gets right, one thing per check. Serious checks catch mistakes that change what your product does; minor ones, fair differences of judgment. Each has a bar its pass rate should reach.";
|
|
40
|
+
readonly probabilityQuality: "How good the probabilities were: 100% minus the Brier score. High means sure when right and unsure when wrong; picking right with careless probabilities scores lower.";
|
|
41
|
+
readonly calibrationError: "How far the model's confidence is from how often it's right: 0% means its 80%-sure answers are right 80% of the time. It needs more cases than accuracy to be trusted.";
|
|
42
|
+
readonly nativeProbabilities: "The model returns its own probabilities for every option, so they can be scored and compared.";
|
|
43
|
+
readonly promptedProbabilities: "The model writes out its own estimate. Choices come without a distribution, so only true-or-false questions get probability scores.";
|
|
44
|
+
};
|
|
45
|
+
/** A word the glossary defines, for tooltips. */
|
|
46
|
+
export type GlossaryTerm = keyof typeof GLOSSARY;
|
package/dist/glossary.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each word on an eval page means, in plain words: one source for
|
|
3
|
+
* tooltips, pages, and docs, so they never disagree. Keep each entry to a
|
|
4
|
+
* sentence or two a person can read while hovering.
|
|
5
|
+
*/
|
|
6
|
+
export const GLOSSARY = {
|
|
7
|
+
score: "How much the model got right on every case in use. With partial credit, parts right of parts expected; otherwise answers with every check passed.",
|
|
8
|
+
seriousMistakes: "Failed serious checks: mistakes that change what your product does. Models are compared on these first.",
|
|
9
|
+
time: "Average seconds per answer, from answers the model actually made (reused answers keep their original time).",
|
|
10
|
+
cost: "Average cost per answer on this model, from your catalog's prices.",
|
|
11
|
+
againstLive: "A paired comparison with the live model on the same cases: more accurate, about as accurate, less accurate, or too few cases to tell.",
|
|
12
|
+
live: "The model this feature runs on now.",
|
|
13
|
+
fallback: "The model that answers when the live model's provider is down. It comes from another provider.",
|
|
14
|
+
retiring: "The provider has announced a date it stops serving this model. It's never suggested, and a retiring live model gets replaced.",
|
|
15
|
+
outOfDate: "Cases, answers, or instructions changed since this test ran. Test again, or rescore for free if only answers changed.",
|
|
16
|
+
serious: "A mistake that changes what the product does. Serious checks should pass 95% of the time unless the check sets its own bar.",
|
|
17
|
+
minor: "A fair difference of judgment. Minor checks should pass 75% of the time unless the check sets its own bar.",
|
|
18
|
+
bar: "The pass rate this check should reach.",
|
|
19
|
+
confirmed: "A person said this answer is right. Tests grade against it.",
|
|
20
|
+
provisional: "Put to use before anyone reviewed it. Tests use it, and every score says it rests on unreviewed answers.",
|
|
21
|
+
pending: "Proposed and not reviewed yet. Tests never grade against it until a person confirms it.",
|
|
22
|
+
dev: "Cases you tune against: change prompts and checks while looking at these.",
|
|
23
|
+
test: "Cases you only check: never tune against them, so they show how a change does on cases it wasn't fitted to.",
|
|
24
|
+
suggestion: "Fixed rules pick it, so the reason given is the real one: accuracy first, then the suite's priority. Nothing changes until you choose.",
|
|
25
|
+
priority: "What this suite cares about after accuracy: speed (someone waits), cost (it runs often in the background), or quality (only accuracy counts).",
|
|
26
|
+
rescore: "Regrades stored answers with today's checks and answers. No model calls, so it's free.",
|
|
27
|
+
tags: "What a case tests. The quick set picks cases that cover the most tags.",
|
|
28
|
+
improve: "The suite whose best tested model scores lowest: where better cases, checks, or instructions would help most.",
|
|
29
|
+
slowest: "The live model with the longest average answer time, across suites.",
|
|
30
|
+
costliest: "The live model with the highest cost per answer, across suites.",
|
|
31
|
+
thumbs: "Thumbs up and down people gave the feature's answers in your product, over the last 7 days.",
|
|
32
|
+
keepInput: "Keeps what you typed, without your name, so it can become a test case for future models. Without it, a rating still counts but can't become a case.",
|
|
33
|
+
makeCase: "Turns this rating into a draft case on its real input. A thumbs down needs a person to write the right answer; a thumbs up proposes the model's answer for a person to confirm.",
|
|
34
|
+
needsAnswer: "A draft case with no answer yet. Write what a good answer is; until then, tests leave it out.",
|
|
35
|
+
weeklyCheck: "Each suite's quick set, answered again on its live model every week. A drop of 15 points or more against last week's check of the same model, instructions, and grading is drift.",
|
|
36
|
+
versions: "Each numbered version of this feature's settings (model, fallback, reasoning, output limit, temperature), with who changed it, when, why, and its evidence. Any version can be made live again.",
|
|
37
|
+
suites: "One suite per AI feature: its cases, the answers people confirmed, and the checks that grade any model against them. Open one to compare models and choose the live one.",
|
|
38
|
+
answerKey: "The cases and the answers tests grade against. Only confirmed answers count: a model can draft answers, but they stay pending until a person confirms them. Open a case to see how each model did on it.",
|
|
39
|
+
checks: "What a good answer gets right, one thing per check. Serious checks catch mistakes that change what your product does; minor ones, fair differences of judgment. Each has a bar its pass rate should reach.",
|
|
40
|
+
probabilityQuality: "How good the probabilities were: 100% minus the Brier score. High means sure when right and unsure when wrong; picking right with careless probabilities scores lower.",
|
|
41
|
+
calibrationError: "How far the model's confidence is from how often it's right: 0% means its 80%-sure answers are right 80% of the time. It needs more cases than accuracy to be trusted.",
|
|
42
|
+
nativeProbabilities: "The model returns its own probabilities for every option, so they can be scored and compared.",
|
|
43
|
+
promptedProbabilities: "The model writes out its own estimate. Choices come without a distribution, so only true-or-false questions get probability scores.",
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=glossary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glossary.js","sourceRoot":"","sources":["../src/glossary.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,KAAK,EACH,mJAAmJ;IACrJ,eAAe,EACb,yGAAyG;IAC3G,IAAI,EAAE,6GAA6G;IACnH,IAAI,EAAE,oEAAoE;IAC1E,WAAW,EACT,uIAAuI;IACzI,IAAI,EAAE,qCAAqC;IAC3C,QAAQ,EACN,gGAAgG;IAClG,QAAQ,EACN,+HAA+H;IACjI,SAAS,EACP,uHAAuH;IACzH,OAAO,EACL,6HAA6H;IAC/H,KAAK,EACH,4GAA4G;IAC9G,GAAG,EAAE,wCAAwC;IAC7C,SAAS,EAAE,6DAA6D;IACxE,WAAW,EACT,0GAA0G;IAC5G,OAAO,EACL,yFAAyF;IAC3F,GAAG,EAAE,2EAA2E;IAChF,IAAI,EAAE,6GAA6G;IACnH,UAAU,EACR,wIAAwI;IAC1I,QAAQ,EACN,+IAA+I;IACjJ,OAAO,EACL,wFAAwF;IAC1F,IAAI,EAAE,wEAAwE;IAC9E,OAAO,EACL,+GAA+G;IACjH,OAAO,EACL,qEAAqE;IACvE,SAAS,EAAE,iEAAiE;IAC5E,MAAM,EACJ,6FAA6F;IAC/F,SAAS,EACP,qJAAqJ;IACvJ,QAAQ,EACN,iLAAiL;IACnL,WAAW,EACT,+FAA+F;IACjG,WAAW,EACT,mLAAmL;IACrL,QAAQ,EACN,iMAAiM;IACnM,MAAM,EACJ,0KAA0K;IAC5K,SAAS,EACP,0MAA0M;IAC5M,MAAM,EACJ,4MAA4M;IAC9M,kBAAkB,EAChB,wKAAwK;IAC1K,gBAAgB,EACd,wKAAwK;IAC1K,mBAAmB,EACjB,+FAA+F;IACjG,qBAAqB,EACnB,qIAAqI;CAC/H,CAAC"}
|