-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathodysseysbench.ts
More file actions
139 lines (129 loc) · 4.86 KB
/
Copy pathodysseysbench.ts
File metadata and controls
139 lines (129 loc) · 4.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type { Testcase, EvalInput, AgentModelEntry } from "../types/evals.js";
import { normalizeRubric, type AvailableModel } from "@browserbasehq/stagehand";
import { tasksConfig } from "../taskConfig.js";
import { getPackageRootDir } from "../runtimePaths.js";
import {
readJsonlFile,
parseJsonlRows,
applySampling,
normalizeAgentModelEntries,
} from "../utils.js";
/**
* Build OdysseysBench testcases.
*
* OdysseysBench (https://odysseysbench.com) is a 200-task web-agent benchmark
* spanning easy/medium/hard difficulty. Every task ships a weighted rubric
* (baked into `precomputed_rubric` by scripts/build-odysseysbench-dataset.ts),
* so the verifier scores against the published criteria directly rather than
* generating its own.
*
* Env knobs:
* - EVAL_MAX_K / EVAL_ODYSSEYSBENCH_LIMIT — cap the number of tasks (default 25).
* - EVAL_ODYSSEYSBENCH_SAMPLE — random sample size (overrides the limit cap).
* - EVAL_ODYSSEYSBENCH_LEVEL — comma-separated difficulty filter (easy,medium,hard).
* - EVAL_ODYSSEYSBENCH_IDS — comma-separated task_ids to run exactly, in order
* (ignores sampling / limit / level knobs).
*/
export const buildOdysseysBenchTestcases = (
models: string[] | AgentModelEntry[],
): Testcase[] => {
const odysseysbenchFilePath =
getPackageRootDir() + "/datasets/odysseysbench/OdysseysBench_data.jsonl";
const lines = readJsonlFile(odysseysbenchFilePath);
const maxCases = process.env.EVAL_MAX_K
? Number(process.env.EVAL_MAX_K)
: process.env.EVAL_ODYSSEYSBENCH_LIMIT
? Number(process.env.EVAL_ODYSSEYSBENCH_LIMIT)
: 25;
const sampleCount = process.env.EVAL_ODYSSEYSBENCH_SAMPLE
? Number(process.env.EVAL_ODYSSEYSBENCH_SAMPLE)
: undefined;
type OdysseysBenchRow = {
task_id: string;
confirmed_task: string;
website?: string;
level?: "easy" | "medium" | "hard";
reference_length?: number;
categories?: string[];
/**
* Per-task weighted rubric in verifier `{ items: [...] }` shape, produced
* from the published rubrics by scripts/build-odysseysbench-dataset.ts.
*/
precomputed_rubric?: unknown;
[key: string]: unknown;
};
function isOdysseysBenchRow(parsed: unknown): parsed is OdysseysBenchRow {
if (parsed === null || typeof parsed !== "object") return false;
const obj = parsed as Record<string, unknown>;
return (
typeof obj.task_id === "string" && typeof obj.confirmed_task === "string"
);
}
const candidates = parseJsonlRows(lines, isOdysseysBenchRow);
// EVAL_ODYSSEYSBENCH_IDS restricts the suite to exactly those task IDs,
// preserving the order given and ignoring sampling / limit / level knobs.
const explicitIds = process.env.EVAL_ODYSSEYSBENCH_IDS
? process.env.EVAL_ODYSSEYSBENCH_IDS.split(",")
.map((s) => s.trim())
.filter(Boolean)
: null;
let rows: OdysseysBenchRow[];
if (explicitIds && explicitIds.length > 0) {
const byId = new Map(candidates.map((r) => [r.task_id, r]));
rows = explicitIds
.map((id) => byId.get(id))
.filter((r): r is OdysseysBenchRow => Boolean(r));
} else {
// Optional difficulty filter, applied before sampling.
const levelFilter = process.env.EVAL_ODYSSEYSBENCH_LEVEL
? new Set(
process.env.EVAL_ODYSSEYSBENCH_LEVEL.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean),
)
: null;
const filtered = levelFilter
? candidates.filter((r) => r.level && levelFilter.has(r.level))
: candidates;
rows = applySampling(filtered, sampleCount, maxCases);
}
const allTestcases: Testcase[] = [];
for (const modelEntry of normalizeAgentModelEntries(models)) {
for (const row of rows) {
const input: EvalInput = {
name: "agent/odysseysbench",
modelName: modelEntry.modelName as AvailableModel,
agentMode: modelEntry.mode,
isCUA: modelEntry.mode === "cua",
params: {
task_id: row.task_id,
confirmed_task: row.confirmed_task,
website: row.website,
level: row.level,
reference_length: row.reference_length,
precomputed_rubric: normalizeRubric(row.precomputed_rubric),
},
};
const taskCategories =
tasksConfig.find((t) => t.name === input.name)?.categories || [];
allTestcases.push({
input,
name: input.name,
tags: [modelEntry.modelName, modelEntry.mode, "odysseysbench"],
metadata: {
model: modelEntry.modelName as AvailableModel,
test: `${input.name}:${row.task_id}`,
tier: "bench",
task: input.name,
category: taskCategories[0] || "agent",
categories: taskCategories,
dataset: "odysseysbench",
task_id: row.task_id,
task_category: row.level,
},
expected: true,
});
}
}
return allTestcases;
};