langtrain 0.1.5 → 0.1.9

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/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "langtrain",
3
- "version": "0.1.5",
3
+ "version": "0.1.9",
4
4
  "description": "Unified JavaScript SDK for Langtrain Ecosystem",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "langtrain": "./dist/cli.js"
10
+ },
8
11
  "scripts": {
9
12
  "build": "tsup",
10
13
  "dev": "tsup --watch",
@@ -26,9 +29,18 @@
26
29
  "author": "Langtrain AI",
27
30
  "license": "MIT",
28
31
  "devDependencies": {
29
- "langvision": "file:../langvision/js",
32
+ "@types/node": "^25.2.3",
30
33
  "langtune": "file:../langtune/js",
34
+ "langvision": "file:../langvision/js",
31
35
  "tsup": "^8.0.2",
32
36
  "typescript": "^5.4.2"
37
+ },
38
+ "dependencies": {
39
+ "@clack/prompts": "^1.0.1",
40
+ "axios": "^1.13.5",
41
+ "commander": "^14.0.3",
42
+ "conf": "^15.1.0",
43
+ "gradient-string": "^3.0.0",
44
+ "kleur": "^4.1.5"
33
45
  }
34
46
  }
package/src/agent.ts ADDED
@@ -0,0 +1,54 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+
3
+ export interface Agent {
4
+ id: string;
5
+ workspace_id: string;
6
+ name: string;
7
+ description?: string;
8
+ model_id?: string;
9
+ config: any;
10
+ is_active: boolean;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+
15
+ export interface AgentRun {
16
+ id: string;
17
+ conversation_id: string;
18
+ success: boolean;
19
+ output?: any;
20
+ error?: string;
21
+ latency_ms: number;
22
+ tokens_used: number;
23
+ }
24
+
25
+ export class AgentClient {
26
+ private client: AxiosInstance;
27
+
28
+ constructor(private config: { apiKey: string, baseUrl?: string }) {
29
+ this.client = axios.create({
30
+ baseURL: config.baseUrl || 'https://api.langtrain.ai/api/v1',
31
+ headers: {
32
+ 'X-API-Key': config.apiKey,
33
+ 'Content-Type': 'application/json'
34
+ }
35
+ });
36
+ }
37
+
38
+ async list(workspaceId?: string): Promise<Agent[]> {
39
+ const params: any = {};
40
+ if (workspaceId) params.workspace_id = workspaceId;
41
+
42
+ const response = await this.client.get<{ agents: Agent[] }>('/agents', { params });
43
+ return response.data.agents;
44
+ }
45
+
46
+ async execute(agentId: string, input: any, messages: any[] = [], conversationId?: string): Promise<AgentRun> {
47
+ const response = await this.client.post<AgentRun>(`/agents/${agentId}/execute`, {
48
+ input,
49
+ messages,
50
+ conversation_id: conversationId
51
+ });
52
+ return response.data;
53
+ }
54
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+ import { intro, outro, select, text, spinner, isCancel, cancel, password } from '@clack/prompts';
3
+ import { bgCyan, black, red, green, yellow, gray } from 'kleur/colors';
4
+ import { Command } from 'commander';
5
+ import { Langvision, Langtune, AgentClient, Agent } from './index';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ import gradient from 'gradient-string';
10
+
11
+ // Configuration
12
+ const CONFIG_DIR = path.join(os.homedir(), '.langtrain');
13
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
14
+
15
+ function getConfig() {
16
+ if (!fs.existsSync(CONFIG_FILE)) return {};
17
+ try {
18
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function saveConfig(config: any) {
25
+ if (!fs.existsSync(CONFIG_DIR)) {
26
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
27
+ }
28
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
29
+ }
30
+
31
+ // Initialize clients with config
32
+ // Clients are initialized inside main loop to support re-login
33
+
34
+ async function main() {
35
+ const program = new Command();
36
+
37
+ program
38
+ .name('langtrain')
39
+ .description('Langtrain CLI for AI Model Fine-tuning and Generation')
40
+ .version('0.1.9');
41
+
42
+ program.action(async () => {
43
+ console.clear();
44
+
45
+ // Gradient Banner
46
+ const banner = `
47
+ ██╗ █████╗ ███╗ ██╗ ██████╗████████╗██████╗ █████╗ ██╗███╗ ██╗
48
+ ██║ ██╔══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║
49
+ ██║ ███████║██╔██╗ ██║██║ ███╗ ██║ ██████╔╝███████║██║██╔██╗ ██║
50
+ ██║ ██╔══██║██║╚██╗██║██║ ██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║
51
+ ███████╗██║ ██║██║ ╚████║╚██████╔╝ ██║ ██║ ██║██║ ██║██║██║ ╚████║
52
+ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝
53
+ `;
54
+ console.log(gradient(['#00DC82', '#36E4DA', '#0047E1'])(banner)); // Custom Langtrain Green-Cyan-Blue gradient
55
+ intro(`${bgCyan(black(' Langtrain SDK v0.1.9 '))}`);
56
+
57
+ // Check auth
58
+ const config = getConfig();
59
+ if (!config.apiKey) {
60
+ intro(yellow('Authentication required'));
61
+ const apiKey = await password({
62
+ message: 'Enter your Langtrain API Key:',
63
+ validate(value) {
64
+ if (!value || value.length === 0) return 'API Key is required';
65
+ },
66
+ });
67
+
68
+ if (isCancel(apiKey)) {
69
+ cancel('Operation cancelled');
70
+ process.exit(0);
71
+ }
72
+
73
+ saveConfig({ ...config, apiKey });
74
+ intro(green('Successfully logged in!'));
75
+ }
76
+
77
+ while (true) {
78
+ const operation = await select({
79
+ message: 'Select an operation:',
80
+ options: [
81
+ { value: 'group-agents', label: '🤖 Agents (Server)', hint: 'Chat with custom agents' },
82
+ { value: 'agent-list', label: ' ↳ List & Run Agents' },
83
+
84
+ { value: 'group-tune', label: '🧠 Langtune (LLM)', hint: 'Fine-tuning & Text Generation' },
85
+ { value: 'tune-finetune', label: ' ↳ Fine-tune Text Model' },
86
+ { value: 'tune-generate', label: ' ↳ Generate Text' },
87
+
88
+ { value: 'group-vision', label: '👁️ Langvision (Vision)', hint: 'Vision Analysis & Tuning' },
89
+ { value: 'vision-finetune', label: ' ↳ Fine-tune Vision Model' },
90
+ { value: 'vision-generate', label: ' ↳ Generate Vision Response' },
91
+
92
+ { value: 'group-settings', label: '⚙️ Settings' },
93
+ { value: 'login', label: ' ↳ Update API Key' },
94
+ { value: 'exit', label: ' ↳ Exit' }
95
+ ],
96
+ });
97
+
98
+ if (isCancel(operation) || operation === 'exit') {
99
+ outro('Goodbye!');
100
+ process.exit(0);
101
+ }
102
+
103
+ if (typeof operation === 'string' && operation.startsWith('group-')) {
104
+ // Header selected, just loop
105
+ continue;
106
+ }
107
+
108
+ try {
109
+ // Re-read config & re-init clients
110
+ const currentConfig = getConfig();
111
+ const currentVision = new Langvision({ apiKey: currentConfig.apiKey });
112
+ const currentTune = new Langtune({ apiKey: currentConfig.apiKey });
113
+ const currentAgent = new AgentClient({ apiKey: currentConfig.apiKey, baseUrl: currentConfig.baseUrl });
114
+
115
+ if (operation === 'login') {
116
+ await handleLogin();
117
+ } else if (operation === 'tune-finetune') {
118
+ await handleTuneFinetune(currentTune);
119
+ } else if (operation === 'tune-generate') {
120
+ await handleTuneGenerate(currentTune);
121
+ } else if (operation === 'vision-finetune') {
122
+ await handleVisionFinetune(currentVision);
123
+ } else if (operation === 'vision-generate') {
124
+ await handleVisionGenerate(currentVision);
125
+ } else if (operation === 'agent-list') {
126
+ await handleAgentList(currentAgent);
127
+ }
128
+ } catch (error: any) {
129
+ outro(red(`Error: ${error.message}`));
130
+ // Don't exit, just loop back to menu
131
+ }
132
+ }
133
+ });
134
+
135
+ program.parse(process.argv);
136
+ }
137
+
138
+ async function handleLogin() {
139
+ const apiKey = await password({
140
+ message: 'Enter your new Langtrain API Key:',
141
+ validate(value) {
142
+ if (!value || value.length === 0) return 'API Key is required';
143
+ },
144
+ });
145
+
146
+ if (isCancel(apiKey)) cancel('Operation cancelled');
147
+
148
+ const config = getConfig();
149
+ saveConfig({ ...config, apiKey });
150
+ intro(green('API Key updated successfully!'));
151
+ }
152
+
153
+ async function handleAgentList(client: AgentClient) {
154
+ const s = spinner();
155
+ s.start('Fetching agents...');
156
+ const agents = await client.list();
157
+ s.stop(`Found ${agents.length} agents`);
158
+
159
+ if (agents.length === 0) {
160
+ intro(yellow('No agents found in your workspace.'));
161
+ return;
162
+ }
163
+
164
+ const agentId = await select({
165
+ message: 'Select an agent to run:',
166
+ options: agents.map(a => ({ value: a.id, label: a.name, hint: a.description || 'No description' }))
167
+ });
168
+
169
+ if (isCancel(agentId)) return;
170
+
171
+ await handleAgentRun(client, agentId as string, agents.find(a => a.id === agentId)?.name || 'Agent');
172
+ }
173
+
174
+ async function handleAgentRun(client: AgentClient, agentId: string, agentName: string) {
175
+ intro(bgCyan(black(` Chatting with ${agentName} `)));
176
+ console.log(gray('Type "exit" to quit conversation.'));
177
+
178
+ let conversationId: string | undefined = undefined;
179
+
180
+ while (true) {
181
+ const input = await text({
182
+ message: 'You:',
183
+ placeholder: 'Type a message...',
184
+ });
185
+
186
+ if (isCancel(input) || input === 'exit') {
187
+ break;
188
+ }
189
+
190
+ const s = spinner();
191
+ s.start('Agent is thinking...');
192
+ try {
193
+ const result = await client.execute(agentId, { prompt: input }, [], conversationId);
194
+ s.stop();
195
+
196
+ if (result.output && result.output.response) {
197
+ console.log(gradient.pastel(`Agent: ${result.output.response}`));
198
+ } else {
199
+ console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
200
+ }
201
+
202
+ conversationId = result.conversation_id;
203
+ } catch (e: any) {
204
+ s.stop(red('Error running agent.'));
205
+ console.error(e);
206
+ }
207
+ }
208
+ }
209
+
210
+
211
+ // Handler for Langtune Fine-tuning
212
+ async function handleTuneFinetune(tune: Langtune) {
213
+ const model = await text({
214
+ message: 'Enter base model (e.g., gpt-3.5-turbo):',
215
+ placeholder: 'gpt-3.5-turbo',
216
+ validate(value) {
217
+ if (!value || value.length === 0) return 'Value is required!';
218
+ },
219
+ });
220
+ if (isCancel(model)) cancel('Operation cancelled.');
221
+
222
+ const trainFile = await text({
223
+ message: 'Enter path to training file:',
224
+ placeholder: './data.jsonl',
225
+ validate(value) {
226
+ if (!value || value.length === 0) return 'Value is required!';
227
+ },
228
+ });
229
+ if (isCancel(trainFile)) cancel('Operation cancelled.');
230
+
231
+ const epochs = await text({
232
+ message: 'Num Epochs:',
233
+ placeholder: '3',
234
+ initialValue: '3'
235
+ });
236
+ if (isCancel(epochs)) cancel('Operation cancelled.');
237
+
238
+ const s = spinner();
239
+ s.start('Connecting to Langtrain Cloud...');
240
+ await new Promise(r => setTimeout(r, 800)); // Simulatoin
241
+ s.message('Starting fine-tuning job...');
242
+
243
+ try {
244
+ // Check if FinetuneConfig types match what's needed.
245
+ // Casting to any to bypass strict type checking for this demo or ensure types are imported correctly.
246
+ // In a real scenario, we'd construct the full config object.
247
+ const config: any = {
248
+ model: model as string,
249
+ trainFile: trainFile as string,
250
+ preset: 'default', // simplified
251
+ epochs: parseInt(epochs as string),
252
+ batchSize: 1,
253
+ learningRate: 2e-5,
254
+ loraRank: 16,
255
+ outputDir: './output'
256
+ };
257
+
258
+ await tune.finetune(config);
259
+ s.stop(green('Fine-tuning job started successfully! 🚀'));
260
+ } catch (e: any) {
261
+ s.stop(red('Failed to start job.'));
262
+ throw e;
263
+ }
264
+ }
265
+
266
+ // Handler for Langtune Generation
267
+ async function handleTuneGenerate(tune: Langtune) {
268
+ const model = await text({
269
+ message: 'Enter model path:',
270
+ placeholder: './output/model',
271
+ initialValue: './output/model'
272
+ });
273
+ if (isCancel(model)) cancel('Operation cancelled');
274
+
275
+ const prompt = await text({
276
+ message: 'Enter prompt:',
277
+ placeholder: 'Hello world',
278
+ });
279
+ if (isCancel(prompt)) cancel('Operation cancelled');
280
+
281
+ const s = spinner();
282
+ s.start('Connecting to Langtrain Inference API...');
283
+
284
+ try {
285
+ const response = await tune.generate(model as string, { prompt: prompt as string });
286
+ s.stop('Generation complete');
287
+ intro('Response:');
288
+ console.log(gradient.pastel(response));
289
+ } catch (e: any) {
290
+ s.stop(red('Generation failed.'));
291
+ throw e;
292
+ }
293
+ }
294
+
295
+ // Handler for Langvision Fine-tuning
296
+ async function handleVisionFinetune(vision: Langvision) {
297
+ const model = await text({
298
+ message: 'Enter base vision model:',
299
+ placeholder: 'llava-v1.5-7b',
300
+ initialValue: 'llava-v1.5-7b'
301
+ });
302
+ if (isCancel(model)) cancel('Operation cancelled');
303
+
304
+ const dataset = await text({
305
+ message: 'Enter dataset path:',
306
+ placeholder: './dataset',
307
+ });
308
+ if (isCancel(dataset)) cancel('Operation cancelled');
309
+
310
+ const epochs = await text({
311
+ message: 'Num Epochs:',
312
+ placeholder: '3',
313
+ initialValue: '3'
314
+ });
315
+
316
+ const s = spinner();
317
+ s.start('Analyzing dataset structure...');
318
+ await new Promise(r => setTimeout(r, 800));
319
+ s.message('Starting vision fine-tuning on Langtrain Cloud...');
320
+
321
+ try {
322
+ const config: any = {
323
+ model: model as string,
324
+ dataset: dataset as string,
325
+ epochs: parseInt(epochs as string),
326
+ batchSize: 1,
327
+ learningRate: 2e-5,
328
+ loraRank: 16,
329
+ outputDir: './vision-output'
330
+ };
331
+ await vision.finetune(config);
332
+ s.stop(green('Vision fine-tuning started successfully! 👁️'));
333
+ } catch (e: any) {
334
+ s.stop(red('Failed to start vision job.'));
335
+ throw e;
336
+ }
337
+ }
338
+
339
+ // Handler for Langvision Generation
340
+ async function handleVisionGenerate(vision: Langvision) {
341
+ const model = await text({
342
+ message: 'Enter model path:',
343
+ placeholder: './vision-output/model',
344
+ initialValue: './vision-output/model'
345
+ });
346
+ if (isCancel(model)) cancel('Operation cancelled');
347
+
348
+ const prompt = await text({
349
+ message: 'Enter prompt/image path:', // Simplified for CLI
350
+ placeholder: 'Describe this image...',
351
+ });
352
+ if (isCancel(prompt)) cancel('Operation cancelled');
353
+
354
+ const s = spinner();
355
+ s.start('Uploading image and context...');
356
+ await new Promise(r => setTimeout(r, 600));
357
+ s.message('Generating vision response...');
358
+
359
+ try {
360
+ const response = await vision.generate(model as string, { prompt: prompt as string });
361
+ s.stop('Generation complete');
362
+ intro('Response:');
363
+ console.log(gradient.pastel(response));
364
+ } catch (e: any) {
365
+ s.stop(red('Generation failed.'));
366
+ throw e;
367
+ }
368
+ }
369
+
370
+ main().catch(console.error);
package/src/index.ts CHANGED
@@ -3,8 +3,12 @@
3
3
  export { Langvision } from 'langvision';
4
4
  export { Langtune } from 'langtune';
5
5
 
6
+ // Export Agent Client
7
+ export { AgentClient, Agent, AgentRun } from './agent';
8
+
6
9
  // Export Types with Namespaces to avoid collisions
7
10
  import * as Vision from 'langvision';
8
11
  import * as Text from 'langtune';
12
+ import * as AgentTypes from './agent';
9
13
 
10
- export { Vision, Text };
14
+ export { Vision, Text, AgentTypes };
package/tsup.config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineConfig } from 'tsup';
2
2
 
3
3
  export default defineConfig({
4
- entry: ['src/index.ts'],
4
+ entry: ['src/index.ts', 'src/cli.ts'],
5
5
  format: ['cjs', 'esm'],
6
6
  dts: true,
7
7
  splitting: false,