@crowandraven/evalkeeper 0.1.1 → 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/README.md +2 -2
- package/dist/attention.js +2 -1
- package/dist/attention.js.map +1 -1
- package/dist/board.d.ts +2 -0
- package/dist/glossary.d.ts +1 -1
- package/dist/glossary.js +1 -1
- package/dist/glossary.js.map +1 -1
- package/dist/health.js +1 -1
- package/dist/health.js.map +1 -1
- package/dist/models.d.ts +27 -1
- package/dist/models.js +19 -2
- package/dist/models.js.map +1 -1
- package/dist/runner.d.ts +10 -1
- package/dist/runner.js +34 -6
- package/dist/runner.js.map +1 -1
- package/dist/settings.d.ts +42 -3
- package/dist/settings.js +100 -11
- package/dist/settings.js.map +1 -1
- package/dist/store.d.ts +5 -0
- package/dist/suggest.js +4 -2
- package/dist/suggest.js.map +1 -1
- package/dist/testing.js +21 -0
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/dist/weekly.js +4 -2
- package/dist/weekly.js.map +1 -1
- package/package.json +2 -2
- package/src/attention.ts +2 -1
- package/src/board.ts +2 -0
- package/src/glossary.ts +1 -1
- package/src/health.ts +3 -1
- package/src/models.ts +48 -3
- package/src/runner.ts +43 -4
- package/src/settings.ts +124 -14
- package/src/store.ts +5 -0
- package/src/suggest.ts +3 -1
- package/src/testing.ts +23 -0
- package/src/types.ts +6 -0
- package/src/weekly.ts +4 -2
package/src/runner.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { compareRuns, type RunOutput } from "./compare.js";
|
|
|
9
9
|
import { isStuck, shouldContinue } from "./legs.js";
|
|
10
10
|
import {
|
|
11
11
|
effortFor,
|
|
12
|
+
isBaseline,
|
|
12
13
|
type ModelCatalog,
|
|
13
14
|
type ReasoningEffort,
|
|
14
15
|
} from "./models.js";
|
|
@@ -107,12 +108,15 @@ export type RunnerOptions = {
|
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
|
-
* Which model a feature runs on now, its fallback,
|
|
111
|
+
* Which model a feature runs on now, its fallback, its reasoning setting,
|
|
112
|
+
* and its temperature.
|
|
111
113
|
*/
|
|
112
114
|
export type Route = {
|
|
113
115
|
model: string;
|
|
114
116
|
fallbackModel: string | null;
|
|
115
117
|
reasoningEffort: ReasoningEffort | null;
|
|
118
|
+
/** Null or unset: no temperature sent. */
|
|
119
|
+
temperature?: number | null;
|
|
116
120
|
};
|
|
117
121
|
|
|
118
122
|
/**
|
|
@@ -143,6 +147,8 @@ export function requestHashFor(r: {
|
|
|
143
147
|
prompt: string;
|
|
144
148
|
input: unknown;
|
|
145
149
|
repeat: number;
|
|
150
|
+
/** Left out of the hash when null, so requests without one reuse as before. */
|
|
151
|
+
temperature?: number | null;
|
|
146
152
|
}): string {
|
|
147
153
|
return sha(
|
|
148
154
|
stableJson({
|
|
@@ -152,6 +158,7 @@ export function requestHashFor(r: {
|
|
|
152
158
|
prompt: r.prompt,
|
|
153
159
|
input: r.input,
|
|
154
160
|
repeat: r.repeat,
|
|
161
|
+
...(r.temperature != null ? { temperature: r.temperature } : {}),
|
|
155
162
|
}),
|
|
156
163
|
64,
|
|
157
164
|
);
|
|
@@ -254,6 +261,12 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
254
261
|
return null;
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
/** The operation's live temperature: the route's, else none. */
|
|
265
|
+
async function liveTemperatureFor(operation: string): Promise<number | null> {
|
|
266
|
+
if (!opts.route) return null;
|
|
267
|
+
return (await opts.route(operation)).temperature ?? null;
|
|
268
|
+
}
|
|
269
|
+
|
|
257
270
|
/**
|
|
258
271
|
* What a test would cost. Answers an identical request gave in the last
|
|
259
272
|
* 7 days are reused for free, so only the rest are priced: a retest
|
|
@@ -270,6 +283,8 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
270
283
|
fresh?: boolean;
|
|
271
284
|
/** The setting to test; undefined: the operation's live one. */
|
|
272
285
|
reasoningEffort?: ReasoningEffort | null;
|
|
286
|
+
/** The temperature to test; undefined: the operation's live one. */
|
|
287
|
+
temperature?: number | null;
|
|
273
288
|
} = {},
|
|
274
289
|
): Promise<{
|
|
275
290
|
cases: number;
|
|
@@ -280,13 +295,16 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
280
295
|
costUsd: number | null;
|
|
281
296
|
}> {
|
|
282
297
|
const suite = suiteOrThrow(suiteKey);
|
|
283
|
-
const [cases, perCall, prompt, effort] = await Promise.all([
|
|
298
|
+
const [cases, perCall, prompt, effort, temperature] = await Promise.all([
|
|
284
299
|
casesFor(suite.key, set),
|
|
285
300
|
opts.estimateCallCost?.(suite.operation, model) ?? null,
|
|
286
301
|
opts.promptVersion(suite.operation),
|
|
287
302
|
o.reasoningEffort !== undefined
|
|
288
303
|
? o.reasoningEffort
|
|
289
304
|
: liveEffortFor(suite.operation),
|
|
305
|
+
o.temperature !== undefined
|
|
306
|
+
? o.temperature
|
|
307
|
+
: liveTemperatureFor(suite.operation),
|
|
290
308
|
]);
|
|
291
309
|
const answers = cases.length * repeats;
|
|
292
310
|
const since = new Date(Date.now() - REUSE_DAYS * 86_400_000);
|
|
@@ -304,6 +322,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
304
322
|
prompt,
|
|
305
323
|
input: c.input,
|
|
306
324
|
repeat: i + 1,
|
|
325
|
+
temperature,
|
|
307
326
|
}),
|
|
308
327
|
since,
|
|
309
328
|
),
|
|
@@ -329,6 +348,8 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
329
348
|
startedBy?: string | null;
|
|
330
349
|
/** A reasoning setting to test; undefined: the operation's own. */
|
|
331
350
|
reasoningEffort?: ReasoningEffort | null;
|
|
351
|
+
/** A temperature to test; undefined: the operation's live one. */
|
|
352
|
+
temperature?: number | null;
|
|
332
353
|
/** For the estimate: pass the same `fresh` to runEval. */
|
|
333
354
|
fresh?: boolean;
|
|
334
355
|
}) {
|
|
@@ -339,9 +360,14 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
339
360
|
args.reasoningEffort !== undefined
|
|
340
361
|
? args.reasoningEffort
|
|
341
362
|
: await liveEffortFor(suite.operation);
|
|
363
|
+
const temperature =
|
|
364
|
+
args.temperature !== undefined
|
|
365
|
+
? args.temperature
|
|
366
|
+
: await liveTemperatureFor(suite.operation);
|
|
342
367
|
const [estimate, promptVersion] = await Promise.all([
|
|
343
368
|
estimateRun(suite.key, args.model, set, repeats, {
|
|
344
369
|
reasoningEffort,
|
|
370
|
+
temperature,
|
|
345
371
|
fresh: Boolean(args.fresh),
|
|
346
372
|
}),
|
|
347
373
|
opts.promptVersion(suite.operation),
|
|
@@ -353,6 +379,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
353
379
|
promptVersion,
|
|
354
380
|
reasoningEffort:
|
|
355
381
|
args.reasoningEffort !== undefined ? args.reasoningEffort : liveEffort,
|
|
382
|
+
temperature,
|
|
356
383
|
set,
|
|
357
384
|
repeats,
|
|
358
385
|
datasetVersion: null,
|
|
@@ -488,6 +515,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
488
515
|
prompt: run.promptVersion,
|
|
489
516
|
input: c.input,
|
|
490
517
|
repeat,
|
|
518
|
+
temperature: run.temperature ?? null,
|
|
491
519
|
});
|
|
492
520
|
const earlier = o.fresh
|
|
493
521
|
? null
|
|
@@ -517,6 +545,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
517
545
|
model: run.model,
|
|
518
546
|
reasoningEffort:
|
|
519
547
|
(run.reasoningEffort as ReasoningEffort | null) ?? null,
|
|
548
|
+
temperature: run.temperature ?? null,
|
|
520
549
|
onCost: (usd) => (cost += usd),
|
|
521
550
|
}),
|
|
522
551
|
timeout,
|
|
@@ -663,6 +692,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
663
692
|
set: "SMOKE",
|
|
664
693
|
startedBy: SCHEDULED,
|
|
665
694
|
reasoningEffort: route.reasoningEffort,
|
|
695
|
+
temperature: route.temperature ?? null,
|
|
666
696
|
});
|
|
667
697
|
return run.id;
|
|
668
698
|
}
|
|
@@ -784,8 +814,12 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
784
814
|
if (r.status === "SUCCEEDED" && !latestDone.has(runKey(r)))
|
|
785
815
|
latestDone.set(runKey(r), r);
|
|
786
816
|
}
|
|
817
|
+
const liveTemperature = route.temperature ?? null;
|
|
818
|
+
// A test at another temperature than the live one answered differently.
|
|
787
819
|
const isCurrent = (r: RunRecord) =>
|
|
788
|
-
r.datasetVersion === current &&
|
|
820
|
+
r.datasetVersion === current &&
|
|
821
|
+
r.promptVersion === prompt &&
|
|
822
|
+
(r.temperature ?? null) === liveTemperature;
|
|
789
823
|
|
|
790
824
|
const liveKey = keyOf(route.model, defaultEffort);
|
|
791
825
|
const liveRun = latestDone.get(liveKey);
|
|
@@ -794,7 +828,8 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
794
828
|
const sameAsLive = (r: RunRecord) =>
|
|
795
829
|
liveRun !== undefined &&
|
|
796
830
|
r.datasetVersion === liveRun.datasetVersion &&
|
|
797
|
-
r.promptVersion === liveRun.promptVersion
|
|
831
|
+
r.promptVersion === liveRun.promptVersion &&
|
|
832
|
+
(r.temperature ?? null) === (liveRun.temperature ?? null);
|
|
798
833
|
const outputs = new Map<string, RunOutput[]>();
|
|
799
834
|
for (const r of latestDone.values())
|
|
800
835
|
outputs.set(
|
|
@@ -863,6 +898,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
863
898
|
.verdict
|
|
864
899
|
: null;
|
|
865
900
|
const label = opts.models[model]?.label ?? model;
|
|
901
|
+
const baseline = isBaseline(opts.models, model);
|
|
866
902
|
return {
|
|
867
903
|
key,
|
|
868
904
|
model,
|
|
@@ -873,6 +909,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
873
909
|
: label,
|
|
874
910
|
live: key === liveKey,
|
|
875
911
|
fallback: !variant && model === route.fallbackModel,
|
|
912
|
+
baseline,
|
|
876
913
|
testing: testing.has(key),
|
|
877
914
|
failed:
|
|
878
915
|
lastRun?.status === "FAILED"
|
|
@@ -900,6 +937,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
900
937
|
vsLive,
|
|
901
938
|
// Only on up-to-date tests: never promote on an old answer key.
|
|
902
939
|
canMakeLive:
|
|
940
|
+
!baseline &&
|
|
903
941
|
models.includes(model) &&
|
|
904
942
|
fresh &&
|
|
905
943
|
complete(r) &&
|
|
@@ -907,6 +945,7 @@ export function createRunner(opts: RunnerOptions) {
|
|
|
907
945
|
liveOnly: !fallbackModels.includes(model),
|
|
908
946
|
fallbackOnly: !models.includes(model),
|
|
909
947
|
canMakeFallback:
|
|
948
|
+
!baseline &&
|
|
910
949
|
fallbackModels.includes(model) &&
|
|
911
950
|
!variant &&
|
|
912
951
|
model !== route.model &&
|
package/src/settings.ts
CHANGED
|
@@ -12,7 +12,8 @@ import type { Route } from "./runner.js";
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* A feature's model settings: live model, fallback, reasoning setting,
|
|
15
|
+
* A feature's model settings: live model, fallback, reasoning setting,
|
|
16
|
+
* output limit, and temperature.
|
|
16
17
|
*/
|
|
17
18
|
export type Settings = {
|
|
18
19
|
model: string;
|
|
@@ -21,6 +22,12 @@ export type Settings = {
|
|
|
21
22
|
reasoningEffort: ReasoningEffort | null;
|
|
22
23
|
/** Null: the call's own default. */
|
|
23
24
|
maxOutputTokens: number | null;
|
|
25
|
+
/**
|
|
26
|
+
* The sampling temperature the feature sends. Null or unset: none sent,
|
|
27
|
+
* so the model's own default. Tests record it, and a test at another
|
|
28
|
+
* temperature than the live one is out of date.
|
|
29
|
+
*/
|
|
30
|
+
temperature?: number | null;
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
/**
|
|
@@ -106,13 +113,31 @@ export class SettingsConflict extends Error {
|
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
|
|
116
|
+
/**
|
|
117
|
+
* A feature has no settings: no version yet, and no default in code. Apps
|
|
118
|
+
* whose models are chosen in an admin page, not in code, see this until
|
|
119
|
+
* the feature's first version is recorded (`seed` or `change`).
|
|
120
|
+
*/
|
|
121
|
+
export class NoSettingsError extends Error {
|
|
122
|
+
/** The feature with no settings. */
|
|
123
|
+
readonly operation: string;
|
|
124
|
+
constructor(operation: string) {
|
|
125
|
+
super(
|
|
126
|
+
`${operation} has no settings yet: no version, and no default in code.`,
|
|
127
|
+
);
|
|
128
|
+
this.name = "NoSettingsError";
|
|
129
|
+
this.operation = operation;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
109
133
|
/** Whether two settings are the same in every field. */
|
|
110
134
|
export function sameSettings(a: Settings, b: Settings): boolean {
|
|
111
135
|
return (
|
|
112
136
|
a.model === b.model &&
|
|
113
137
|
a.fallbackModel === b.fallbackModel &&
|
|
114
138
|
a.reasoningEffort === b.reasoningEffort &&
|
|
115
|
-
a.maxOutputTokens === b.maxOutputTokens
|
|
139
|
+
a.maxOutputTokens === b.maxOutputTokens &&
|
|
140
|
+
(a.temperature ?? null) === (b.temperature ?? null)
|
|
116
141
|
);
|
|
117
142
|
}
|
|
118
143
|
|
|
@@ -123,6 +148,7 @@ export function settingsOf(v: Settings): Settings {
|
|
|
123
148
|
fallbackModel: v.fallbackModel,
|
|
124
149
|
reasoningEffort: v.reasoningEffort,
|
|
125
150
|
maxOutputTokens: v.maxOutputTokens,
|
|
151
|
+
temperature: v.temperature ?? null,
|
|
126
152
|
};
|
|
127
153
|
}
|
|
128
154
|
|
|
@@ -148,6 +174,10 @@ export function describeChange(
|
|
|
148
174
|
out.push(
|
|
149
175
|
`Output limit: ${from.maxOutputTokens ?? "default"} → ${to.maxOutputTokens ?? "default"}`,
|
|
150
176
|
);
|
|
177
|
+
if ((from.temperature ?? null) !== (to.temperature ?? null))
|
|
178
|
+
out.push(
|
|
179
|
+
`Temperature: ${from.temperature ?? "default"} → ${to.temperature ?? "default"}`,
|
|
180
|
+
);
|
|
151
181
|
return out;
|
|
152
182
|
}
|
|
153
183
|
|
|
@@ -175,11 +205,28 @@ export type HistoryEntry = SettingsChange & {
|
|
|
175
205
|
* });
|
|
176
206
|
* await settings.rollBack("ticket_triage", versionId, { by: admin.name, reason: "Worse in production" });
|
|
177
207
|
* ```
|
|
208
|
+
*
|
|
209
|
+
* When models are chosen in an admin page rather than in code, return null
|
|
210
|
+
* from `defaults` and record each feature's existing config as version 1
|
|
211
|
+
* with `seed`:
|
|
212
|
+
*
|
|
213
|
+
* ```ts
|
|
214
|
+
* const settings = createSettings({ store, defaults: () => null });
|
|
215
|
+
* for (const row of await db.aiConfig.findMany())
|
|
216
|
+
* await settings.seed(row.operation, settingsFromRow(row), {
|
|
217
|
+
* by: null,
|
|
218
|
+
* reason: "Imported from the admin config",
|
|
219
|
+
* });
|
|
220
|
+
* ```
|
|
178
221
|
*/
|
|
179
222
|
export function createSettings(opts: {
|
|
180
223
|
store: SettingsStore;
|
|
181
|
-
/**
|
|
182
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Each feature's settings in code, used until it's first changed. Null:
|
|
226
|
+
* the feature has no default, and reading its settings throws
|
|
227
|
+
* `NoSettingsError` until a version is recorded.
|
|
228
|
+
*/
|
|
229
|
+
defaults: (operation: string) => Settings | null;
|
|
183
230
|
/** The instructions' current hash, so a version records what it ran. */
|
|
184
231
|
promptVersion?: (operation: string) => string | Promise<string>;
|
|
185
232
|
}) {
|
|
@@ -187,32 +234,37 @@ export function createSettings(opts: {
|
|
|
187
234
|
const prompt = async (op: string) =>
|
|
188
235
|
opts.promptVersion ? await opts.promptVersion(op) : null;
|
|
189
236
|
|
|
190
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* The live settings: the live version's, else the default from code.
|
|
239
|
+
* Throws `NoSettingsError` when the feature has neither.
|
|
240
|
+
*/
|
|
191
241
|
async function current(
|
|
192
242
|
operation: string,
|
|
193
243
|
): Promise<{ settings: Settings; version: SettingsVersion | null }> {
|
|
194
244
|
const version = await store.liveVersion(operation);
|
|
195
|
-
return {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
245
|
+
if (version) return { settings: settingsOf(version), version };
|
|
246
|
+
const fallback = opts.defaults(operation);
|
|
247
|
+
if (!fallback) throw new NoSettingsError(operation);
|
|
248
|
+
return { settings: fallback, version };
|
|
199
249
|
}
|
|
200
250
|
|
|
201
|
-
/** The runner's route for a feature, from its live settings. */
|
|
251
|
+
/** The runner's route for a feature, from its live settings; throws `NoSettingsError` when it has none. */
|
|
202
252
|
async function route(operation: string): Promise<Route> {
|
|
203
253
|
const { settings } = await current(operation);
|
|
204
254
|
return {
|
|
205
255
|
model: settings.model,
|
|
206
256
|
fallbackModel: settings.fallbackModel,
|
|
207
257
|
reasoningEffort: settings.reasoningEffort,
|
|
258
|
+
temperature: settings.temperature ?? null,
|
|
208
259
|
};
|
|
209
260
|
}
|
|
210
261
|
|
|
211
262
|
/**
|
|
212
263
|
* Makes new settings live as a new version. The first change to a
|
|
213
264
|
* feature first records its default as version 1, so there's always
|
|
214
|
-
* something to roll back to
|
|
215
|
-
*
|
|
265
|
+
* something to roll back to; a feature with no default gets `next` as
|
|
266
|
+
* version 1. With `evidence`, it's a promotion. Returns null when
|
|
267
|
+
* nothing changed.
|
|
216
268
|
*/
|
|
217
269
|
async function change(
|
|
218
270
|
operation: string,
|
|
@@ -223,6 +275,15 @@ export function createSettings(opts: {
|
|
|
223
275
|
let live = await store.liveVersion(operation);
|
|
224
276
|
if (!live) {
|
|
225
277
|
const initial = opts.defaults(operation);
|
|
278
|
+
if (!initial)
|
|
279
|
+
return store.switchTo(operation, {
|
|
280
|
+
create: { settings: settingsOf(next), promptVersion },
|
|
281
|
+
kind: by.evidence === undefined ? "CHANGE" : "PROMOTE",
|
|
282
|
+
reason: by.reason,
|
|
283
|
+
by: by.by,
|
|
284
|
+
evidence: by.evidence,
|
|
285
|
+
expectLive: null,
|
|
286
|
+
});
|
|
226
287
|
if (sameSettings(initial, next)) return null;
|
|
227
288
|
live = await store.switchTo(operation, {
|
|
228
289
|
create: { settings: initial, promptVersion },
|
|
@@ -242,7 +303,11 @@ export function createSettings(opts: {
|
|
|
242
303
|
});
|
|
243
304
|
}
|
|
244
305
|
|
|
245
|
-
/**
|
|
306
|
+
/**
|
|
307
|
+
* Changes only some settings (e.g. the fallback), keeping the rest.
|
|
308
|
+
* Throws `NoSettingsError` for a feature with no settings to keep: use
|
|
309
|
+
* `change` or `seed` with full settings first.
|
|
310
|
+
*/
|
|
246
311
|
async function update(
|
|
247
312
|
operation: string,
|
|
248
313
|
patch: Partial<Settings>,
|
|
@@ -301,6 +366,42 @@ export function createSettings(opts: {
|
|
|
301
366
|
}
|
|
302
367
|
}
|
|
303
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Records existing settings as a feature's version 1, for an app moving
|
|
371
|
+
* its model choices into versioned settings (an import from its own
|
|
372
|
+
* config table). Only when the feature has no version yet: otherwise it
|
|
373
|
+
* changes nothing and returns null, so an import can run again safely.
|
|
374
|
+
* `promptVersion` defaults to the instructions' current hash.
|
|
375
|
+
*/
|
|
376
|
+
async function seed(
|
|
377
|
+
operation: string,
|
|
378
|
+
initial: Settings,
|
|
379
|
+
by: {
|
|
380
|
+
by: string | null;
|
|
381
|
+
reason: string | null;
|
|
382
|
+
promptVersion?: string | null;
|
|
383
|
+
},
|
|
384
|
+
): Promise<SettingsVersion | null> {
|
|
385
|
+
if (await store.liveVersion(operation)) return null;
|
|
386
|
+
const promptVersion =
|
|
387
|
+
by.promptVersion !== undefined
|
|
388
|
+
? by.promptVersion
|
|
389
|
+
: await prompt(operation);
|
|
390
|
+
try {
|
|
391
|
+
return await store.switchTo(operation, {
|
|
392
|
+
create: { settings: settingsOf(initial), promptVersion },
|
|
393
|
+
kind: "CHANGE",
|
|
394
|
+
reason: by.reason,
|
|
395
|
+
by: by.by,
|
|
396
|
+
expectLive: null,
|
|
397
|
+
});
|
|
398
|
+
} catch (e) {
|
|
399
|
+
// Another request seeded it first: that version stands.
|
|
400
|
+
if (e instanceof SettingsConflict) return null;
|
|
401
|
+
throw e;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
304
405
|
/** Every switch, newest first, with the versions it went from and to. */
|
|
305
406
|
async function history(
|
|
306
407
|
operation: string,
|
|
@@ -324,7 +425,16 @@ export function createSettings(opts: {
|
|
|
324
425
|
});
|
|
325
426
|
}
|
|
326
427
|
|
|
327
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
current,
|
|
430
|
+
route,
|
|
431
|
+
change,
|
|
432
|
+
update,
|
|
433
|
+
rollBack,
|
|
434
|
+
syncPrompt,
|
|
435
|
+
seed,
|
|
436
|
+
history,
|
|
437
|
+
};
|
|
328
438
|
}
|
|
329
439
|
|
|
330
440
|
/** What createSettings returns. */
|
package/src/store.ts
CHANGED
|
@@ -77,6 +77,11 @@ export type RunRecord = {
|
|
|
77
77
|
promptVersion: string;
|
|
78
78
|
/** The reasoning setting tested; null: the model's own default. */
|
|
79
79
|
reasoningEffort: string | null;
|
|
80
|
+
/**
|
|
81
|
+
* The temperature tested; null or unset: none sent. Stores keep it, so a
|
|
82
|
+
* test at another temperature than the live one shows as out of date.
|
|
83
|
+
*/
|
|
84
|
+
temperature?: number | null;
|
|
80
85
|
set: EvalSet;
|
|
81
86
|
repeats: number;
|
|
82
87
|
/** Changes when the cases or their answers do, so tests compare. */
|
package/src/suggest.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { retiresLabel, type ModelCatalog } from "./models.js";
|
|
1
|
+
import { isBaseline, retiresLabel, type ModelCatalog } from "./models.js";
|
|
2
2
|
import type { Rate } from "./stats.js";
|
|
3
3
|
import type { CheckDef, SuitePriority } from "./types.js";
|
|
4
4
|
import type { Verdict } from "./compare.js";
|
|
@@ -109,8 +109,10 @@ export function suggest<R extends SuggestRow>(
|
|
|
109
109
|
const retiring = (r: SuggestRow) => retiresLabel(models, r.model);
|
|
110
110
|
const limitMs = priority.maxSeconds ? priority.maxSeconds * 1000 : Infinity;
|
|
111
111
|
const tooSlow = (r: SuggestRow) => ms(r) !== Infinity && ms(r) > limitMs;
|
|
112
|
+
// Baselines are code, compared on the board but never suggested.
|
|
112
113
|
const tested = rows.filter(
|
|
113
114
|
(r) =>
|
|
115
|
+
!isBaseline(models, r.model) &&
|
|
114
116
|
r.test &&
|
|
115
117
|
!r.test.stale &&
|
|
116
118
|
!r.test.failedAnswers &&
|
package/src/testing.ts
CHANGED
|
@@ -127,6 +127,14 @@ export function describeStoreContract(
|
|
|
127
127
|
expect(await store.getRun("missing")).toBeNull();
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
it("keeps the temperature a run was tested at", async () => {
|
|
131
|
+
const store = await make();
|
|
132
|
+
const warm = await store.createRun({ ...newRun("s"), temperature: 0.7 });
|
|
133
|
+
const none = await store.createRun({ ...newRun("s"), temperature: null });
|
|
134
|
+
expect((await store.getRun(warm.id))?.temperature).toBe(0.7);
|
|
135
|
+
expect((await store.getRun(none.id))?.temperature ?? null).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
|
|
130
138
|
it("updates a run only while it has the status it's guarded by", async () => {
|
|
131
139
|
const store = await make();
|
|
132
140
|
const r = await store.createRun(newRun("s"));
|
|
@@ -381,6 +389,21 @@ export function describeSettingsStoreContract(
|
|
|
381
389
|
expect(await store.listVersions("triage")).toHaveLength(2);
|
|
382
390
|
});
|
|
383
391
|
|
|
392
|
+
it("keeps a version's temperature", async () => {
|
|
393
|
+
const store = await make();
|
|
394
|
+
const v1 = await store.switchTo("triage", {
|
|
395
|
+
...create("a", null),
|
|
396
|
+
create: {
|
|
397
|
+
settings: { ...settings("a"), temperature: 0.4 },
|
|
398
|
+
promptVersion: "p1",
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
expect(v1.temperature).toBe(0.4);
|
|
402
|
+
expect((await store.liveVersion("triage"))?.temperature).toBe(0.4);
|
|
403
|
+
const v2 = await store.switchTo("triage", create("b", v1.id));
|
|
404
|
+
expect(v2.temperature ?? null).toBeNull();
|
|
405
|
+
});
|
|
406
|
+
|
|
384
407
|
it("keeps each feature's versions and live one apart", async () => {
|
|
385
408
|
const store = await make();
|
|
386
409
|
await store.switchTo("triage", create("a", null));
|
package/src/types.ts
CHANGED
|
@@ -108,6 +108,12 @@ export type Suite<Input, Output, Label> = {
|
|
|
108
108
|
model: string;
|
|
109
109
|
/** A reasoning setting to test; undefined: the operation's own. */
|
|
110
110
|
reasoningEffort?: ReasoningEffort | null;
|
|
111
|
+
/**
|
|
112
|
+
* The temperature the test runs at: the operation's live one when the
|
|
113
|
+
* test was started. Null: send none. Undefined from runners before
|
|
114
|
+
* temperature was versioned.
|
|
115
|
+
*/
|
|
116
|
+
temperature?: number | null;
|
|
111
117
|
onCost: (usd: number) => void;
|
|
112
118
|
},
|
|
113
119
|
): Promise<Output>;
|
package/src/weekly.ts
CHANGED
|
@@ -104,13 +104,15 @@ export function weeklySummary(args: {
|
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
106
|
if (r.status !== "SUCCEEDED") continue;
|
|
107
|
-
// The last check of the same model, instructions, and
|
|
108
|
-
// more earlier: anything else changed for a known
|
|
107
|
+
// The last check of the same model, temperature, instructions, and
|
|
108
|
+
// grading, a day or more earlier: anything else changed for a known
|
|
109
|
+
// reason.
|
|
109
110
|
const prior = scheduled.find(
|
|
110
111
|
(p) =>
|
|
111
112
|
p.suite === r.suite &&
|
|
112
113
|
p.status === "SUCCEEDED" &&
|
|
113
114
|
p.model === r.model &&
|
|
115
|
+
(p.temperature ?? null) === (r.temperature ?? null) &&
|
|
114
116
|
p.promptVersion === r.promptVersion &&
|
|
115
117
|
p.graderVersion === r.graderVersion &&
|
|
116
118
|
+r.startedAt - +p.startedAt >= DAY,
|