langtrain 0.1.14 → 0.1.16
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 +33 -11
- package/dist/chunk-PAHGEWDE.js +30 -0
- package/dist/chunk-PAHGEWDE.js.map +1 -0
- package/dist/chunk-Q46V6ODQ.mjs +30 -0
- package/dist/chunk-Q46V6ODQ.mjs.map +1 -0
- package/dist/cli.d.mts +3 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -23508
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +5 -23523
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +45 -3
- package/dist/index.d.ts +45 -3
- package/dist/index.js +1 -23056
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -23044
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
- package/src/cli/auth.ts +76 -0
- package/src/cli/config.ts +28 -0
- package/src/cli/handlers/agent.ts +183 -0
- package/src/cli/handlers/subscription.ts +30 -0
- package/src/cli/handlers/tune.ts +155 -0
- package/src/cli/handlers/vision.ts +159 -0
- package/src/cli/index.ts +154 -0
- package/src/cli/menu.ts +70 -0
- package/src/cli/ui.ts +50 -0
- package/src/index.ts +8 -6
- package/src/{agent.ts → lib/agent.ts} +2 -2
- package/src/lib/models.ts +61 -0
- package/tsup.config.ts +7 -2
- package/src/cli.ts +0 -622
- /package/src/{files.ts → lib/files.ts} +0 -0
- /package/src/{subscription.ts → lib/subscription.ts} +0 -0
- /package/src/{training.ts → lib/training.ts} +0 -0
package/src/cli.ts
DELETED
|
@@ -1,622 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
intro, outro, text, select, confirm, spinner, isCancel, note, cancel, password
|
|
4
|
-
} from '@clack/prompts';
|
|
5
|
-
import { bgCyan, black, red, green, yellow, gray } from 'kleur/colors';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import { AgentClient, AgentCreate, FileClient, TrainingClient, SubscriptionClient, Langvision, Langtune } from './index';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import os from 'os';
|
|
11
|
-
import gradient from 'gradient-string';
|
|
12
|
-
|
|
13
|
-
// Configuration
|
|
14
|
-
const CONFIG_DIR = path.join(os.homedir(), '.langtrain');
|
|
15
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
|
-
|
|
17
|
-
function getConfig() {
|
|
18
|
-
if (!fs.existsSync(CONFIG_FILE)) return {};
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
21
|
-
} catch {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function saveConfig(config: any) {
|
|
27
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
-
}
|
|
30
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const packageJson = require(path.join(__dirname, '../package.json'));
|
|
34
|
-
|
|
35
|
-
async function main() {
|
|
36
|
-
const program = new Command();
|
|
37
|
-
const version = packageJson.version;
|
|
38
|
-
|
|
39
|
-
program
|
|
40
|
-
.name('langtrain')
|
|
41
|
-
.description(packageJson.description || 'Langtrain CLI for AI Model Fine-tuning and Generation')
|
|
42
|
-
.version(version);
|
|
43
|
-
|
|
44
|
-
program.action(async () => {
|
|
45
|
-
console.clear();
|
|
46
|
-
|
|
47
|
-
// Gradient Banner
|
|
48
|
-
const banner = `
|
|
49
|
-
██╗ █████╗ ███╗ ██╗ ██████╗████████╗██████╗ █████╗ ██╗███╗ ██╗
|
|
50
|
-
██║ ██╔══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║
|
|
51
|
-
██║ ███████║██╔██╗ ██║██║ ███╗ ██║ ██████╔╝███████║██║██╔██╗ ██║
|
|
52
|
-
██║ ██╔══██║██║╚██╗██║██║ ██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║
|
|
53
|
-
███████╗██║ ██║██║ ╚████║╚██████╔╝ ██║ ██║ ██║██║ ██║██║██║ ╚████║
|
|
54
|
-
╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝
|
|
55
|
-
`;
|
|
56
|
-
console.log(gradient(['#00DC82', '#36E4DA', '#0047E1'])(banner)); // Custom Langtrain Green-Cyan-Blue gradient
|
|
57
|
-
intro(`${bgCyan(black(` Langtrain SDK v${version} `))}`);
|
|
58
|
-
|
|
59
|
-
// Check auth (only show login if missing)
|
|
60
|
-
const config = getConfig();
|
|
61
|
-
if (!config.apiKey) {
|
|
62
|
-
intro(yellow('Authentication required'));
|
|
63
|
-
await handleLogin();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Operation Handlers Map (O(1) lookup)
|
|
67
|
-
const handlers: Record<string, (clients?: any) => Promise<void>> = {
|
|
68
|
-
'login': handleLogin,
|
|
69
|
-
'status': handleSubscriptionStatus,
|
|
70
|
-
'tune-finetune': (c) => handleTuneFinetune(c.tune),
|
|
71
|
-
'tune-generate': (c) => handleTuneGenerate(c.tune),
|
|
72
|
-
'vision-finetune': (c) => handleVisionFinetune(c.vision),
|
|
73
|
-
'vision-generate': (c) => handleVisionGenerate(c.vision),
|
|
74
|
-
'agent-list': (c) => handleAgentList(c.agent),
|
|
75
|
-
'agent-create': (c) => handleAgentCreate(c.agent),
|
|
76
|
-
'agent-delete': (c) => handleAgentDelete(c.agent),
|
|
77
|
-
'exit': async () => { outro('Goodbye!'); process.exit(0); },
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
while (true) {
|
|
81
|
-
const operation = await select({
|
|
82
|
-
message: 'Select an operation:',
|
|
83
|
-
options: [
|
|
84
|
-
{ value: 'group-agents', label: '🤖 Agents (Server)', hint: 'Chat with custom agents' },
|
|
85
|
-
{ value: 'agent-list', label: ' ↳ List & Run Agents' },
|
|
86
|
-
{ value: 'agent-create', label: ' ↳ Create New Agent' },
|
|
87
|
-
{ value: 'agent-delete', label: ' ↳ Delete Agent' },
|
|
88
|
-
|
|
89
|
-
{ value: 'group-tune', label: '🧠 Langtune (LLM)', hint: 'Fine-tuning & Text Generation' },
|
|
90
|
-
{ value: 'tune-finetune', label: ' ↳ Fine-tune Text Model' },
|
|
91
|
-
{ value: 'tune-generate', label: ' ↳ Generate Text' },
|
|
92
|
-
|
|
93
|
-
{ value: 'group-vision', label: '👁️ Langvision (Vision)', hint: 'Vision Analysis & Tuning' },
|
|
94
|
-
{ value: 'vision-finetune', label: ' ↳ Fine-tune Vision Model' },
|
|
95
|
-
{ value: 'vision-generate', label: ' ↳ Generate Vision Response' },
|
|
96
|
-
|
|
97
|
-
{ value: 'group-settings', label: '⚙️ Settings' },
|
|
98
|
-
{ value: 'login', label: ' ↳ Update API Key' },
|
|
99
|
-
{ value: 'exit', label: ' ↳ Exit' }
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (isCancel(operation)) {
|
|
104
|
-
outro('Goodbye!');
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (typeof operation === 'string') {
|
|
109
|
-
if (operation.startsWith('group-')) continue;
|
|
110
|
-
|
|
111
|
-
// Execute handler via map lookup
|
|
112
|
-
const handler = handlers[operation];
|
|
113
|
-
if (handler) {
|
|
114
|
-
try {
|
|
115
|
-
// Re-read config & re-init clients freshly for each operation
|
|
116
|
-
const currentConfig = getConfig();
|
|
117
|
-
const clients = {
|
|
118
|
-
vision: new Langvision({ apiKey: currentConfig.apiKey }),
|
|
119
|
-
tune: new Langtune({ apiKey: currentConfig.apiKey }),
|
|
120
|
-
agent: new AgentClient({ apiKey: currentConfig.apiKey, baseUrl: currentConfig.baseUrl })
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
await handler(clients);
|
|
124
|
-
} catch (error: any) {
|
|
125
|
-
outro(red(`Error: ${error.message}`));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
program.parse(process.argv);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function handleLogin() {
|
|
136
|
-
const apiKey = await password({
|
|
137
|
-
message: 'Enter your new Langtrain API Key:',
|
|
138
|
-
validate(value) {
|
|
139
|
-
if (!value || value.length === 0) return 'API Key is required';
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (isCancel(apiKey)) cancel('Operation cancelled');
|
|
144
|
-
|
|
145
|
-
const config = getConfig();
|
|
146
|
-
saveConfig({ ...config, apiKey });
|
|
147
|
-
intro(green('API Key updated successfully!'));
|
|
148
|
-
}
|
|
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
|
-
|
|
177
|
-
async function handleAgentCreate(client: AgentClient) {
|
|
178
|
-
const name = await text({
|
|
179
|
-
message: 'Agent Name:',
|
|
180
|
-
placeholder: 'e.g. Support Bot',
|
|
181
|
-
validate(value) {
|
|
182
|
-
if (!value || value.length === 0) return 'API Key is required';
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
if (isCancel(name)) return;
|
|
186
|
-
|
|
187
|
-
const description = await text({
|
|
188
|
-
message: 'Description:',
|
|
189
|
-
placeholder: 'e.g. A helpful support assistant',
|
|
190
|
-
});
|
|
191
|
-
if (isCancel(description)) return;
|
|
192
|
-
|
|
193
|
-
const systemPrompt = await text({
|
|
194
|
-
message: 'System Prompt:',
|
|
195
|
-
placeholder: 'e.g. You are a helpful assistant.',
|
|
196
|
-
initialValue: 'You are a helpful assistant.'
|
|
197
|
-
});
|
|
198
|
-
if (isCancel(systemPrompt)) return;
|
|
199
|
-
|
|
200
|
-
const s = spinner();
|
|
201
|
-
s.start('Creating agent...');
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
// We need a workspace ID. server usually infers it from API key context if not provided?
|
|
205
|
-
// But the schema says workspace_id is required in AgentCreate.
|
|
206
|
-
// The server implementation of create_agent takes AgentCreate which has workspace_id.
|
|
207
|
-
// However, standard users might not know their exact workspace UUID.
|
|
208
|
-
// We might need to fetch it or rely on server to fill it if we made it optional in schema (which we didn't).
|
|
209
|
-
// EDIT: Let's fetch one agent to get the workspace_id or assume one?
|
|
210
|
-
// Better: List agents, get workspace_id from first one. Hacky but works for single-workspace users.
|
|
211
|
-
// Use 'default' or similar if server supports it?
|
|
212
|
-
// Checking agents.py: verify_api_key returns workspace_id.
|
|
213
|
-
// But create_agent payload requires it.
|
|
214
|
-
// I'll try to fetch list first to get workspace ID. If list empty, we are stuck?
|
|
215
|
-
// Wait, list_agents returns `AgentListResponse` which doesn't explicitly return workspace_id at top level, but agents have it.
|
|
216
|
-
// If no agents, we can't guess it.
|
|
217
|
-
// Maybe I should fetch user profile? No endpoint for that in CLI yet.
|
|
218
|
-
// I'll try to pass a placeholder and hope server ignores it if it uses context?
|
|
219
|
-
// Server code: `workspace_id=agent_in.workspace_id`. It uses payload.
|
|
220
|
-
// I might need to ask user for workspace ID or update server to be smarter.
|
|
221
|
-
// For now, I'll attempt to LIST agents to get a workspace ID.
|
|
222
|
-
|
|
223
|
-
const agents = await client.list();
|
|
224
|
-
let workspaceId = "";
|
|
225
|
-
if (agents.length > 0) {
|
|
226
|
-
workspaceId = agents[0].workspace_id;
|
|
227
|
-
} else {
|
|
228
|
-
// Fallback: Ask user or fail?
|
|
229
|
-
// Or maybe decoding JWT/API key client side? No.
|
|
230
|
-
// I'll prompt for it if not found, with a hint.
|
|
231
|
-
s.stop(yellow('Workspace ID needed (no existing agents found).'));
|
|
232
|
-
const wid = await text({
|
|
233
|
-
message: 'Enter Workspace ID (UUID):',
|
|
234
|
-
validate(value) {
|
|
235
|
-
if (!value || value.length === 0) return 'Required';
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
if (isCancel(wid)) return;
|
|
239
|
-
workspaceId = wid as string;
|
|
240
|
-
s.start('Creating agent...');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const agent = await client.create({
|
|
244
|
-
workspace_id: workspaceId,
|
|
245
|
-
name: name as string,
|
|
246
|
-
description: description as string,
|
|
247
|
-
config: {
|
|
248
|
-
system_prompt: systemPrompt as string,
|
|
249
|
-
model: 'gpt-4o' // Default or prompt? simplified
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
s.stop(green(`Agent "${agent.name}" created successfully! ID: ${agent.id}`));
|
|
253
|
-
} catch (e: any) {
|
|
254
|
-
s.stop(red('Failed to create agent.'));
|
|
255
|
-
throw e;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function handleAgentDelete(client: AgentClient) {
|
|
260
|
-
const s = spinner();
|
|
261
|
-
s.start('Fetching agents...');
|
|
262
|
-
const agents = await client.list();
|
|
263
|
-
s.stop(`Found ${agents.length} agents`);
|
|
264
|
-
|
|
265
|
-
if (agents.length === 0) {
|
|
266
|
-
intro(yellow('No agents to delete.'));
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const agentId = await select({
|
|
271
|
-
message: 'Select an agent to DELETE:',
|
|
272
|
-
options: agents.map(a => ({ value: a.id, label: a.name, hint: a.description || 'No description' }))
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
if (isCancel(agentId)) return;
|
|
276
|
-
|
|
277
|
-
const confirm = await select({
|
|
278
|
-
message: `Are you sure you want to delete this agent?`,
|
|
279
|
-
options: [
|
|
280
|
-
{ value: 'yes', label: 'Yes, delete it', hint: 'Cannot be undone' },
|
|
281
|
-
{ value: 'no', label: 'No, keep it' }
|
|
282
|
-
]
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
if (confirm !== 'yes') {
|
|
286
|
-
intro(gray('Deletion cancelled.'));
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const d = spinner();
|
|
291
|
-
d.start('Deleting agent...');
|
|
292
|
-
try {
|
|
293
|
-
await client.delete(agentId as string);
|
|
294
|
-
d.stop(green('Agent deleted successfully.'));
|
|
295
|
-
} catch (e: any) {
|
|
296
|
-
d.stop(red('Failed to delete agent.'));
|
|
297
|
-
throw e;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function handleAgentList(client: AgentClient) {
|
|
302
|
-
const s = spinner();
|
|
303
|
-
s.start('Fetching agents...');
|
|
304
|
-
const agents = await client.list();
|
|
305
|
-
s.stop(`Found ${agents.length} agents`);
|
|
306
|
-
|
|
307
|
-
if (agents.length === 0) {
|
|
308
|
-
intro(yellow('No agents found in your workspace.'));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const agentId = await select({
|
|
313
|
-
message: 'Select an agent to run:',
|
|
314
|
-
options: agents.map(a => ({ value: a.id, label: a.name, hint: a.description || 'No description' }))
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
if (isCancel(agentId)) return;
|
|
318
|
-
|
|
319
|
-
await handleAgentRun(client, agentId as string, agents.find(a => a.id === agentId)?.name || 'Agent');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function handleAgentRun(client: AgentClient, agentId: string, agentName: string) {
|
|
323
|
-
intro(bgCyan(black(` Chatting with ${agentName} `)));
|
|
324
|
-
console.log(gray('Type "exit" to quit conversation.'));
|
|
325
|
-
|
|
326
|
-
let conversationId: string | undefined = undefined;
|
|
327
|
-
|
|
328
|
-
while (true) {
|
|
329
|
-
const input = await text({
|
|
330
|
-
message: 'You:',
|
|
331
|
-
placeholder: 'Type a message...',
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
if (isCancel(input) || input === 'exit') {
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const s = spinner();
|
|
339
|
-
s.start('Agent is thinking...');
|
|
340
|
-
try {
|
|
341
|
-
const result = await client.execute(agentId, { prompt: input }, [], conversationId);
|
|
342
|
-
s.stop();
|
|
343
|
-
|
|
344
|
-
if (result.output && result.output.response) {
|
|
345
|
-
console.log(gradient.pastel(`Agent: ${result.output.response}`));
|
|
346
|
-
} else {
|
|
347
|
-
console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
conversationId = result.conversation_id;
|
|
351
|
-
} catch (e: any) {
|
|
352
|
-
s.stop(red('Error running agent.'));
|
|
353
|
-
console.error(e);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
// Handler for Langtune Fine-tuning
|
|
360
|
-
async function handleTuneFinetune(tune: Langtune) {
|
|
361
|
-
const model = await text({
|
|
362
|
-
message: 'Enter base model (e.g., gpt-3.5-turbo):',
|
|
363
|
-
placeholder: 'gpt-3.5-turbo',
|
|
364
|
-
validate(value) {
|
|
365
|
-
if (!value || value.length === 0) return 'Value is required!';
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
if (isCancel(model)) cancel('Operation cancelled.');
|
|
369
|
-
|
|
370
|
-
const trainFile = await text({
|
|
371
|
-
message: 'Enter path to training file:',
|
|
372
|
-
placeholder: './data.jsonl',
|
|
373
|
-
validate(value) {
|
|
374
|
-
if (!value || value.length === 0) return 'Value is required!';
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
if (isCancel(trainFile)) cancel('Operation cancelled.');
|
|
378
|
-
|
|
379
|
-
const epochs = await text({
|
|
380
|
-
message: 'Num Epochs:',
|
|
381
|
-
placeholder: '3',
|
|
382
|
-
initialValue: '3'
|
|
383
|
-
});
|
|
384
|
-
if (isCancel(epochs)) cancel('Operation cancelled.');
|
|
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
|
-
|
|
438
|
-
const s = spinner();
|
|
439
|
-
s.start('Starting local fine-tuning...');
|
|
440
|
-
|
|
441
|
-
try {
|
|
442
|
-
// Check if FinetuneConfig types match what's needed.
|
|
443
|
-
// Casting to any to bypass strict type checking for this demo or ensure types are imported correctly.
|
|
444
|
-
// In a real scenario, we'd construct the full config object.
|
|
445
|
-
const config: any = {
|
|
446
|
-
model: model as string,
|
|
447
|
-
trainFile: trainFile as string,
|
|
448
|
-
preset: 'default', // simplified
|
|
449
|
-
epochs: parseInt(epochs as string),
|
|
450
|
-
batchSize: 1,
|
|
451
|
-
learningRate: 2e-5,
|
|
452
|
-
loraRank: 16,
|
|
453
|
-
outputDir: './output'
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
await tune.finetune(config);
|
|
457
|
-
s.stop(green('Fine-tuning job started successfully! 🚀'));
|
|
458
|
-
} catch (e: any) {
|
|
459
|
-
s.stop(red('Failed to start job.'));
|
|
460
|
-
throw e;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Handler for Langtune Generation
|
|
465
|
-
async function handleTuneGenerate(tune: Langtune) {
|
|
466
|
-
const model = await text({
|
|
467
|
-
message: 'Enter model path:',
|
|
468
|
-
placeholder: './output/model',
|
|
469
|
-
initialValue: './output/model'
|
|
470
|
-
});
|
|
471
|
-
if (isCancel(model)) cancel('Operation cancelled');
|
|
472
|
-
|
|
473
|
-
const prompt = await text({
|
|
474
|
-
message: 'Enter prompt:',
|
|
475
|
-
placeholder: 'Hello world',
|
|
476
|
-
});
|
|
477
|
-
if (isCancel(prompt)) cancel('Operation cancelled');
|
|
478
|
-
|
|
479
|
-
const s = spinner();
|
|
480
|
-
s.start('Connecting to Langtrain Inference API...');
|
|
481
|
-
|
|
482
|
-
try {
|
|
483
|
-
const response = await tune.generate(model as string, { prompt: prompt as string });
|
|
484
|
-
s.stop('Generation complete');
|
|
485
|
-
intro('Response:');
|
|
486
|
-
console.log(gradient.pastel(response));
|
|
487
|
-
} catch (e: any) {
|
|
488
|
-
s.stop(red('Generation failed.'));
|
|
489
|
-
throw e;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Handler for Langvision Fine-tuning
|
|
494
|
-
async function handleVisionFinetune(vision: Langvision) {
|
|
495
|
-
const model = await text({
|
|
496
|
-
message: 'Enter base vision model:',
|
|
497
|
-
placeholder: 'llava-v1.5-7b',
|
|
498
|
-
initialValue: 'llava-v1.5-7b'
|
|
499
|
-
});
|
|
500
|
-
if (isCancel(model)) cancel('Operation cancelled');
|
|
501
|
-
|
|
502
|
-
const dataset = await text({
|
|
503
|
-
message: 'Enter dataset path:',
|
|
504
|
-
placeholder: './dataset',
|
|
505
|
-
});
|
|
506
|
-
if (isCancel(dataset)) cancel('Operation cancelled');
|
|
507
|
-
|
|
508
|
-
const epochs = await text({
|
|
509
|
-
message: 'Num Epochs:',
|
|
510
|
-
placeholder: '3',
|
|
511
|
-
initialValue: '3'
|
|
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
|
-
}
|
|
567
|
-
|
|
568
|
-
const s = spinner();
|
|
569
|
-
s.start('Analyzing dataset structure...');
|
|
570
|
-
await new Promise(r => setTimeout(r, 800));
|
|
571
|
-
s.message('Starting vision fine-tuning on Langtrain Cloud...');
|
|
572
|
-
|
|
573
|
-
try {
|
|
574
|
-
const config: any = {
|
|
575
|
-
model: model as string,
|
|
576
|
-
dataset: dataset as string,
|
|
577
|
-
epochs: parseInt(epochs as string),
|
|
578
|
-
batchSize: 1,
|
|
579
|
-
learningRate: 2e-5,
|
|
580
|
-
loraRank: 16,
|
|
581
|
-
outputDir: './vision-output'
|
|
582
|
-
};
|
|
583
|
-
await vision.finetune(config);
|
|
584
|
-
s.stop(green('Vision fine-tuning started successfully! 👁️'));
|
|
585
|
-
} catch (e: any) {
|
|
586
|
-
s.stop(red('Failed to start vision job.'));
|
|
587
|
-
throw e;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Handler for Langvision Generation
|
|
592
|
-
async function handleVisionGenerate(vision: Langvision) {
|
|
593
|
-
const model = await text({
|
|
594
|
-
message: 'Enter model path:',
|
|
595
|
-
placeholder: './vision-output/model',
|
|
596
|
-
initialValue: './vision-output/model'
|
|
597
|
-
});
|
|
598
|
-
if (isCancel(model)) cancel('Operation cancelled');
|
|
599
|
-
|
|
600
|
-
const prompt = await text({
|
|
601
|
-
message: 'Enter prompt/image path:', // Simplified for CLI
|
|
602
|
-
placeholder: 'Describe this image...',
|
|
603
|
-
});
|
|
604
|
-
if (isCancel(prompt)) cancel('Operation cancelled');
|
|
605
|
-
|
|
606
|
-
const s = spinner();
|
|
607
|
-
s.start('Uploading image and context...');
|
|
608
|
-
await new Promise(r => setTimeout(r, 600));
|
|
609
|
-
s.message('Generating vision response...');
|
|
610
|
-
|
|
611
|
-
try {
|
|
612
|
-
const response = await vision.generate(model as string, { prompt: prompt as string });
|
|
613
|
-
s.stop('Generation complete');
|
|
614
|
-
intro('Response:');
|
|
615
|
-
console.log(gradient.pastel(response));
|
|
616
|
-
} catch (e: any) {
|
|
617
|
-
s.stop(red('Generation failed.'));
|
|
618
|
-
throw e;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
main().catch(console.error);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|