Case transformers
Normalize benchmark-specific datasets into immutable Trunchbull eval cases without adding an SDK or rewriting upstream source files.
A case transformer converts one or more benchmark-owned source files into the canonical eval-case shape that Trunchbull runs. Transformers are useful when prompts, labels, solutions, or verifier arguments live in different files or use a benchmark-specific schema.
Transformers do not replace evaluators:
raw source files
↓
case transformer
↓
normalized model prompt + private grading data
↓
model trial
↓
evaluatorThe platform invokes the transformer while inspecting and compiling the pinned repository. Every normalized case is then frozen into the immutable release. At run time, every selected model receives the same compiled prompt, tools, and limits. Private expected answers and custom-evaluator case data are not added to the model prompt.
Repository layout
Keep upstream data separate from authored transformation and evaluation logic:
benchmark/
├── benchmark.config.ts
├── data/
│ ├── cases.jsonl
│ └── labels.jsonl
├── transformers/
│ └── index.ts
└── evals/
└── benchmark.tsThe eval names its transformer and raw sources. Paths are relative to the eval module:
export default {
id: "benchmark",
title: "Benchmark",
cases: {
transformer: "../transformers/index.ts",
sources: {
cases: "../data/cases.jsonl",
labels: "../data/labels.jsonl",
},
},
evaluate: { kind: "non_empty" },
}An eval may declare between one and sixteen named sources. Source formats are not prescribed: the transformer receives their exact text, path, and SHA-256 digest.
Transformer module
transformers/index.ts default-exports a plain object with a name and
transform function:
export default {
name: "Example transformer",
transform({ sources }) {
const labels = JSON.parse(sources.labels.content)
return sources.cases.content
.split(/\r?\n/)
.filter(Boolean)
.map((line) => {
const row = JSON.parse(line)
const expected = labels[row.id]
return {
id: row.id,
prompt: row.prompt,
description: row.description ?? "",
evaluate: {
kind: "exact",
expected,
},
}
})
},
}The function may be synchronous or asynchronous. It must return between one and 25,000 ordinary author cases. Each case may define:
- a stable
id; - the model-facing
promptand optionalsystemPrompt; - available
tools; - JSON-compatible private
datafor a module-level custom evaluator; - a built-in per-case
evaluatespecification.
Do not place solutions in prompt. Put expected values in the evaluator or in
data, which is delivered only to a custom evaluator after model generation.
Validation and determinism
Inspection:
- loads only the named regular source files;
- hashes every source;
- bundles the transformer from the pinned repository;
- executes it twice with cloned inputs;
- rejects nondeterministic output;
- validates every returned author case;
- rejects duplicate IDs and releases above 25,000 cases;
- stores the transformer name, paths, source hashes, byte counts, and case count in the immutable release manifest;
- compiles both the normalized cases and that provenance into the release digest.
Transformer modules use ordinary JavaScript or TypeScript, built-in APIs, and relative imports. They do not import a Trunchbull SDK. Network requests, environment-dependent values, randomness, and current-time behavior should not be used because a release must reproduce exactly from its source commit.
Preview and export locally
Use the same transformer contract before publishing:
pnpm benchmarks:transform -- \
--transformer=transformers/index.ts \
--source=cases=data/cases.jsonl \
--source=labels=data/labels.jsonl \
--output=exports/cases.jsonlThe command writes normalized JSONL plus
exports/cases.jsonl.manifest.json. The manifest records transformer and source
hashes, output hash, format, and case count.
Use JSON output when another tool expects an array:
pnpm benchmarks:transform -- \
--transformer=transformers/index.ts \
--source=cases=data/cases.jsonl \
--output=exports/cases.json \
--format=jsonBatch exports
A batch manifest can transform several benchmark ports in one deterministic authoring pass:
{
"protocolVersion": 1,
"jobs": [
{
"id": "arc-challenge",
"transformer": "arc-challenge/transformers/index.ts",
"sources": {
"cases": "arc-challenge/data/cases.jsonl"
},
"output": "arc-challenge/exports/cases.jsonl"
}
]
}Run every job with:
pnpm benchmarks:transform:batch -- --manifest=transform-batch.jsonPaths in a batch manifest are relative to that manifest. The command writes an
individual receipt for every export and a combined .report.json.
What belongs elsewhere
A transformer normalizes data; it does not create missing runtime infrastructure.
- Use an evaluator for output grading.
- Use
tools/for callable benchmark resources. - Mark cases that need a sandbox and provide their environment separately.
- Keep probability-scored tasks unported until the model runtime exposes the required probabilities.
- Use a custom evaluator when deterministic grading cannot be expressed by a built-in evaluator.
The complete ARC-Challenge example in
examples/benchmarks/arc-challenge demonstrates raw source data, a transformer,
an eval declaration, and an exported normalized fixture.