langtrain 0.1.11 → 0.1.12
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/README.md +4 -4
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +10520 -7
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +10531 -8
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +10405 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10402 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +141 -7
- package/src/files.ts +53 -0
- package/src/index.ts +3 -0
- package/src/subscription.ts +45 -0
- package/src/training.ts +63 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
intro, outro, text, select, confirm, spinner, isCancel, note, cancel, password
|
|
4
|
+
} from '@clack/prompts';
|
|
3
5
|
import { bgCyan, black, red, green, yellow, gray } from 'kleur/colors';
|
|
4
6
|
import { Command } from 'commander';
|
|
5
|
-
import {
|
|
7
|
+
import { AgentClient, AgentCreate, FileClient, TrainingClient, SubscriptionClient, Langvision, Langtune } from './index';
|
|
6
8
|
import fs from 'fs';
|
|
7
9
|
import path from 'path';
|
|
8
10
|
import os from 'os';
|
|
@@ -64,6 +66,7 @@ async function main() {
|
|
|
64
66
|
// Operation Handlers Map (O(1) lookup)
|
|
65
67
|
const handlers: Record<string, (clients?: any) => Promise<void>> = {
|
|
66
68
|
'login': handleLogin,
|
|
69
|
+
'status': handleSubscriptionStatus,
|
|
67
70
|
'tune-finetune': (c) => handleTuneFinetune(c.tune),
|
|
68
71
|
'tune-generate': (c) => handleTuneGenerate(c.tune),
|
|
69
72
|
'vision-finetune': (c) => handleVisionFinetune(c.vision),
|
|
@@ -144,12 +147,39 @@ async function handleLogin() {
|
|
|
144
147
|
intro(green('API Key updated successfully!'));
|
|
145
148
|
}
|
|
146
149
|
|
|
150
|
+
async function handleSubscriptionStatus() {
|
|
151
|
+
const config = getConfig();
|
|
152
|
+
if (!config.apiKey) {
|
|
153
|
+
intro(red('Not logged in. Run "login" first.'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const client = new SubscriptionClient({ apiKey: config.apiKey });
|
|
157
|
+
const s = spinner();
|
|
158
|
+
s.start('Fetching subscription status...');
|
|
159
|
+
try {
|
|
160
|
+
const info = await client.getStatus();
|
|
161
|
+
s.stop(green('Subscription Status:'));
|
|
162
|
+
|
|
163
|
+
console.log(gray('Plan: ') + (info.plan === 'pro' ? bgCyan(' PRO ') : info.plan.toUpperCase()));
|
|
164
|
+
console.log(gray('Active: ') + (info.is_active ? green('Yes') : red('No')));
|
|
165
|
+
if (info.expires_at) console.log(gray('Expires: ') + new Date(info.expires_at).toLocaleDateString());
|
|
166
|
+
|
|
167
|
+
console.log(gray('\nLimits:'));
|
|
168
|
+
console.log(` Models: ${info.limits.max_models === -1 ? 'Unlimited' : info.limits.max_models}`);
|
|
169
|
+
console.log(` Training Jobs: ${info.limits.max_training_jobs}`);
|
|
170
|
+
|
|
171
|
+
} catch (e: any) {
|
|
172
|
+
s.stop(red('Failed to fetch status.'));
|
|
173
|
+
console.error(e.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
147
177
|
async function handleAgentCreate(client: AgentClient) {
|
|
148
178
|
const name = await text({
|
|
149
179
|
message: 'Agent Name:',
|
|
150
180
|
placeholder: 'e.g. Support Bot',
|
|
151
181
|
validate(value) {
|
|
152
|
-
if (value.length === 0) return '
|
|
182
|
+
if (!value || value.length === 0) return 'API Key is required';
|
|
153
183
|
},
|
|
154
184
|
});
|
|
155
185
|
if (isCancel(name)) return;
|
|
@@ -202,7 +232,7 @@ async function handleAgentCreate(client: AgentClient) {
|
|
|
202
232
|
const wid = await text({
|
|
203
233
|
message: 'Enter Workspace ID (UUID):',
|
|
204
234
|
validate(value) {
|
|
205
|
-
if (value.length === 0) return 'Required';
|
|
235
|
+
if (!value || value.length === 0) return 'Required';
|
|
206
236
|
},
|
|
207
237
|
});
|
|
208
238
|
if (isCancel(wid)) return;
|
|
@@ -353,10 +383,60 @@ async function handleTuneFinetune(tune: Langtune) {
|
|
|
353
383
|
});
|
|
354
384
|
if (isCancel(epochs)) cancel('Operation cancelled.');
|
|
355
385
|
|
|
386
|
+
const track = await select({
|
|
387
|
+
message: 'Track this job on Langtrain Cloud?',
|
|
388
|
+
options: [
|
|
389
|
+
{ value: 'yes', label: 'Yes', hint: 'Upload dataset and log job' },
|
|
390
|
+
{ value: 'no', label: 'No', hint: 'Local only' }
|
|
391
|
+
]
|
|
392
|
+
});
|
|
393
|
+
if (isCancel(track)) cancel('Operation cancelled.');
|
|
394
|
+
|
|
395
|
+
if (track === 'yes') {
|
|
396
|
+
const s = spinner();
|
|
397
|
+
s.start('Connecting to Cloud...');
|
|
398
|
+
try {
|
|
399
|
+
const config = getConfig();
|
|
400
|
+
if (!config.apiKey) throw new Error('API Key required. Run "login" first.');
|
|
401
|
+
|
|
402
|
+
// Check Subscription
|
|
403
|
+
const subClient = new SubscriptionClient({ apiKey: config.apiKey });
|
|
404
|
+
const sub = await subClient.getStatus();
|
|
405
|
+
if (!sub.features.includes('cloud_finetuning')) {
|
|
406
|
+
s.stop(red('Feature "cloud_finetuning" is not available on your plan.'));
|
|
407
|
+
const upgrade = await confirm({ message: 'Upgrade to Pro for cloud tracking?' });
|
|
408
|
+
if (upgrade && !isCancel(upgrade)) {
|
|
409
|
+
console.log(bgCyan(black(' Visit https://langtrain.ai/dashboard/billing to upgrade. ')));
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const fileClient = new FileClient({ apiKey: config.apiKey });
|
|
415
|
+
const trainingClient = new TrainingClient({ apiKey: config.apiKey });
|
|
416
|
+
|
|
417
|
+
s.message('Uploading dataset...');
|
|
418
|
+
const fileResp = await fileClient.upload(trainFile as string);
|
|
419
|
+
|
|
420
|
+
s.message('Creating Job...');
|
|
421
|
+
const job = await trainingClient.createJob({
|
|
422
|
+
name: `cli-sft-${Date.now()}`,
|
|
423
|
+
base_model: model as string,
|
|
424
|
+
dataset_id: fileResp.id,
|
|
425
|
+
task: 'text',
|
|
426
|
+
hyperparameters: {
|
|
427
|
+
n_epochs: parseInt(epochs as string)
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
s.stop(green(`Job tracked: ${job.id}`));
|
|
431
|
+
} catch (e: any) {
|
|
432
|
+
s.stop(red(`Tracking failed: ${e.message}`));
|
|
433
|
+
const cont = await confirm({ message: 'Continue with local training anyway?' });
|
|
434
|
+
if (!cont || isCancel(cont)) return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
356
438
|
const s = spinner();
|
|
357
|
-
s.start('
|
|
358
|
-
await new Promise(r => setTimeout(r, 800)); // Simulatoin
|
|
359
|
-
s.message('Starting fine-tuning job...');
|
|
439
|
+
s.start('Starting local fine-tuning...');
|
|
360
440
|
|
|
361
441
|
try {
|
|
362
442
|
// Check if FinetuneConfig types match what's needed.
|
|
@@ -430,6 +510,60 @@ async function handleVisionFinetune(vision: Langvision) {
|
|
|
430
510
|
placeholder: '3',
|
|
431
511
|
initialValue: '3'
|
|
432
512
|
});
|
|
513
|
+
if (isCancel(epochs)) cancel('Operation cancelled');
|
|
514
|
+
|
|
515
|
+
const track = await select({
|
|
516
|
+
message: 'Track this job on Langtrain Cloud?',
|
|
517
|
+
options: [
|
|
518
|
+
{ value: 'yes', label: 'Yes', hint: 'Upload dataset and log job' },
|
|
519
|
+
{ value: 'no', label: 'No', hint: 'Local only' }
|
|
520
|
+
]
|
|
521
|
+
});
|
|
522
|
+
if (isCancel(track)) cancel('Operation cancelled');
|
|
523
|
+
|
|
524
|
+
if (track === 'yes') {
|
|
525
|
+
const s = spinner();
|
|
526
|
+
s.start('Connecting to Cloud...');
|
|
527
|
+
try {
|
|
528
|
+
const config = getConfig();
|
|
529
|
+
if (!config.apiKey) throw new Error('API Key required. Run "login" first.');
|
|
530
|
+
|
|
531
|
+
// Check Subscription
|
|
532
|
+
const subClient = new SubscriptionClient({ apiKey: config.apiKey });
|
|
533
|
+
const sub = await subClient.getStatus();
|
|
534
|
+
if (!sub.features.includes('cloud_finetuning')) {
|
|
535
|
+
s.stop(red('Feature "cloud_finetuning" is not available on your plan.'));
|
|
536
|
+
const upgrade = await confirm({ message: 'Upgrade to Pro for cloud tracking?' });
|
|
537
|
+
if (upgrade && !isCancel(upgrade)) {
|
|
538
|
+
console.log(bgCyan(black(' Visit https://langtrain.ai/dashboard/billing to upgrade. ')));
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const fileClient = new FileClient({ apiKey: config.apiKey });
|
|
544
|
+
const trainingClient = new TrainingClient({ apiKey: config.apiKey });
|
|
545
|
+
|
|
546
|
+
s.message('Uploading dataset...');
|
|
547
|
+
const fileResp = await fileClient.upload(dataset as string, undefined, 'fine-tune-vision');
|
|
548
|
+
|
|
549
|
+
s.message('Creating Job...');
|
|
550
|
+
const job = await trainingClient.createJob({
|
|
551
|
+
name: `cli-vision-${Date.now()}`,
|
|
552
|
+
base_model: model as string,
|
|
553
|
+
dataset_id: fileResp.id,
|
|
554
|
+
task: 'vision',
|
|
555
|
+
training_method: 'lora',
|
|
556
|
+
hyperparameters: {
|
|
557
|
+
n_epochs: parseInt(epochs as string)
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
s.stop(green(`Job tracked: ${job.id}`));
|
|
561
|
+
} catch (e: any) {
|
|
562
|
+
s.stop(red(`Tracking failed: ${e.message}`));
|
|
563
|
+
const cont = await confirm({ message: 'Continue with local training anyway?' });
|
|
564
|
+
if (!cont || isCancel(cont)) return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
433
567
|
|
|
434
568
|
const s = spinner();
|
|
435
569
|
s.start('Analyzing dataset structure...');
|
package/src/files.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class FileClient {
|
|
2
|
+
private client: any; // AxiosInstance
|
|
3
|
+
|
|
4
|
+
constructor(config: { apiKey: string, baseUrl?: string }) {
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
this.client = axios.create({
|
|
7
|
+
baseURL: config.baseUrl || 'https://api.langtrain.ai/api/v1',
|
|
8
|
+
headers: {
|
|
9
|
+
'X-API-Key': config.apiKey,
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async upload(file: any, workspaceId?: string, purpose: string = 'fine-tune'): Promise<FileResponse> {
|
|
15
|
+
const FormData = require('form-data');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
const form = new FormData();
|
|
19
|
+
// Check if file is a path or buffer. Assuming path for CLI
|
|
20
|
+
if (typeof file === 'string') {
|
|
21
|
+
if (!fs.existsSync(file)) throw new Error(`File not found: ${file}`);
|
|
22
|
+
form.append('file', fs.createReadStream(file));
|
|
23
|
+
} else {
|
|
24
|
+
// Handle buffer or other types if needed, but for now strict to path
|
|
25
|
+
throw new Error('File path required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (workspaceId) form.append('workspace_id', workspaceId);
|
|
29
|
+
form.append('purpose', purpose);
|
|
30
|
+
|
|
31
|
+
const response = await this.client.post('/files', form, {
|
|
32
|
+
headers: {
|
|
33
|
+
...form.getHeaders()
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return response.data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async list(workspaceId: string, purpose?: string): Promise<FileResponse[]> {
|
|
40
|
+
const params: any = { workspace_id: workspaceId };
|
|
41
|
+
if (purpose) params.purpose = purpose;
|
|
42
|
+
const response = await this.client.get('/files', { params });
|
|
43
|
+
return response.data.data;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface FileResponse {
|
|
48
|
+
id: string;
|
|
49
|
+
filename: string;
|
|
50
|
+
purpose: string;
|
|
51
|
+
bytes: number;
|
|
52
|
+
created_at: string;
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,9 @@ export { Langtune } from 'langtune';
|
|
|
5
5
|
|
|
6
6
|
// Export Agent Client
|
|
7
7
|
export { AgentClient, Agent, AgentRun, AgentCreate } from './agent';
|
|
8
|
+
export { FileClient, FileResponse } from './files';
|
|
9
|
+
export { TrainingClient, FineTuneJobCreate, FineTuneJobResponse } from './training';
|
|
10
|
+
export { SubscriptionClient, SubscriptionInfo, FeatureCheck } from './subscription';
|
|
8
11
|
|
|
9
12
|
// Export Types with Namespaces to avoid collisions
|
|
10
13
|
import * as Vision from 'langvision';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class SubscriptionClient {
|
|
2
|
+
private client: any; // AxiosInstance
|
|
3
|
+
|
|
4
|
+
constructor(config: { apiKey: string, baseUrl?: string }) {
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
this.client = axios.create({
|
|
7
|
+
baseURL: config.baseUrl || 'https://api.langtrain.ai/api/v1',
|
|
8
|
+
headers: {
|
|
9
|
+
'X-API-Key': config.apiKey,
|
|
10
|
+
'Content-Type': 'application/json'
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getStatus(): Promise<SubscriptionInfo> {
|
|
16
|
+
const response = await this.client.get('/subscription/status');
|
|
17
|
+
return response.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async checkFeature(feature: string): Promise<FeatureCheck> {
|
|
21
|
+
const response = await this.client.get(`/subscription/check/${feature}`);
|
|
22
|
+
return response.data;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getLimits(): Promise<any> {
|
|
26
|
+
const response = await this.client.get('/subscription/analytics');
|
|
27
|
+
return response.data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SubscriptionInfo {
|
|
32
|
+
is_active: boolean;
|
|
33
|
+
plan: string;
|
|
34
|
+
plan_name: string;
|
|
35
|
+
expires_at?: string;
|
|
36
|
+
features: string[];
|
|
37
|
+
limits: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface FeatureCheck {
|
|
41
|
+
feature: string;
|
|
42
|
+
allowed: boolean;
|
|
43
|
+
limit?: number;
|
|
44
|
+
used?: number;
|
|
45
|
+
}
|
package/src/training.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class TrainingClient {
|
|
2
|
+
private client: any; // AxiosInstance
|
|
3
|
+
|
|
4
|
+
constructor(config: { apiKey: string, baseUrl?: string }) {
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
this.client = axios.create({
|
|
7
|
+
baseURL: config.baseUrl || 'https://api.langtrain.ai/api/v1',
|
|
8
|
+
headers: {
|
|
9
|
+
'X-API-Key': config.apiKey,
|
|
10
|
+
'Content-Type': 'application/json'
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async createJob(job: FineTuneJobCreate): Promise<FineTuneJobResponse> {
|
|
16
|
+
const response = await this.client.post('/finetune/jobs', job);
|
|
17
|
+
return response.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async listJobs(workspaceId: string, limit: number = 10): Promise<FineTuneJobList> {
|
|
21
|
+
const response = await this.client.get('/finetune/jobs', {
|
|
22
|
+
params: { workspace_id: workspaceId, limit }
|
|
23
|
+
});
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getJob(jobId: string): Promise<FineTuneJobResponse> {
|
|
28
|
+
const response = await this.client.get(`/finetune/jobs/${jobId}`);
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async cancelJob(jobId: string): Promise<FineTuneJobResponse> {
|
|
33
|
+
const response = await this.client.post(`/finetune/jobs/${jobId}/cancel`);
|
|
34
|
+
return response.data;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FineTuneJobCreate {
|
|
39
|
+
name?: string;
|
|
40
|
+
base_model: string;
|
|
41
|
+
model_id?: string;
|
|
42
|
+
dataset_id: string;
|
|
43
|
+
guardrail_id?: string;
|
|
44
|
+
task?: 'text' | 'vision';
|
|
45
|
+
training_method?: 'sft' | 'dpo' | 'rlhf' | 'lora' | 'qlora';
|
|
46
|
+
hyperparameters?: any;
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface FineTuneJobResponse {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
status: string;
|
|
54
|
+
progress: number;
|
|
55
|
+
error_message?: string;
|
|
56
|
+
created_at: string;
|
|
57
|
+
[key: string]: any;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface FineTuneJobList {
|
|
61
|
+
data: FineTuneJobResponse[];
|
|
62
|
+
has_more: boolean;
|
|
63
|
+
}
|