cf6bc4c844
runner 用 Docker 跑一条命令并交回结局和资源用量;tasks 读 task.json、准备运行目录、给出正式运行/校验器/轻量检查的运行定义,并从原始产物独立判分。任务目录从参考项目搬到 tasks/circle_packing/,新增 task.json;vocab 的 VERDICTS 删掉"判不了",ProcessResult 的 cancelled 改为"容器从外面被杀了"。 全新上下文审阅后的两处修复:计量 shell 以容器内 root 运行,命令经 setpriv 降权,结果写在容器自己的文件层、结束后 docker cp 取出,并加 no-new-privileges,候选无法伪造计量结果把超时记成正常退出;harness 开跑前删掉旧的 solution.json、metrics.json,旧产物不会被当成本次的值。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
262 lines
9.7 KiB
TypeScript
262 lines
9.7 KiB
TypeScript
import { Type, type Static } from "typebox";
|
||
import { DIRECTIONS, Id, Measured, type Json } from "./basics.ts";
|
||
|
||
/** 不认识的字段、不合格式的键一律不收。 */
|
||
const CLOSED = { additionalProperties: false } as const;
|
||
const REASON = Type.String({ minLength: 1 });
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 指标(方法.md 第五部分"指标")
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** 指标层次:最终指标由任务规定;常规辅助指标由程序统一采集;假设专用指标由研究者登记。 */
|
||
export const METRIC_LAYERS = ["final", "auxiliary", "hypothesis"] as const;
|
||
export type MetricLayer = (typeof METRIC_LAYERS)[number];
|
||
|
||
/** "测不到时怎么办"只有两档。"只记缺失"排在前面:它是诊断量的常规选择。 */
|
||
export const MISSING_POLICIES = ["recordMissing", "invalidateRun"] as const;
|
||
export type MissingPolicy = (typeof MISSING_POLICIES)[number];
|
||
|
||
/** 每一档的后果,字段说明和给模型看的材料都用这一份。 */
|
||
export const MISSING_POLICY_EFFECTS: Readonly<Record<MissingPolicy, string>> = {
|
||
recordMissing: "缺了只记缺失:这次运行的成绩照常算。诊断用的量一般选这一种。",
|
||
invalidateRun:
|
||
"缺了整次作废:这次运行不能被采纳,运行次数照扣。只有没有这个量成绩就不可信时,才选这一种。",
|
||
};
|
||
|
||
/** 一条指标除编号和层次以外的内容。研究者登记假设专用指标时填的就是这些,所以说明写给模型看。 */
|
||
const METRIC_FIELDS = {
|
||
meaning: Type.String({
|
||
minLength: 1,
|
||
description: "含义:这个量测的是什么",
|
||
}),
|
||
unit: Type.String({
|
||
minLength: 1,
|
||
description: "单位;没有单位(计数、比例)写\"无\"",
|
||
}),
|
||
direction: Type.Enum(DIRECTIONS, {
|
||
type: "string",
|
||
description: "maximize:越大越好;minimize:越小越好",
|
||
}),
|
||
howMeasured: Type.String({
|
||
minLength: 1,
|
||
description:
|
||
"怎么采样和汇总:在什么时刻测、测几次、怎么合成一个数,写到换一个人照着写代码能测出同一个数",
|
||
}),
|
||
missing: Type.Enum(MISSING_POLICIES, {
|
||
type: "string",
|
||
description:
|
||
`测不到时怎么办。recordMissing——${MISSING_POLICY_EFFECTS.recordMissing}` +
|
||
`invalidateRun——${MISSING_POLICY_EFFECTS.invalidateRun}` +
|
||
"消融格里一律只记缺失。引用别人登记过的指标时,沿用原登记的选法",
|
||
}),
|
||
};
|
||
|
||
/** 研究者在协议里登记的一条假设专用指标。编号由研究者起,候选代码按这个编号写出取值。 */
|
||
export const HypothesisMetric = Type.Object(
|
||
{
|
||
id: Type.String({
|
||
...Id,
|
||
description: "指标短名,只用字母、数字和 ._-,候选代码按它写出取值",
|
||
}),
|
||
...METRIC_FIELDS,
|
||
},
|
||
CLOSED,
|
||
);
|
||
export type HypothesisMetric = Static<typeof HypothesisMetric>;
|
||
|
||
/** 一条完整的指标定义:任务给的最终指标、常规辅助指标,以及冻结后的假设专用指标。 */
|
||
export const MetricDefinition = Type.Object(
|
||
{
|
||
id: Id,
|
||
layer: Type.Enum(METRIC_LAYERS, { type: "string" }),
|
||
...METRIC_FIELDS,
|
||
},
|
||
CLOSED,
|
||
);
|
||
export type MetricDefinition = Static<typeof MetricDefinition>;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 运行定义:tasks 交给 runner,告诉它怎么起这个进程
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const RunDefinition = Type.Object(
|
||
{
|
||
/** 命令行,按参数切好,不经过 shell。 */
|
||
command: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
||
/** 进程在容器里的工作目录,绝对路径。 */
|
||
workdir: Type.String({ pattern: "^/" }),
|
||
/**
|
||
* 镜像按 sha256 钉死,不接受 tag:tag 可以被重新指向另一份内容。
|
||
* digest 填本机镜像 ID,即 `docker image inspect -f '{{.Id}}'` 的值;
|
||
* 不是仓库清单摘要(RepoDigests),本地构建的镜像没有那个值。
|
||
* 运行时必须写明:本机 Docker 的默认运行时不一定是 runc,跟着默认走,换台机器环境就悄悄变了。
|
||
*/
|
||
image: Type.Object(
|
||
{
|
||
digest: Type.String({ pattern: "^sha256:[0-9a-f]{64}$" }),
|
||
runtime: Type.String({ minLength: 1 }),
|
||
},
|
||
CLOSED,
|
||
),
|
||
/** 单次运行的墙钟时限(秒)和核数,由任务规定,是同批候选共用的比较条件。 */
|
||
wallSeconds: Type.Number({ exclusiveMinimum: 0 }),
|
||
cores: Type.Integer({ minimum: 1 }),
|
||
/**
|
||
* 候选进程不继承宿主的环境变量,类型上只允许 false。参考项目实测过:宿主环境被污染时,
|
||
* 同一份代码的分数从 0.9598 变成 0.8849,退出码是 0,没有任何报错。
|
||
*/
|
||
inheritEnv: Type.Literal(false),
|
||
/** 进程拿到的全部环境变量。 */
|
||
env: Type.Record(Type.String({ pattern: "^[A-Za-z_][A-Za-z0-9_]*$" }), Type.String(), CLOSED),
|
||
},
|
||
CLOSED,
|
||
);
|
||
export type RunDefinition = Static<typeof RunDefinition>;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 运行结果第一层:runner 交出的进程层面结果
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** 资源用量,每一项都可能测不到。 */
|
||
const RESOURCE_USAGE = {
|
||
cpuSeconds: Measured,
|
||
wallSeconds: Measured,
|
||
peakMemoryBytes: Measured,
|
||
};
|
||
|
||
/**
|
||
* 以什么方式结束。有没有启动由它决定:只有 notStarted 是没启动,
|
||
* 其余三种都启动过、要扣次数(方法.md 第七部分)。
|
||
* 启动前就取消的记 notStarted;cancelled 是容器从外面被杀了(关机、手动 docker kill、
|
||
* 计量的 shell 自己被杀),没有留下计量结果,三项资源都记测不到。
|
||
* 退出码只有正常退出才有,资源用量只有启动过才有,所以分成四种形状。
|
||
*/
|
||
export const ProcessResult = Type.Union([
|
||
Type.Object(
|
||
{
|
||
ending: Type.Literal("notStarted"),
|
||
reason: REASON,
|
||
},
|
||
CLOSED,
|
||
),
|
||
Type.Object(
|
||
{
|
||
ending: Type.Literal("exited"),
|
||
exitCode: Type.Integer(),
|
||
...RESOURCE_USAGE,
|
||
},
|
||
CLOSED,
|
||
),
|
||
Type.Object(
|
||
{
|
||
ending: Type.Literal("timeout"),
|
||
...RESOURCE_USAGE,
|
||
},
|
||
CLOSED,
|
||
),
|
||
Type.Object(
|
||
{
|
||
ending: Type.Literal("cancelled"),
|
||
...RESOURCE_USAGE,
|
||
},
|
||
CLOSED,
|
||
),
|
||
]);
|
||
export type ProcessResult = Static<typeof ProcessResult>;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 运行结果第二层:tasks 从原始产物独立判分
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** 判定二选一:算数、不算数。 */
|
||
export const VERDICTS = ["valid", "invalid"] as const;
|
||
export type Verdict = (typeof VERDICTS)[number];
|
||
|
||
/**
|
||
* 候选在写最终指标的同一个文件里多写的两个保留键(方法.md 第五部分"答案路径")。
|
||
* 实现的字段说明(tasks 一侧写)和给模型的材料(materials 一侧读)都用这两个常量。
|
||
*/
|
||
export const ANSWER_PATH_KEY = "answer-path";
|
||
export const SAFEGUARD_TRIGGERS_KEY = "safeguard-triggers";
|
||
|
||
/**
|
||
* 答案路径:候选写的最终答案出自哪里(main、某个因子编号或某条保障的短名)。
|
||
* tasks 只记读到的原始内容:读到了就原样存字符串;没写或写的不是字符串,记读不到并附原因。
|
||
* 取值在不在位置表里不在这里判断,由以后拿得到位置表的模块现算。
|
||
*/
|
||
export const AnswerPath = Type.Union([
|
||
Type.Object(
|
||
{
|
||
status: Type.Literal("read"),
|
||
value: Type.String(),
|
||
},
|
||
CLOSED,
|
||
),
|
||
Type.Object(
|
||
{
|
||
status: Type.Literal("unreadable"),
|
||
reason: REASON,
|
||
},
|
||
CLOSED,
|
||
),
|
||
]);
|
||
export type AnswerPath = Static<typeof AnswerPath>;
|
||
|
||
/**
|
||
* 各条保障这次动手的次数。tasks 只记读到的原始内容:读到"名字 → 非负整数"的对象就原样存;
|
||
* 没写,或者写的不是对象、值不是非负整数,记读不到并附原因。
|
||
* 名字和位置表对不对得上不在这里判断,由以后拿得到位置表的模块现算。
|
||
*/
|
||
export const SafeguardTriggers = Type.Union([
|
||
Type.Object(
|
||
{
|
||
status: Type.Literal("read"),
|
||
counts: Type.Record(Type.String(), Type.Integer({ minimum: 0 })),
|
||
},
|
||
CLOSED,
|
||
),
|
||
Type.Object(
|
||
{
|
||
status: Type.Literal("unreadable"),
|
||
reason: REASON,
|
||
},
|
||
CLOSED,
|
||
),
|
||
]);
|
||
export type SafeguardTriggers = Static<typeof SafeguardTriggers>;
|
||
|
||
export const JudgedResult = Type.Object(
|
||
{
|
||
verdict: Type.Enum(VERDICTS, { type: "string" }),
|
||
verdictReason: REASON,
|
||
/** 最终指标的分数(合法化之后的)。 */
|
||
score: Measured,
|
||
/** 各指标的取值,键是指标编号。 */
|
||
metrics: Type.Record(Id, Measured, CLOSED),
|
||
answerPath: AnswerPath,
|
||
safeguardTriggers: SafeguardTriggers,
|
||
/** 任务自己的附加记录,任务想记什么就记什么,例如填圆的合法化。 */
|
||
extra: Type.Unsafe<Json>({}),
|
||
},
|
||
CLOSED,
|
||
);
|
||
export type JudgedResult = Static<typeof JudgedResult>;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 模型用量(方法.md 第七部分:拿不到用量时记"未知",不记 0)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** 一次模型调用的用量。四种 token 互不重叠;价格单位为美元,与 Pi 的用量报告一致。 */
|
||
export const ModelUsage = Type.Object(
|
||
{
|
||
inputTokens: Measured,
|
||
cacheReadTokens: Measured,
|
||
cacheWriteTokens: Measured,
|
||
outputTokens: Measured,
|
||
cost: Measured,
|
||
},
|
||
CLOSED,
|
||
);
|
||
export type ModelUsage = Static<typeof ModelUsage>;
|