feat: 研究编号只许小写、配置路径必须绝对;检验判重两个顺序取平均
任务书 13 第一部分,用户 2026-10-03 同意。 - config:studyId 限定小写字母、数字和 ._-;task、promptDir 必须以 / 开头。 - eval:research.json 路径改绝对,writeConfig 不再转换;quality.ts 第 2、6 问每对 A、B 与 B、A 各问一次取平均,两次原始概率都存。 - tests:大写编号、相对路径各加测试。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+2
-7
@@ -1,6 +1,6 @@
|
||||
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import { join } from "node:path";
|
||||
|
||||
// 检验脚本共用的起研究工具:检验目录放在仓库外,配置照用户的写法写一份,研究在子进程里跑(要能被 SIGKILL)。
|
||||
|
||||
@@ -15,15 +15,10 @@ export function newEvalDir(name: string): string {
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 照一份底稿写这次检验用的 research.json:换研究编号(容器名、镜像标签都带它),需要时换各角色模型;
|
||||
* 任务目录、提示词目录写成绝对路径(底稿里相对仓库根目录),这样配置文件放在仓库外也指得对。
|
||||
*/
|
||||
/** 照一份底稿写这次检验用的 research.json:换研究编号(容器名、镜像标签都带它),需要时换各角色模型。 */
|
||||
export function writeConfig(base: string, path: string, studyId: string, models: unknown): string {
|
||||
const config = JSON.parse(readFileSync(base, "utf8")) as Record<string, unknown>;
|
||||
config["studyId"] = studyId;
|
||||
config["task"] = resolve(config["task"] as string);
|
||||
config["promptDir"] = resolve(config["promptDir"] as string);
|
||||
if (models !== null) config["models"] = models;
|
||||
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
|
||||
return path;
|
||||
|
||||
+28
-13
@@ -1,6 +1,6 @@
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { ask, THRESHOLD_NOTE, type Answer, type Noul } from "../lib/jev.ts";
|
||||
import { ask, THRESHOLD, THRESHOLD_NOTE, type Answer, type Noul } from "../lib/jev.ts";
|
||||
import { entriesIn, entryDecision, readLedger, stages, type Entry } from "../lib/ledger.ts";
|
||||
import { ENTRY_ONLY, LEADS_TO_IDEA, SAME_KIND, STOPS_AT_DEAD_END, TASKS } from "./questions.ts";
|
||||
|
||||
@@ -56,14 +56,29 @@ function query(site: string, ids: string[], state: Record<string, unknown>, ques
|
||||
}
|
||||
return cache.get(key)!;
|
||||
}
|
||||
const yes = async (p: Promise<Answer[]>, question: string) => (await p).find((a) => a.question === question)!.yes;
|
||||
const answer = async (p: Promise<Answer[]>, question: string) => (await p).find((a) => a.question === question)!;
|
||||
const yes = async (p: Promise<Answer[]>, question: string) => (await answer(p, question)).yes;
|
||||
|
||||
const ideaState = (e: Entry, idea: Idea) => ({ task: TASKS.idea, entry: show(e), idea: { description: idea.description } });
|
||||
const leadsTo = (e: Entry, idea: Idea) => yes(query("leads", [e.id, idea.id], ideaState(e, idea), LEADS_TO_IDEA), "leads_to_idea");
|
||||
const staysIn = (e: Entry, idea: Idea) => yes(query("stays", [e.id, idea.id], ideaState(e, idea), STOPS_AT_DEAD_END), "stays_within_idea");
|
||||
const entryOnly = (e: Entry, question: string) => yes(query("entry", [e.id], { task: TASKS.entry, entry: show(e) }, ENTRY_ONLY), question);
|
||||
const sameKind = (a: Entry, b: Entry) =>
|
||||
yes(query("pair", [a.id, b.id], { task: TASKS.pair, entry_a: show(a), entry_b: show(b) }, SAME_KIND), "same_kind_of_change");
|
||||
const pairProbability = async (a: Entry, b: Entry) =>
|
||||
(await answer(query("pair", [a.id, b.id], { task: TASKS.pair, entry_a: show(a), entry_b: show(b) }, SAME_KIND), "same_kind_of_change"))
|
||||
.probability;
|
||||
|
||||
/** 一对入口是不是同一条路:问题本身对称,所以 A、B 和 B、A 各问一次,取两次概率的平均再比阈值(任务书 13 第一部分第 3 条)。 */
|
||||
type PairJudgement = { pair: [string, string]; ab: number; ba: number; mean: number; same: boolean };
|
||||
async function sameKind(a: Entry, b: Entry): Promise<PairJudgement> {
|
||||
const [ab, ba] = await Promise.all([pairProbability(a, b), pairProbability(b, a)]);
|
||||
const mean = (ab + ba) / 2;
|
||||
return { pair: [a.id, b.id], ab, ba, mean, same: mean > THRESHOLD };
|
||||
}
|
||||
type PairsResult = { pairs: PairJudgement[]; same: string[][] };
|
||||
async function judgePairs(pairs: Array<readonly [Entry, Entry]>): Promise<PairsResult> {
|
||||
const judged = await Promise.all(pairs.map(([a, b]) => sameKind(a, b)));
|
||||
return { pairs: judged, same: judged.filter((j) => j.same).map((j) => j.pair) };
|
||||
}
|
||||
|
||||
async function coverage(list: Entry[]): Promise<Record<string, string[]>> {
|
||||
const out: Record<string, string[]> = {};
|
||||
@@ -73,11 +88,10 @@ async function coverage(list: Entry[]): Promise<Record<string, string[]>> {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
async function overlap(list: Entry[]): Promise<{ pairs: number; same: string[][]; ratio: number | null }> {
|
||||
const pairs = list.flatMap((a, i) => list.slice(i + 1).filter((b) => b.author !== a.author).map((b) => [a, b] as const));
|
||||
const hits = await Promise.all(pairs.map(([a, b]) => sameKind(a, b)));
|
||||
const same = pairs.filter((_, i) => hits[i]).map(([a, b]) => [a.id, b.id]);
|
||||
return { pairs: pairs.length, same, ratio: pairs.length === 0 ? null : same.length / pairs.length };
|
||||
/** 重合度:不同作者的入口两两成对,判"是"的对数占总对数的比例。 */
|
||||
async function overlap(list: Entry[]): Promise<PairsResult & { ratio: number | null }> {
|
||||
const judged = await judgePairs(list.flatMap((a, i) => list.slice(i + 1).filter((b) => b.author !== a.author).map((b) => [a, b] as const)));
|
||||
return { ...judged, ratio: judged.pairs.length === 0 ? null : judged.same.length / judged.pairs.length };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -89,8 +103,8 @@ const covFinal = await coverage(final);
|
||||
const q1 = { coverage: covFinal, pass: covFinal["K1"]!.length > 0 };
|
||||
|
||||
const assignedPairs = assigned.flatMap((a, i) => assigned.slice(i + 1).map((b) => [a, b] as const));
|
||||
const pairHits = await Promise.all(assignedPairs.map(([a, b]) => sameKind(a, b)));
|
||||
const q2 = { same: assignedPairs.filter((_, i) => pairHits[i]).map(([a, b]) => [a.id, b.id]), pass: !pairHits.some(Boolean) };
|
||||
const assignedJudged = await judgePairs(assignedPairs);
|
||||
const q2 = { ...assignedJudged, pass: assignedJudged.same.length === 0 };
|
||||
|
||||
const assignedK1 = await Promise.all(assigned.map((e) => leadsTo(e, k1)));
|
||||
const deadEnd = await Promise.all(
|
||||
@@ -134,13 +148,14 @@ const report = {
|
||||
writeFileSync(join(out, "quality.json"), `${JSON.stringify(report, null, 2)}\n`);
|
||||
|
||||
const mark = (b: boolean) => (b ? "通过" : "不通过");
|
||||
const ratio = (o: { pairs: number; same: string[][]; ratio: number | null }) => `${o.same.length}/${o.pairs}${o.ratio === null ? "" : `(${(o.ratio * 100).toFixed(0)}%)`}`;
|
||||
const ratio = (o: PairsResult & { ratio: number | null }) => `${o.same.length}/${o.pairs.length}${o.ratio === null ? "" : `(${(o.ratio * 100).toFixed(0)}%)`}`;
|
||||
const count = (c: Record<string, string[]>) => Object.entries(c).map(([k, v]) => `${k}:${v.length}`).join(" ");
|
||||
console.log(`(${THRESHOLD_NOTE})`);
|
||||
console.log(`入口全体:${final.map((e) => `${e.id} ${e.name}`).join("\n ")}`);
|
||||
console.log(`分到的入口:${report.assignment.map((a) => `${a.researcher} → ${a.entry}(第 ${a.rank} 志愿)${a.name}`).join("\n ")}`);
|
||||
console.log(`1 方向对不对:${mark(q1.pass)};覆盖 ${count(covFinal)}`);
|
||||
console.log(`2 分没分开:${mark(q2.pass)};判"是"的对 ${JSON.stringify(q2.same)}`);
|
||||
const pairLine = (j: PairJudgement) => `${j.pair.join("、")}:${j.ab.toFixed(2)}/${j.ba.toFixed(2)},平均 ${j.mean.toFixed(2)}`;
|
||||
console.log(`2 分没分开:${mark(q2.pass)};判"是"的对 ${JSON.stringify(q2.same)};每对两个顺序的概率\n ${q2.pairs.map(pairLine).join("\n ")}`);
|
||||
console.log(`3 好方向分出去没有:${mark(q3.pass)};指向 K1 的 ${JSON.stringify(q3.k1)},停在死路的 ${JSON.stringify(q3.deadEnds)}`);
|
||||
console.log(`4 改代码的路线:${mark(q4.pass)};不是的 ${JSON.stringify(q4.notCodeRoute)}`);
|
||||
console.log(`5 有没有写答案:${mark(q5pass)};分到的里写了的 ${JSON.stringify(q5.assignedWritingAnswer)},入口全体里写了的 ${JSON.stringify(q5.allWritingAnswer)}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"studyId": "r0-eval",
|
||||
"task": "tasks/circle_packing",
|
||||
"task": "/home/iomgaa/Projects/InfoRSI/tasks/circle_packing",
|
||||
"models": {
|
||||
"researchers": [
|
||||
{ "provider": "iomgaa", "model": "glm-5.3", "thinking": "medium" },
|
||||
@@ -17,7 +17,7 @@
|
||||
"factorLimit": 3,
|
||||
"maxRepairs": 1,
|
||||
"lightChecks": { "maxCount": 2, "seconds": 5 },
|
||||
"promptDir": "prompts",
|
||||
"promptDir": "/home/iomgaa/Projects/InfoRSI/prompts",
|
||||
"devEntry": false,
|
||||
"flow": {
|
||||
"preset": "full",
|
||||
|
||||
Reference in New Issue
Block a user