@crewhaus/eval-dataset 0.1.3 → 0.1.5
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/dist/errors.d.ts +4 -0
- package/{src/errors.ts → dist/errors.js} +3 -4
- package/dist/index.d.ts +94 -0
- package/{src/index.ts → dist/index.js} +20 -37
- package/dist/loaders/csv.d.ts +11 -0
- package/dist/loaders/csv.js +118 -0
- package/dist/loaders/http.d.ts +10 -0
- package/dist/loaders/http.js +127 -0
- package/dist/loaders/jsonl.d.ts +2 -0
- package/dist/loaders/jsonl.js +35 -0
- package/dist/loaders/yaml.d.ts +2 -0
- package/dist/loaders/yaml.js +42 -0
- package/package.json +9 -6
- package/src/__fixtures__/bare-array.yaml +0 -5
- package/src/__fixtures__/empty.jsonl +0 -0
- package/src/__fixtures__/ok.csv +0 -5
- package/src/__fixtures__/ok.jsonl +0 -3
- package/src/__fixtures__/ok.yaml +0 -12
- package/src/index.test.ts +0 -145
- package/src/loaders/csv.ts +0 -123
- package/src/loaders/http.ts +0 -141
- package/src/loaders/jsonl.ts +0 -37
- package/src/loaders/loaders.test.ts +0 -290
- package/src/loaders/yaml.ts +0 -47
package/src/index.test.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { DatasetLoadError, loadDataset, parseCsv } from "./index";
|
|
4
|
-
|
|
5
|
-
// `tsc -b` compiles this file into `dist/`, so `bun test` (with no path filter)
|
|
6
|
-
// picks up both `src/index.test.ts` and `dist/index.test.js`. The compiled copy
|
|
7
|
-
// resolves `import.meta.dir` to `dist/`, but `__fixtures__/` only exists under
|
|
8
|
-
// `src/`. Map back to the source tree so both copies find the fixtures.
|
|
9
|
-
const FIX = join(import.meta.dir.replace(/([/\\])dist$/, "$1src"), "__fixtures__");
|
|
10
|
-
|
|
11
|
-
async function collect<T>(iter: AsyncIterable<T>): Promise<T[]> {
|
|
12
|
-
const out: T[] = [];
|
|
13
|
-
for await (const item of iter) out.push(item);
|
|
14
|
-
return out;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("loadDataset — JSONL loader (T1)", () => {
|
|
18
|
-
test("parses well-formed JSONL with all field shapes", async () => {
|
|
19
|
-
const ds = await loadDataset(join(FIX, "ok.jsonl"));
|
|
20
|
-
expect(ds.name).toBe("ok");
|
|
21
|
-
const samples = await collect(ds.samples);
|
|
22
|
-
expect(samples).toHaveLength(3);
|
|
23
|
-
expect(samples[0]).toEqual({ id: "q1", input: "hello" });
|
|
24
|
-
expect(samples[1]?.expected_output).toBe("bye");
|
|
25
|
-
expect(samples[2]?.expected_tools).toEqual(["bash", "read"]);
|
|
26
|
-
expect(samples[2]?.metadata).toEqual({ difficulty: "easy" });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("yields zero samples for empty file", async () => {
|
|
30
|
-
const ds = await loadDataset(join(FIX, "empty.jsonl"));
|
|
31
|
-
expect(await collect(ds.samples)).toEqual([]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("rejects malformed JSON with line number", async () => {
|
|
35
|
-
const tmp = `${FIX}/__tmp_malformed.jsonl`;
|
|
36
|
-
await Bun.write(tmp, '{"id":"q1","input":"ok"}\nnot json\n');
|
|
37
|
-
try {
|
|
38
|
-
const ds = await loadDataset(tmp);
|
|
39
|
-
await expect(collect(ds.samples)).rejects.toThrow(/malformed JSON on line 2/);
|
|
40
|
-
} finally {
|
|
41
|
-
await Bun.file(tmp).delete();
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("rejects sample missing required fields", async () => {
|
|
46
|
-
const tmp = `${FIX}/__tmp_invalid.jsonl`;
|
|
47
|
-
await Bun.write(tmp, '{"input":"no id"}\n');
|
|
48
|
-
try {
|
|
49
|
-
const ds = await loadDataset(tmp);
|
|
50
|
-
await expect(collect(ds.samples)).rejects.toThrow(DatasetLoadError);
|
|
51
|
-
} finally {
|
|
52
|
-
await Bun.file(tmp).delete();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("rejects missing file", async () => {
|
|
57
|
-
await expect(loadDataset(`${FIX}/does-not-exist.jsonl`)).rejects.toThrow(DatasetLoadError);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("loadDataset — CSV loader (T1)", () => {
|
|
62
|
-
test("parses CSV with quoted fields, embedded newlines, and multi-tool column", async () => {
|
|
63
|
-
const ds = await loadDataset(join(FIX, "ok.csv"));
|
|
64
|
-
const samples = await collect(ds.samples);
|
|
65
|
-
expect(samples).toHaveLength(3);
|
|
66
|
-
expect(samples[0]).toEqual({ id: "q1", input: "hello" });
|
|
67
|
-
expect(samples[1]).toEqual({
|
|
68
|
-
id: "q2",
|
|
69
|
-
input: "hello, world",
|
|
70
|
-
expected_output: "greeting",
|
|
71
|
-
expected_tools: ["bash", "read"],
|
|
72
|
-
});
|
|
73
|
-
expect(samples[2]?.input).toBe("line1\nline2");
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("parseCsv — RFC 4180 subset (T1)", () => {
|
|
78
|
-
test("empty input yields no rows", () => {
|
|
79
|
-
expect(parseCsv("")).toEqual([]);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("escaped double-quotes inside quoted field", () => {
|
|
83
|
-
expect(parseCsv('a,"b ""quoted"" c"')).toEqual([["a", 'b "quoted" c']]);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("CRLF line endings", () => {
|
|
87
|
-
expect(parseCsv("a,b\r\nc,d\r\n")).toEqual([
|
|
88
|
-
["a", "b"],
|
|
89
|
-
["c", "d"],
|
|
90
|
-
]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("trailing newline is not a synthetic empty row", () => {
|
|
94
|
-
expect(parseCsv("a,b\n")).toEqual([["a", "b"]]);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("missing final newline still yields the last row", () => {
|
|
98
|
-
expect(parseCsv("a,b\nc,d")).toEqual([
|
|
99
|
-
["a", "b"],
|
|
100
|
-
["c", "d"],
|
|
101
|
-
]);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe("loadDataset — YAML loader (T1)", () => {
|
|
106
|
-
test("parses Dataset wrapper shape", async () => {
|
|
107
|
-
const ds = await loadDataset(join(FIX, "ok.yaml"));
|
|
108
|
-
expect(ds.name).toBe("yaml-fixture");
|
|
109
|
-
const samples = await collect(ds.samples);
|
|
110
|
-
expect(samples).toHaveLength(3);
|
|
111
|
-
expect(samples[0]).toEqual({ id: "q1", input: "hello" });
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("parses bare-array shape and derives name from filename", async () => {
|
|
115
|
-
const ds = await loadDataset(join(FIX, "bare-array.yaml"));
|
|
116
|
-
expect(ds.name).toBe("bare-array");
|
|
117
|
-
const samples = await collect(ds.samples);
|
|
118
|
-
expect(samples).toHaveLength(2);
|
|
119
|
-
expect(samples[1]?.expected_output).toBe("ok");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("supports .yml extension", async () => {
|
|
123
|
-
const tmp = `${FIX}/__tmp.yml`;
|
|
124
|
-
await Bun.write(tmp, "- id: x\n input: y\n");
|
|
125
|
-
try {
|
|
126
|
-
const ds = await loadDataset(tmp);
|
|
127
|
-
const samples = await collect(ds.samples);
|
|
128
|
-
expect(samples).toHaveLength(1);
|
|
129
|
-
} finally {
|
|
130
|
-
await Bun.file(tmp).delete();
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe("loadDataset — dispatch", () => {
|
|
136
|
-
test("rejects unknown extension", async () => {
|
|
137
|
-
await expect(loadDataset("/tmp/something.xyz")).rejects.toThrow(/unrecognized dataset source/);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("HTTP scheme dispatches to http loader (rejects offline lookups gracefully)", async () => {
|
|
141
|
-
// Simply verifies dispatch — actual fetch is exercised in __test__/http.test.ts
|
|
142
|
-
// when CREWHAUS_TEST_HTTP=1 is set.
|
|
143
|
-
await expect(loadDataset("http://127.0.0.1:1/never-listens.jsonl")).rejects.toThrow();
|
|
144
|
-
});
|
|
145
|
-
});
|
package/src/loaders/csv.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { DatasetLoadError } from "../errors";
|
|
3
|
-
import { type LoadedDataset, type Sample, SampleSchema } from "../index";
|
|
4
|
-
|
|
5
|
-
export async function loadCsv(path: string): Promise<LoadedDataset> {
|
|
6
|
-
const file = Bun.file(path);
|
|
7
|
-
if (!(await file.exists())) {
|
|
8
|
-
throw new DatasetLoadError(`file not found: ${path}`);
|
|
9
|
-
}
|
|
10
|
-
const text = await file.text();
|
|
11
|
-
const [header, ...dataRows] = parseCsv(text);
|
|
12
|
-
const name = basename(path).replace(/\.csv$/i, "");
|
|
13
|
-
if (!header) {
|
|
14
|
-
return { name, samples: emptyIterable() };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
name,
|
|
19
|
-
samples: rowsToSamples(dataRows, header, path),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function* emptyIterable(): AsyncIterable<Sample> {
|
|
24
|
-
/* nothing */
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function* rowsToSamples(
|
|
28
|
-
rows: ReadonlyArray<ReadonlyArray<string>>,
|
|
29
|
-
header: ReadonlyArray<string>,
|
|
30
|
-
path: string,
|
|
31
|
-
): AsyncIterable<Sample> {
|
|
32
|
-
let rowNo = 1; // header was row 0
|
|
33
|
-
for (const row of rows) {
|
|
34
|
-
rowNo += 1;
|
|
35
|
-
if (row.length === 1 && row[0] === "") continue; // trailing newline
|
|
36
|
-
const obj: Record<string, string | string[]> = {};
|
|
37
|
-
for (let i = 0; i < header.length; i++) {
|
|
38
|
-
const key = header[i];
|
|
39
|
-
if (key === undefined) continue;
|
|
40
|
-
const cell = row[i] ?? "";
|
|
41
|
-
// expected_tools is the only array-shaped field — comma-split
|
|
42
|
-
if (key === "expected_tools" && cell !== "") {
|
|
43
|
-
obj[key] = cell
|
|
44
|
-
.split(",")
|
|
45
|
-
.map((s) => s.trim())
|
|
46
|
-
.filter((s) => s.length > 0);
|
|
47
|
-
} else if (cell !== "") {
|
|
48
|
-
obj[key] = cell;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const result = SampleSchema.safeParse(obj);
|
|
52
|
-
if (!result.success) {
|
|
53
|
-
throw new DatasetLoadError(
|
|
54
|
-
`invalid sample on row ${rowNo} of ${path}: ${result.error.message}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
yield result.data;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* RFC 4180-style CSV parser. Handles:
|
|
63
|
-
* - quoted fields with embedded commas, newlines (CRLF/LF), and escaped `""`
|
|
64
|
-
* - trailing empty line tolerance
|
|
65
|
-
* - any combination of CRLF / LF / mixed line endings
|
|
66
|
-
*
|
|
67
|
-
* Returns one `string[]` per row. Empty input yields `[]`.
|
|
68
|
-
*/
|
|
69
|
-
export function parseCsv(text: string): string[][] {
|
|
70
|
-
const rows: string[][] = [];
|
|
71
|
-
if (text.length === 0) return rows;
|
|
72
|
-
let row: string[] = [];
|
|
73
|
-
let field = "";
|
|
74
|
-
let inQuotes = false;
|
|
75
|
-
let i = 0;
|
|
76
|
-
while (i < text.length) {
|
|
77
|
-
const ch = text[i];
|
|
78
|
-
if (inQuotes) {
|
|
79
|
-
if (ch === '"') {
|
|
80
|
-
if (text[i + 1] === '"') {
|
|
81
|
-
field += '"';
|
|
82
|
-
i += 2;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
inQuotes = false;
|
|
86
|
-
i += 1;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
field += ch;
|
|
90
|
-
i += 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (ch === '"') {
|
|
94
|
-
inQuotes = true;
|
|
95
|
-
i += 1;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (ch === ",") {
|
|
99
|
-
row.push(field);
|
|
100
|
-
field = "";
|
|
101
|
-
i += 1;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (ch === "\n" || ch === "\r") {
|
|
105
|
-
row.push(field);
|
|
106
|
-
field = "";
|
|
107
|
-
rows.push(row);
|
|
108
|
-
row = [];
|
|
109
|
-
// swallow CRLF as a single newline
|
|
110
|
-
if (ch === "\r" && text[i + 1] === "\n") i += 2;
|
|
111
|
-
else i += 1;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
field += ch;
|
|
115
|
-
i += 1;
|
|
116
|
-
}
|
|
117
|
-
// EOF without trailing newline
|
|
118
|
-
if (field !== "" || row.length > 0) {
|
|
119
|
-
row.push(field);
|
|
120
|
-
rows.push(row);
|
|
121
|
-
}
|
|
122
|
-
return rows;
|
|
123
|
-
}
|
package/src/loaders/http.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { parse as parseYaml } from "yaml";
|
|
2
|
-
import { DatasetLoadError } from "../errors";
|
|
3
|
-
import { DatasetSchema, type LoadedDataset, type Sample, SampleSchema } from "../index";
|
|
4
|
-
import { parseCsv } from "./csv";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* HTTP loader — fetches the URL into memory, then dispatches to the
|
|
8
|
-
* appropriate format parser based on the URL extension first, then
|
|
9
|
-
* the response Content-Type as a fallback.
|
|
10
|
-
*
|
|
11
|
-
* Buffers the entire body in memory; HuggingFace-scale datasets should
|
|
12
|
-
* be downloaded ahead of time and loaded via the local-file loaders.
|
|
13
|
-
*/
|
|
14
|
-
export async function loadHttp(url: string): Promise<LoadedDataset> {
|
|
15
|
-
const response = await fetch(url);
|
|
16
|
-
if (!response.ok) {
|
|
17
|
-
throw new DatasetLoadError(`HTTP ${response.status} fetching ${url}`);
|
|
18
|
-
}
|
|
19
|
-
const text = await response.text();
|
|
20
|
-
const ct = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
21
|
-
const lower = url.toLowerCase();
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
lower.endsWith(".jsonl") ||
|
|
25
|
-
lower.endsWith(".ndjson") ||
|
|
26
|
-
ct.includes("application/x-jsonlines") ||
|
|
27
|
-
ct.includes("application/x-ndjson")
|
|
28
|
-
) {
|
|
29
|
-
return { name: deriveName(url), samples: parseJsonlText(text, url) };
|
|
30
|
-
}
|
|
31
|
-
if (lower.endsWith(".csv") || ct.includes("text/csv")) {
|
|
32
|
-
return { name: deriveName(url), samples: parseCsvText(text, url) };
|
|
33
|
-
}
|
|
34
|
-
if (
|
|
35
|
-
lower.endsWith(".yaml") ||
|
|
36
|
-
lower.endsWith(".yml") ||
|
|
37
|
-
ct.includes("application/yaml") ||
|
|
38
|
-
ct.includes("text/yaml")
|
|
39
|
-
) {
|
|
40
|
-
return parseYamlText(text, url);
|
|
41
|
-
}
|
|
42
|
-
throw new DatasetLoadError(
|
|
43
|
-
`unrecognized HTTP dataset format for ${url} (content-type: ${ct || "unknown"})`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function deriveName(url: string): string {
|
|
48
|
-
try {
|
|
49
|
-
const u = new URL(url);
|
|
50
|
-
const last = u.pathname.split("/").filter(Boolean).pop() ?? "remote-dataset";
|
|
51
|
-
return last.replace(/\.(jsonl|ndjson|csv|yaml|yml)$/i, "");
|
|
52
|
-
} catch {
|
|
53
|
-
return "remote-dataset";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function* parseJsonlText(text: string, source: string): AsyncIterable<Sample> {
|
|
58
|
-
let lineNo = 0;
|
|
59
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
60
|
-
lineNo += 1;
|
|
61
|
-
if (raw.trim() === "") continue;
|
|
62
|
-
let parsed: unknown;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(raw);
|
|
65
|
-
} catch (err) {
|
|
66
|
-
throw new DatasetLoadError(`malformed JSON on line ${lineNo} of ${source}`, err);
|
|
67
|
-
}
|
|
68
|
-
const result = SampleSchema.safeParse(parsed);
|
|
69
|
-
if (!result.success) {
|
|
70
|
-
throw new DatasetLoadError(
|
|
71
|
-
`invalid sample on line ${lineNo} of ${source}: ${result.error.message}`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
yield result.data;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function* parseCsvText(text: string, source: string): AsyncIterable<Sample> {
|
|
79
|
-
const [header, ...dataRows] = parseCsv(text);
|
|
80
|
-
if (!header) return;
|
|
81
|
-
let rowNo = 1;
|
|
82
|
-
for (const row of dataRows) {
|
|
83
|
-
rowNo += 1;
|
|
84
|
-
if (row.length === 1 && row[0] === "") continue;
|
|
85
|
-
const obj: Record<string, string | string[]> = {};
|
|
86
|
-
for (let i = 0; i < header.length; i++) {
|
|
87
|
-
const key = header[i];
|
|
88
|
-
if (key === undefined) continue;
|
|
89
|
-
const cell = row[i] ?? "";
|
|
90
|
-
if (key === "expected_tools" && cell !== "") {
|
|
91
|
-
obj[key] = cell
|
|
92
|
-
.split(",")
|
|
93
|
-
.map((s) => s.trim())
|
|
94
|
-
.filter((s) => s.length > 0);
|
|
95
|
-
} else if (cell !== "") {
|
|
96
|
-
obj[key] = cell;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const result = SampleSchema.safeParse(obj);
|
|
100
|
-
if (!result.success) {
|
|
101
|
-
throw new DatasetLoadError(
|
|
102
|
-
`invalid sample on row ${rowNo} of ${source}: ${result.error.message}`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
yield result.data;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function parseYamlText(text: string, source: string): LoadedDataset {
|
|
110
|
-
let parsed: unknown;
|
|
111
|
-
try {
|
|
112
|
-
parsed = parseYaml(text);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
throw new DatasetLoadError(`malformed YAML in ${source}`, err);
|
|
115
|
-
}
|
|
116
|
-
if (Array.isArray(parsed)) {
|
|
117
|
-
return { name: deriveName(source), samples: yieldSamples(parsed, source) };
|
|
118
|
-
}
|
|
119
|
-
const result = DatasetSchema.safeParse(parsed);
|
|
120
|
-
if (!result.success) {
|
|
121
|
-
throw new DatasetLoadError(`invalid dataset in ${source}: ${result.error.message}`);
|
|
122
|
-
}
|
|
123
|
-
return { name: result.data.name, samples: yieldSamples(result.data.samples, source) };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function* yieldSamples(
|
|
127
|
-
samples: ReadonlyArray<unknown>,
|
|
128
|
-
source: string,
|
|
129
|
-
): AsyncIterable<Sample> {
|
|
130
|
-
let i = 0;
|
|
131
|
-
for (const raw of samples) {
|
|
132
|
-
const result = SampleSchema.safeParse(raw);
|
|
133
|
-
if (!result.success) {
|
|
134
|
-
throw new DatasetLoadError(
|
|
135
|
-
`invalid sample at index ${i} of ${source}: ${result.error.message}`,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
yield result.data;
|
|
139
|
-
i += 1;
|
|
140
|
-
}
|
|
141
|
-
}
|
package/src/loaders/jsonl.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { DatasetLoadError } from "../errors";
|
|
3
|
-
import { type LoadedDataset, type Sample, SampleSchema } from "../index";
|
|
4
|
-
|
|
5
|
-
export async function loadJsonl(path: string): Promise<LoadedDataset> {
|
|
6
|
-
const file = Bun.file(path);
|
|
7
|
-
if (!(await file.exists())) {
|
|
8
|
-
throw new DatasetLoadError(`file not found: ${path}`);
|
|
9
|
-
}
|
|
10
|
-
return {
|
|
11
|
-
name: basename(path).replace(/\.(jsonl|ndjson)$/i, ""),
|
|
12
|
-
samples: streamJsonl(path),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function* streamJsonl(path: string): AsyncIterable<Sample> {
|
|
17
|
-
const file = Bun.file(path);
|
|
18
|
-
const text = await file.text();
|
|
19
|
-
let lineNo = 0;
|
|
20
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
21
|
-
lineNo += 1;
|
|
22
|
-
if (raw.trim() === "") continue;
|
|
23
|
-
let parsed: unknown;
|
|
24
|
-
try {
|
|
25
|
-
parsed = JSON.parse(raw);
|
|
26
|
-
} catch (err) {
|
|
27
|
-
throw new DatasetLoadError(`malformed JSON on line ${lineNo} of ${path}`, err);
|
|
28
|
-
}
|
|
29
|
-
const result = SampleSchema.safeParse(parsed);
|
|
30
|
-
if (!result.success) {
|
|
31
|
-
throw new DatasetLoadError(
|
|
32
|
-
`invalid sample on line ${lineNo} of ${path}: ${result.error.message}`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
yield result.data;
|
|
36
|
-
}
|
|
37
|
-
}
|