aitelier 0.1.0
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/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +141 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/add.test.d.ts +2 -0
- package/dist/commands/add.test.d.ts.map +1 -0
- package/dist/commands/add.test.js +203 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/eval.d.ts +3 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +505 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/eval.test.d.ts +2 -0
- package/dist/commands/eval.test.d.ts.map +1 -0
- package/dist/commands/eval.test.js +804 -0
- package/dist/commands/eval.test.js.map +1 -0
- package/dist/commands/format.d.ts +3 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +198 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/format.test.d.ts +2 -0
- package/dist/commands/format.test.d.ts.map +1 -0
- package/dist/commands/format.test.js +568 -0
- package/dist/commands/format.test.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +94 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +123 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/rate.d.ts +3 -0
- package/dist/commands/rate.d.ts.map +1 -0
- package/dist/commands/rate.js +209 -0
- package/dist/commands/rate.js.map +1 -0
- package/dist/commands/rate.test.d.ts +2 -0
- package/dist/commands/rate.test.d.ts.map +1 -0
- package/dist/commands/rate.test.js +393 -0
- package/dist/commands/rate.test.js.map +1 -0
- package/dist/commands/split.d.ts +3 -0
- package/dist/commands/split.d.ts.map +1 -0
- package/dist/commands/split.js +210 -0
- package/dist/commands/split.js.map +1 -0
- package/dist/commands/split.test.d.ts +2 -0
- package/dist/commands/split.test.d.ts.map +1 -0
- package/dist/commands/split.test.js +503 -0
- package/dist/commands/split.test.js.map +1 -0
- package/dist/commands/stats.d.ts +3 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +209 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/stats.test.d.ts +2 -0
- package/dist/commands/stats.test.d.ts.map +1 -0
- package/dist/commands/stats.test.js +476 -0
- package/dist/commands/stats.test.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +132 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/status.test.d.ts +2 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +443 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/commands/train.d.ts +3 -0
- package/dist/commands/train.d.ts.map +1 -0
- package/dist/commands/train.js +139 -0
- package/dist/commands/train.js.map +1 -0
- package/dist/commands/train.test.d.ts +2 -0
- package/dist/commands/train.test.d.ts.map +1 -0
- package/dist/commands/train.test.js +288 -0
- package/dist/commands/train.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/openai.d.ts +9 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +20 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/together.d.ts +11 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/together.js +118 -0
- package/dist/providers/together.js.map +1 -0
- package/dist/providers/types.d.ts +28 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/storage/config.d.ts +24 -0
- package/dist/storage/config.d.ts.map +1 -0
- package/dist/storage/config.js +11 -0
- package/dist/storage/config.js.map +1 -0
- package/dist/storage/dataset.d.ts +14 -0
- package/dist/storage/dataset.d.ts.map +1 -0
- package/dist/storage/dataset.js +18 -0
- package/dist/storage/dataset.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { writeFile, mkdtemp, rm, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { registerStats } from './stats.js';
|
|
7
|
+
describe('ft stats', () => {
|
|
8
|
+
let testDir;
|
|
9
|
+
let originalCwd;
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
// Create a temporary directory for each test
|
|
12
|
+
testDir = await mkdtemp(join(tmpdir(), 'ft-stats-test-'));
|
|
13
|
+
originalCwd = process.cwd();
|
|
14
|
+
process.chdir(testDir);
|
|
15
|
+
// Create basic project structure
|
|
16
|
+
const config = {
|
|
17
|
+
name: 'test-project',
|
|
18
|
+
provider: 'together',
|
|
19
|
+
model: 'meta-llama/Llama-3.3-70B-Instruct',
|
|
20
|
+
qualityThreshold: 8,
|
|
21
|
+
runs: [],
|
|
22
|
+
};
|
|
23
|
+
await writeFile('.ftpipeline.json', JSON.stringify(config, null, 2));
|
|
24
|
+
await mkdir('data', { recursive: true });
|
|
25
|
+
});
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
process.chdir(originalCwd);
|
|
28
|
+
await rm(testDir, { recursive: true, force: true });
|
|
29
|
+
vi.restoreAllMocks();
|
|
30
|
+
});
|
|
31
|
+
it('should display stats for a dataset with rated and unrated examples', async () => {
|
|
32
|
+
const examples = [
|
|
33
|
+
{
|
|
34
|
+
id: 1,
|
|
35
|
+
messages: [
|
|
36
|
+
{ role: 'user', content: 'Q1' },
|
|
37
|
+
{ role: 'assistant', content: 'A1' },
|
|
38
|
+
],
|
|
39
|
+
rating: 8,
|
|
40
|
+
createdAt: new Date().toISOString(),
|
|
41
|
+
version: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 2,
|
|
45
|
+
messages: [
|
|
46
|
+
{ role: 'user', content: 'Q2' },
|
|
47
|
+
{ role: 'assistant', content: 'A2' },
|
|
48
|
+
],
|
|
49
|
+
rating: 9,
|
|
50
|
+
createdAt: new Date().toISOString(),
|
|
51
|
+
version: 1,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 3,
|
|
55
|
+
messages: [
|
|
56
|
+
{ role: 'user', content: 'Q3' },
|
|
57
|
+
{ role: 'assistant', content: 'A3' },
|
|
58
|
+
],
|
|
59
|
+
rating: null,
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
version: 1,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
65
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
66
|
+
const program = new Command();
|
|
67
|
+
registerStats(program);
|
|
68
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
69
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Dataset Health Overview'));
|
|
70
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Total examples: 3'));
|
|
71
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rated: 2 (67%)'));
|
|
72
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Unrated: 1 (33%)'));
|
|
73
|
+
});
|
|
74
|
+
it('should display rating distribution histogram', async () => {
|
|
75
|
+
const examples = [
|
|
76
|
+
{
|
|
77
|
+
id: 1,
|
|
78
|
+
messages: [
|
|
79
|
+
{ role: 'user', content: 'Q1' },
|
|
80
|
+
{ role: 'assistant', content: 'A1' },
|
|
81
|
+
],
|
|
82
|
+
rating: 5,
|
|
83
|
+
createdAt: new Date().toISOString(),
|
|
84
|
+
version: 1,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 2,
|
|
88
|
+
messages: [
|
|
89
|
+
{ role: 'user', content: 'Q2' },
|
|
90
|
+
{ role: 'assistant', content: 'A2' },
|
|
91
|
+
],
|
|
92
|
+
rating: 8,
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
version: 1,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 3,
|
|
98
|
+
messages: [
|
|
99
|
+
{ role: 'user', content: 'Q3' },
|
|
100
|
+
{ role: 'assistant', content: 'A3' },
|
|
101
|
+
],
|
|
102
|
+
rating: 8,
|
|
103
|
+
createdAt: new Date().toISOString(),
|
|
104
|
+
version: 1,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 4,
|
|
108
|
+
messages: [
|
|
109
|
+
{ role: 'user', content: 'Q4' },
|
|
110
|
+
{ role: 'assistant', content: 'A4' },
|
|
111
|
+
],
|
|
112
|
+
rating: 10,
|
|
113
|
+
createdAt: new Date().toISOString(),
|
|
114
|
+
version: 1,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
118
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
119
|
+
const program = new Command();
|
|
120
|
+
registerStats(program);
|
|
121
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
122
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rating Distribution:'));
|
|
123
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('10/10'));
|
|
124
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(' 8/10'));
|
|
125
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(' 5/10'));
|
|
126
|
+
});
|
|
127
|
+
it('should show quality threshold analysis', async () => {
|
|
128
|
+
const examples = [
|
|
129
|
+
{
|
|
130
|
+
id: 1,
|
|
131
|
+
messages: [
|
|
132
|
+
{ role: 'user', content: 'Q1' },
|
|
133
|
+
{ role: 'assistant', content: 'A1' },
|
|
134
|
+
],
|
|
135
|
+
rating: 7,
|
|
136
|
+
createdAt: new Date().toISOString(),
|
|
137
|
+
version: 1,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 2,
|
|
141
|
+
messages: [
|
|
142
|
+
{ role: 'user', content: 'Q2' },
|
|
143
|
+
{ role: 'assistant', content: 'A2' },
|
|
144
|
+
],
|
|
145
|
+
rating: 8,
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
version: 1,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 3,
|
|
151
|
+
messages: [
|
|
152
|
+
{ role: 'user', content: 'Q3' },
|
|
153
|
+
{ role: 'assistant', content: 'A3' },
|
|
154
|
+
],
|
|
155
|
+
rating: 9,
|
|
156
|
+
createdAt: new Date().toISOString(),
|
|
157
|
+
version: 1,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
161
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
162
|
+
const program = new Command();
|
|
163
|
+
registerStats(program);
|
|
164
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
165
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Quality Analysis:'));
|
|
166
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Quality threshold: 8/10'));
|
|
167
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Examples ≥ 8: 2 (67%)'));
|
|
168
|
+
});
|
|
169
|
+
it('should show train/val split status when not split', async () => {
|
|
170
|
+
const examples = [
|
|
171
|
+
{
|
|
172
|
+
id: 1,
|
|
173
|
+
messages: [
|
|
174
|
+
{ role: 'user', content: 'Q1' },
|
|
175
|
+
{ role: 'assistant', content: 'A1' },
|
|
176
|
+
],
|
|
177
|
+
rating: 8,
|
|
178
|
+
createdAt: new Date().toISOString(),
|
|
179
|
+
version: 1,
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
await writeFile('data/examples.jsonl', JSON.stringify(examples[0]) + '\n');
|
|
183
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
184
|
+
const program = new Command();
|
|
185
|
+
registerStats(program);
|
|
186
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
187
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Train/Val Split:'));
|
|
188
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Not yet split (run `ft split` to assign train/val splits)'));
|
|
189
|
+
});
|
|
190
|
+
it('should show train/val split status when split is assigned', async () => {
|
|
191
|
+
const examples = [
|
|
192
|
+
{
|
|
193
|
+
id: 1,
|
|
194
|
+
messages: [
|
|
195
|
+
{ role: 'user', content: 'Q1' },
|
|
196
|
+
{ role: 'assistant', content: 'A1' },
|
|
197
|
+
],
|
|
198
|
+
rating: 8,
|
|
199
|
+
createdAt: new Date().toISOString(),
|
|
200
|
+
version: 1,
|
|
201
|
+
split: 'train',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 2,
|
|
205
|
+
messages: [
|
|
206
|
+
{ role: 'user', content: 'Q2' },
|
|
207
|
+
{ role: 'assistant', content: 'A2' },
|
|
208
|
+
],
|
|
209
|
+
rating: 9,
|
|
210
|
+
createdAt: new Date().toISOString(),
|
|
211
|
+
version: 1,
|
|
212
|
+
split: 'val',
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
216
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
217
|
+
const program = new Command();
|
|
218
|
+
registerStats(program);
|
|
219
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
220
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Train: 1 examples'));
|
|
221
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Val: 1 examples'));
|
|
222
|
+
});
|
|
223
|
+
it('should detect when train.jsonl and val.jsonl files exist', async () => {
|
|
224
|
+
const examples = [
|
|
225
|
+
{
|
|
226
|
+
id: 1,
|
|
227
|
+
messages: [
|
|
228
|
+
{ role: 'user', content: 'Q1' },
|
|
229
|
+
{ role: 'assistant', content: 'A1' },
|
|
230
|
+
],
|
|
231
|
+
rating: 8,
|
|
232
|
+
createdAt: new Date().toISOString(),
|
|
233
|
+
version: 1,
|
|
234
|
+
split: 'train',
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
await writeFile('data/examples.jsonl', JSON.stringify(examples[0]) + '\n');
|
|
238
|
+
await writeFile('data/train.jsonl', JSON.stringify(examples[0]) + '\n');
|
|
239
|
+
await writeFile('data/val.jsonl', JSON.stringify(examples[0]) + '\n');
|
|
240
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
241
|
+
const program = new Command();
|
|
242
|
+
registerStats(program);
|
|
243
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
244
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✓ train.jsonl exists'));
|
|
245
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✓ val.jsonl exists'));
|
|
246
|
+
});
|
|
247
|
+
it('should show missing train/val files when split is assigned but files not generated', async () => {
|
|
248
|
+
const examples = [
|
|
249
|
+
{
|
|
250
|
+
id: 1,
|
|
251
|
+
messages: [
|
|
252
|
+
{ role: 'user', content: 'Q1' },
|
|
253
|
+
{ role: 'assistant', content: 'A1' },
|
|
254
|
+
],
|
|
255
|
+
rating: 8,
|
|
256
|
+
createdAt: new Date().toISOString(),
|
|
257
|
+
version: 1,
|
|
258
|
+
split: 'train',
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
await writeFile('data/examples.jsonl', JSON.stringify(examples[0]) + '\n');
|
|
262
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
263
|
+
const program = new Command();
|
|
264
|
+
registerStats(program);
|
|
265
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
266
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✗ train.jsonl not found (run `ft format` to generate)'));
|
|
267
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✗ val.jsonl not found (run `ft format` to generate)'));
|
|
268
|
+
});
|
|
269
|
+
it('should assess readiness and show dataset is ready', async () => {
|
|
270
|
+
// Create 20+ high-quality examples with split and files
|
|
271
|
+
const examples = Array.from({ length: 25 }, (_, i) => ({
|
|
272
|
+
id: i + 1,
|
|
273
|
+
messages: [
|
|
274
|
+
{ role: 'user', content: `Q${i + 1}` },
|
|
275
|
+
{ role: 'assistant', content: `A${i + 1}` },
|
|
276
|
+
],
|
|
277
|
+
rating: 8,
|
|
278
|
+
createdAt: new Date().toISOString(),
|
|
279
|
+
version: 1,
|
|
280
|
+
split: i % 5 === 0 ? 'val' : 'train',
|
|
281
|
+
}));
|
|
282
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
283
|
+
await writeFile('data/train.jsonl', '');
|
|
284
|
+
await writeFile('data/val.jsonl', '');
|
|
285
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
286
|
+
const program = new Command();
|
|
287
|
+
registerStats(program);
|
|
288
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
289
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Readiness:'));
|
|
290
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✓ Dataset is ready for training'));
|
|
291
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Run `ft train` to start fine-tuning'));
|
|
292
|
+
});
|
|
293
|
+
it('should assess readiness and show issues when dataset not ready', async () => {
|
|
294
|
+
const examples = [
|
|
295
|
+
{
|
|
296
|
+
id: 1,
|
|
297
|
+
messages: [
|
|
298
|
+
{ role: 'user', content: 'Q1' },
|
|
299
|
+
{ role: 'assistant', content: 'A1' },
|
|
300
|
+
],
|
|
301
|
+
rating: null,
|
|
302
|
+
createdAt: new Date().toISOString(),
|
|
303
|
+
version: 1,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 2,
|
|
307
|
+
messages: [
|
|
308
|
+
{ role: 'user', content: 'Q2' },
|
|
309
|
+
{ role: 'assistant', content: 'A2' },
|
|
310
|
+
],
|
|
311
|
+
rating: 5,
|
|
312
|
+
createdAt: new Date().toISOString(),
|
|
313
|
+
version: 1,
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
317
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
318
|
+
const program = new Command();
|
|
319
|
+
registerStats(program);
|
|
320
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
321
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('✗ Dataset not ready for training:'));
|
|
322
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('unrated examples'));
|
|
323
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('meet quality threshold (recommend 20+ for fine-tuning)'));
|
|
324
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('No train/val split assigned'));
|
|
325
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Training files not generated'));
|
|
326
|
+
});
|
|
327
|
+
it('should display message when no examples exist', async () => {
|
|
328
|
+
await writeFile('data/examples.jsonl', '');
|
|
329
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
330
|
+
const program = new Command();
|
|
331
|
+
registerStats(program);
|
|
332
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
333
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('No examples found. Add examples with `ft add` first.');
|
|
334
|
+
});
|
|
335
|
+
it('should display message when examples file does not exist', async () => {
|
|
336
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
337
|
+
const program = new Command();
|
|
338
|
+
registerStats(program);
|
|
339
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
340
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('No examples found. Add examples with `ft add` first.');
|
|
341
|
+
});
|
|
342
|
+
it('should fail if project is not initialized', async () => {
|
|
343
|
+
await rm('.ftpipeline.json');
|
|
344
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
345
|
+
throw new Error('process.exit called');
|
|
346
|
+
});
|
|
347
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
348
|
+
const program = new Command();
|
|
349
|
+
registerStats(program);
|
|
350
|
+
await expect(program.parseAsync(['node', 'test', 'stats'])).rejects.toThrow('process.exit called');
|
|
351
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Project not initialized'));
|
|
352
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
353
|
+
});
|
|
354
|
+
it('should handle dataset with all examples rated', async () => {
|
|
355
|
+
const examples = [
|
|
356
|
+
{
|
|
357
|
+
id: 1,
|
|
358
|
+
messages: [
|
|
359
|
+
{ role: 'user', content: 'Q1' },
|
|
360
|
+
{ role: 'assistant', content: 'A1' },
|
|
361
|
+
],
|
|
362
|
+
rating: 8,
|
|
363
|
+
createdAt: new Date().toISOString(),
|
|
364
|
+
version: 1,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: 2,
|
|
368
|
+
messages: [
|
|
369
|
+
{ role: 'user', content: 'Q2' },
|
|
370
|
+
{ role: 'assistant', content: 'A2' },
|
|
371
|
+
],
|
|
372
|
+
rating: 9,
|
|
373
|
+
createdAt: new Date().toISOString(),
|
|
374
|
+
version: 1,
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
378
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
379
|
+
const program = new Command();
|
|
380
|
+
registerStats(program);
|
|
381
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
382
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rated: 2 (100%)'));
|
|
383
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Unrated: 0 (0%)'));
|
|
384
|
+
});
|
|
385
|
+
it('should handle dataset with all examples unrated', async () => {
|
|
386
|
+
const examples = [
|
|
387
|
+
{
|
|
388
|
+
id: 1,
|
|
389
|
+
messages: [
|
|
390
|
+
{ role: 'user', content: 'Q1' },
|
|
391
|
+
{ role: 'assistant', content: 'A1' },
|
|
392
|
+
],
|
|
393
|
+
rating: null,
|
|
394
|
+
createdAt: new Date().toISOString(),
|
|
395
|
+
version: 1,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: 2,
|
|
399
|
+
messages: [
|
|
400
|
+
{ role: 'user', content: 'Q2' },
|
|
401
|
+
{ role: 'assistant', content: 'A2' },
|
|
402
|
+
],
|
|
403
|
+
rating: null,
|
|
404
|
+
createdAt: new Date().toISOString(),
|
|
405
|
+
version: 1,
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
409
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
410
|
+
const program = new Command();
|
|
411
|
+
registerStats(program);
|
|
412
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
413
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rated: 0 (0%)'));
|
|
414
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Unrated: 2 (100%)'));
|
|
415
|
+
// Should not display rating distribution when no ratings
|
|
416
|
+
const allCalls = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
417
|
+
expect(allCalls).not.toContain('Rating Distribution:');
|
|
418
|
+
});
|
|
419
|
+
it('should calculate percentages correctly', async () => {
|
|
420
|
+
const examples = Array.from({ length: 10 }, (_, i) => ({
|
|
421
|
+
id: i + 1,
|
|
422
|
+
messages: [
|
|
423
|
+
{ role: 'user', content: `Q${i + 1}` },
|
|
424
|
+
{ role: 'assistant', content: `A${i + 1}` },
|
|
425
|
+
],
|
|
426
|
+
rating: i < 7 ? 8 : null,
|
|
427
|
+
createdAt: new Date().toISOString(),
|
|
428
|
+
version: 1,
|
|
429
|
+
}));
|
|
430
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
431
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
432
|
+
const program = new Command();
|
|
433
|
+
registerStats(program);
|
|
434
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
435
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rated: 7 (70%)'));
|
|
436
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Unrated: 3 (30%)'));
|
|
437
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Examples ≥ 8: 7 (70%)'));
|
|
438
|
+
});
|
|
439
|
+
it('should display recommendation to rate more examples when insufficient quality', async () => {
|
|
440
|
+
const examples = Array.from({ length: 10 }, (_, i) => ({
|
|
441
|
+
id: i + 1,
|
|
442
|
+
messages: [
|
|
443
|
+
{ role: 'user', content: `Q${i + 1}` },
|
|
444
|
+
{ role: 'assistant', content: `A${i + 1}` },
|
|
445
|
+
],
|
|
446
|
+
rating: null,
|
|
447
|
+
createdAt: new Date().toISOString(),
|
|
448
|
+
version: 1,
|
|
449
|
+
}));
|
|
450
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
451
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
452
|
+
const program = new Command();
|
|
453
|
+
registerStats(program);
|
|
454
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
455
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Rate more examples to identify high-quality data'));
|
|
456
|
+
});
|
|
457
|
+
it('should display recommendation to add more examples when all rated but insufficient quality', async () => {
|
|
458
|
+
const examples = Array.from({ length: 10 }, (_, i) => ({
|
|
459
|
+
id: i + 1,
|
|
460
|
+
messages: [
|
|
461
|
+
{ role: 'user', content: `Q${i + 1}` },
|
|
462
|
+
{ role: 'assistant', content: `A${i + 1}` },
|
|
463
|
+
],
|
|
464
|
+
rating: 5,
|
|
465
|
+
createdAt: new Date().toISOString(),
|
|
466
|
+
version: 1,
|
|
467
|
+
}));
|
|
468
|
+
await writeFile('data/examples.jsonl', examples.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
469
|
+
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
470
|
+
const program = new Command();
|
|
471
|
+
registerStats(program);
|
|
472
|
+
await program.parseAsync(['node', 'test', 'stats']);
|
|
473
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Add more examples with `ft add` or lower quality threshold'));
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
//# sourceMappingURL=stats.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.test.js","sourceRoot":"","sources":["../../src/commands/stats.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAarD"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { loadConfig, saveConfig } from '../storage/config.js';
|
|
4
|
+
import { TogetherProvider } from '../providers/together.js';
|
|
5
|
+
const CONFIG_FILE = '.ftpipeline.json';
|
|
6
|
+
export function registerStatus(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('status')
|
|
9
|
+
.description('Check fine-tuning job status')
|
|
10
|
+
.option('--all', 'Show all runs')
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
try {
|
|
13
|
+
await statusCommand(options);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function statusCommand(options) {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
// Check if project is initialized
|
|
24
|
+
try {
|
|
25
|
+
await access(join(cwd, CONFIG_FILE));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error('Project not initialized. Run `ft init` first to create .ftpipeline.json');
|
|
29
|
+
}
|
|
30
|
+
// Load config
|
|
31
|
+
const config = await loadConfig(cwd);
|
|
32
|
+
// Check if any jobs exist
|
|
33
|
+
if (config.runs.length === 0) {
|
|
34
|
+
console.log('\n' + '═'.repeat(70));
|
|
35
|
+
console.log('No Training Jobs Found');
|
|
36
|
+
console.log('═'.repeat(70));
|
|
37
|
+
console.log('\nNo fine-tuning jobs have been started yet.');
|
|
38
|
+
console.log('\nTo start a job, run: ft train');
|
|
39
|
+
console.log('═'.repeat(70) + '\n');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Initialize provider based on config
|
|
43
|
+
let provider;
|
|
44
|
+
if (config.provider === 'together') {
|
|
45
|
+
provider = new TogetherProvider();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error(`Provider "${config.provider}" not yet supported. Use "together" for now.`);
|
|
49
|
+
}
|
|
50
|
+
// Determine which runs to show
|
|
51
|
+
const runsToCheck = options.all ? config.runs : [config.runs[config.runs.length - 1]];
|
|
52
|
+
console.log('\n' + '═'.repeat(70));
|
|
53
|
+
console.log(options.all ? 'All Training Jobs' : 'Latest Training Job');
|
|
54
|
+
console.log('═'.repeat(70));
|
|
55
|
+
let configUpdated = false;
|
|
56
|
+
for (let i = 0; i < runsToCheck.length; i++) {
|
|
57
|
+
const run = runsToCheck[i];
|
|
58
|
+
// Fetch live status from API
|
|
59
|
+
const jobStatus = await provider.getJobStatus(run.jobId);
|
|
60
|
+
// Update config if status changed or model ID is now available
|
|
61
|
+
if (run.status !== jobStatus.status || (jobStatus.modelId && !run.modelId)) {
|
|
62
|
+
run.status = jobStatus.status;
|
|
63
|
+
if (jobStatus.modelId) {
|
|
64
|
+
run.modelId = jobStatus.modelId;
|
|
65
|
+
}
|
|
66
|
+
configUpdated = true;
|
|
67
|
+
}
|
|
68
|
+
// Display job information
|
|
69
|
+
if (i > 0) {
|
|
70
|
+
console.log('\n' + '━'.repeat(70));
|
|
71
|
+
}
|
|
72
|
+
console.log(`\nJob ID: ${run.jobId}`);
|
|
73
|
+
console.log(`Provider: ${run.provider}`);
|
|
74
|
+
console.log(`Status: ${formatStatus(jobStatus.status)}`);
|
|
75
|
+
console.log(`Started: ${new Date(run.startedAt).toLocaleString()}`);
|
|
76
|
+
// Show model ID if completed
|
|
77
|
+
if (jobStatus.modelId) {
|
|
78
|
+
console.log(`Model ID: ${jobStatus.modelId}`);
|
|
79
|
+
}
|
|
80
|
+
// Show error if failed
|
|
81
|
+
if (jobStatus.error) {
|
|
82
|
+
console.log(`Error: ${jobStatus.error}`);
|
|
83
|
+
}
|
|
84
|
+
// Show hyperparameters
|
|
85
|
+
if (run.hyperparameters) {
|
|
86
|
+
console.log('\nHyperparameters:');
|
|
87
|
+
console.log(` Epochs: ${run.hyperparameters.epochs || 'N/A'}`);
|
|
88
|
+
console.log(` Batch size: ${run.hyperparameters.batchSize || 'N/A'}`);
|
|
89
|
+
console.log(` Learning rate: ${run.hyperparameters.learningRate || 'N/A'}`);
|
|
90
|
+
console.log(` LoRA rank: ${run.hyperparameters.loraR || 'N/A'}`);
|
|
91
|
+
console.log(` LoRA alpha: ${run.hyperparameters.loraAlpha || 'N/A'}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Save config if any updates were made
|
|
95
|
+
if (configUpdated) {
|
|
96
|
+
await saveConfig(config, cwd);
|
|
97
|
+
}
|
|
98
|
+
// Display next steps based on status
|
|
99
|
+
const latestRun = config.runs[config.runs.length - 1];
|
|
100
|
+
console.log('\n' + '═'.repeat(70));
|
|
101
|
+
console.log('Next Steps');
|
|
102
|
+
console.log('═'.repeat(70));
|
|
103
|
+
if (latestRun.status === 'completed' && latestRun.modelId) {
|
|
104
|
+
console.log('\nJob completed successfully!');
|
|
105
|
+
console.log(`\nYou can now evaluate your model: ft eval`);
|
|
106
|
+
console.log(`Model ID: ${latestRun.modelId}`);
|
|
107
|
+
}
|
|
108
|
+
else if (latestRun.status === 'failed') {
|
|
109
|
+
console.log('\nJob failed. Check the error message above.');
|
|
110
|
+
console.log('You may need to adjust your training data or hyperparameters.');
|
|
111
|
+
}
|
|
112
|
+
else if (latestRun.status === 'cancelled') {
|
|
113
|
+
console.log('\nJob was cancelled.');
|
|
114
|
+
console.log('You can start a new job with: ft train');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`\nJob is ${latestRun.status}. Check back later for updates.`);
|
|
118
|
+
console.log('Run `ft status` again to refresh.');
|
|
119
|
+
}
|
|
120
|
+
console.log('═'.repeat(70) + '\n');
|
|
121
|
+
}
|
|
122
|
+
function formatStatus(status) {
|
|
123
|
+
const statusEmoji = {
|
|
124
|
+
pending: '⏳ pending',
|
|
125
|
+
running: '🔄 running',
|
|
126
|
+
completed: '✅ completed',
|
|
127
|
+
failed: '❌ failed',
|
|
128
|
+
cancelled: '🚫 cancelled',
|
|
129
|
+
};
|
|
130
|
+
return statusEmoji[status] || status;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=status.js.map
|