lyceum-cli 1.0.27__py3-none-any.whl → 1.0.29__py3-none-any.whl

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.
@@ -1,7 +1,4 @@
1
- """OpenAI-compatible batch processing commands"""
2
-
3
- import os
4
- from pathlib import Path
1
+ """Inference jobs command"""
5
2
 
6
3
  import httpx
7
4
  import typer
@@ -13,309 +10,102 @@ from ....shared.display import format_timestamp, truncate_id
13
10
 
14
11
  console = Console()
15
12
 
16
- batch_app = typer.Typer(name="batch", help="OpenAI-compatible batch processing")
17
-
18
-
19
- @batch_app.command("upload")
20
- def upload_file(
21
- file_path: str = typer.Argument(..., help="Path to JSONL file to upload"),
22
- purpose: str = typer.Option("batch", "--purpose", "-p", help="File purpose (batch, batch_output, batch_errors)"),
13
+ def jobs_cmd(
14
+ stream_job_id: str = typer.Option(None, "--stream", "-s", metavar="JOB_ID", help="Stream status of a job"),
15
+ result_job_id: str = typer.Option(None, "--result", "-r", metavar="JOB_ID", help="Get result of a job"),
16
+ history: bool = typer.Option(False, "--history", "-h", help="List past jobs"),
23
17
  ):
24
- """Upload a JSONL file for batch processing"""
25
- # Check if file exists
26
- if not Path(file_path).exists():
27
- console.print(f"[red]Error: File '{file_path}' not found[/red]")
28
- raise typer.Exit(1)
29
-
30
- # Validate file extension
31
- if not file_path.endswith('.jsonl'):
32
- console.print("[yellow]Warning: File doesn't have .jsonl extension[/yellow]")
33
-
18
+ """
19
+ Retrieve status of inference jobs
20
+ """
34
21
  try:
35
- console.print(f"[dim]📤 Uploading {os.path.basename(file_path)} for {purpose}...[/dim]")
36
-
37
- # Upload file using multipart form data
38
- with open(file_path, 'rb') as f:
39
- files = {'file': (os.path.basename(file_path), f, 'application/jsonl')}
40
- data = {'purpose': purpose}
41
-
42
- response = httpx.post(
43
- f"{config.base_url}/api/v2/external/files",
22
+ config.get_client()
23
+
24
+ # 1. Stream Job
25
+ if stream_job_id:
26
+ console.print(f"[dim]Streaming job {stream_job_id}...[/dim]")
27
+ # TODO: implement real streaming log retrieval if API supports it
28
+ # For now, get batch status
29
+ response = httpx.get(
30
+ f"{config.base_url}/api/v2/external/batches/{stream_job_id}",
44
31
  headers={"Authorization": f"Bearer {config.api_key}"},
45
- files=files,
46
- data=data,
47
- timeout=60.0
48
- )
49
-
50
- if response.status_code != 200:
51
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
52
- console.print(f"[red]{response.text}[/red]")
53
- raise typer.Exit(1)
54
-
55
- data = response.json()
56
-
57
- console.print("[green] File uploaded successfully![/green]")
58
- console.print(f"[cyan]File ID: {data['id']}[/cyan]")
59
- console.print(f"[dim]Size: {data['bytes']} bytes[/dim]")
60
- console.print(f"[dim]Purpose: {data['purpose']}[/dim]")
61
- console.print(f"[dim]Created: {data['created_at']}[/dim]")
62
-
63
- except Exception as e:
64
- console.print(f"[red]Error: {e}[/red]")
65
- raise typer.Exit(1)
66
-
67
-
68
- @batch_app.command("create")
69
- def create_batch(
70
- input_file_id: str = typer.Argument(..., help="File ID of uploaded JSONL file"),
71
- endpoint: str | None = typer.Option(
72
- None, "--endpoint", "-e", help="API endpoint (optional, uses URLs from JSONL if not specified)"
73
- ),
74
- model: str | None = typer.Option(
75
- None, "--model", "-m", help="Model to use for all requests (overrides model in JSONL)"
76
- ),
77
- completion_window: str = typer.Option(
78
- "24h", "--completion-window", "-w", help="Completion window (24h only)"
79
- ),
80
- ):
81
- """Create a batch processing job"""
82
- try:
83
- console.print(f"[dim]🚀 Creating batch job for file {input_file_id}...[/dim]")
84
-
85
- request_data = {
86
- "input_file_id": input_file_id,
87
- "completion_window": completion_window
88
- }
89
-
90
- # Only include endpoint if explicitly provided as override
91
- if endpoint:
92
- request_data["endpoint"] = endpoint
93
- console.print(f"[dim]Using endpoint override: {endpoint}[/dim]")
94
- # Note: endpoint is NOT required - the API should use URLs from JSONL file
95
-
96
- # Include model override if specified
97
- if model:
98
- request_data["model"] = model
99
- console.print(f"[dim]Using model override: {model}[/dim]")
100
-
101
- response = httpx.post(
102
- f"{config.base_url}/api/v2/external/batches",
103
- headers={"Authorization": f"Bearer {config.api_key}"},
104
- json=request_data,
105
- timeout=30.0
106
- )
107
-
108
- if response.status_code != 200:
109
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
110
- console.print(f"[red]{response.text}[/red]")
111
- raise typer.Exit(1)
112
-
113
- data = response.json()
114
-
115
- console.print("[green]✅ Batch job created successfully![/green]")
116
- console.print(f"[cyan]Batch ID: {data['id']}[/cyan]")
117
- console.print(f"[yellow]Status: {data['status']}[/yellow]")
118
- console.print(f"[dim]Endpoint: {data['endpoint']}[/dim]")
119
- console.print(f"[dim]Input File ID: {data['input_file_id']}[/dim]")
120
- console.print(f"[dim]Expires: {data['expires_at']}[/dim]")
121
-
122
- except Exception as e:
123
- console.print(f"[red]Error: {e}[/red]")
124
- raise typer.Exit(1)
125
-
126
-
127
- @batch_app.command("get")
128
- def get_batch(
129
- batch_id: str = typer.Argument(..., help="Batch ID to retrieve"),
130
- ):
131
- """Get batch job status and details"""
132
- try:
133
- console.print(f"[dim]🔍 Retrieving batch {batch_id}...[/dim]")
134
-
135
- response = httpx.get(
136
- f"{config.base_url}/api/v2/external/batches/{batch_id}",
137
- headers={"Authorization": f"Bearer {config.api_key}"},
138
- timeout=30.0
139
- )
140
-
141
- if response.status_code != 200:
142
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
143
- console.print(f"[red]{response.text}[/red]")
144
- raise typer.Exit(1)
145
-
146
- data = response.json()
147
-
148
- # Status color coding
149
- status = data['status']
150
- if status == "completed":
151
- status_color = "green"
152
- elif status in ["failed", "expired", "cancelled"]:
153
- status_color = "red"
154
- elif status in ["in_progress", "finalizing"]:
155
- status_color = "yellow"
156
- else:
157
- status_color = "dim"
158
-
159
- console.print(f"[cyan]Batch ID: {data['id']}[/cyan]")
160
- console.print(f"[{status_color}]Status: {status}[/{status_color}]")
161
- console.print(f"[dim]Endpoint: {data['endpoint']}[/dim]")
162
- console.print(f"[dim]Input File: {data['input_file_id']}[/dim]")
163
-
164
- if data.get('output_file_id'):
165
- console.print(f"[green]Output File: {data['output_file_id']}[/green]")
166
-
167
- if data.get('error_file_id'):
168
- console.print(f"[red]Error File: {data['error_file_id']}[/red]")
169
-
170
- # Request statistics
171
- counts = data.get('request_counts', {})
172
- console.print(
173
- f"[dim]Requests - Total: {counts.get('total', 0)}, "
174
- f"Completed: {counts.get('completed', 0)}, "
175
- f"Failed: {counts.get('failed', 0)}[/dim]"
176
- )
177
-
178
- # Timestamps
179
- console.print(f"[dim]Created: {data.get('created_at', 'N/A')}[/dim]")
180
- if data.get('completed_at'):
181
- console.print(f"[dim]Completed: {data['completed_at']}[/dim]")
182
- if data.get('expires_at'):
183
- console.print(f"[dim]Expires: {data['expires_at']}[/dim]")
184
-
185
- except Exception as e:
186
- console.print(f"[red]Error: {e}[/red]")
187
- raise typer.Exit(1)
188
-
189
-
190
- @batch_app.command("list")
191
- def list_batches(
192
- after: str | None = typer.Option(None, "--after", help="List batches after this batch ID"),
193
- limit: int = typer.Option(20, "--limit", "-l", help="Number of batches to return"),
194
- ):
195
- """List batch jobs"""
196
- try:
197
- console.print("[dim]📋 Listing batch jobs...[/dim]")
198
-
199
- params = {"limit": limit}
200
- if after:
201
- params["after"] = after
202
-
203
- response = httpx.get(
204
- f"{config.base_url}/api/v2/external/batches",
205
- headers={"Authorization": f"Bearer {config.api_key}"},
206
- params=params,
207
- timeout=30.0
208
- )
209
-
210
- if response.status_code != 200:
211
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
212
- console.print(f"[red]{response.text}[/red]")
213
- raise typer.Exit(1)
214
-
215
- data = response.json()
216
- batches = data.get('data', [])
217
-
218
- if not batches:
219
- console.print("[dim]No batch jobs found[/dim]")
220
- return
221
-
222
- table = Table(title="Batch Jobs")
223
- table.add_column("Batch ID", style="cyan", no_wrap=True, max_width=16)
224
- table.add_column("Status", style="yellow")
225
- table.add_column("Endpoint", style="green")
226
- table.add_column("Requests", style="magenta", justify="center")
227
- table.add_column("Created", style="dim")
228
-
229
- for batch in batches:
230
- batch_id = batch['id']
231
- short_id = truncate_id(batch_id, 12)
232
-
233
- counts = batch.get('request_counts', {})
234
- request_stats = f"{counts.get('completed', 0)}/{counts.get('total', 0)}"
235
-
236
- table.add_row(
237
- short_id,
238
- batch['status'],
239
- batch['endpoint'],
240
- request_stats,
241
- format_timestamp(batch.get('created_at'))
242
- )
243
-
244
- console.print(table)
245
- console.print(f"\n[dim]Found {len(batches)} batch jobs[/dim]")
246
-
247
- except Exception as e:
248
- console.print(f"[red]Error: {e}[/red]")
249
- raise typer.Exit(1)
250
-
251
-
252
- @batch_app.command("cancel")
253
- def cancel_batch(
254
- batch_id: str = typer.Argument(..., help="Batch ID to cancel"),
255
- ):
256
- """Cancel a batch job"""
257
- try:
258
- console.print(f"[dim]🛑 Cancelling batch {batch_id}...[/dim]")
259
-
260
- response = httpx.post(
261
- f"{config.base_url}/api/v2/external/batches/{batch_id}/cancel",
262
- headers={"Authorization": f"Bearer {config.api_key}"},
263
- timeout=30.0
264
- )
265
-
266
- if response.status_code != 200:
267
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
268
- console.print(f"[red]{response.text}[/red]")
269
- raise typer.Exit(1)
270
-
271
- data = response.json()
272
-
273
- console.print("[green]✅ Batch cancelled successfully![/green]")
274
- console.print(f"[cyan]Batch ID: {data['id']}[/cyan]")
275
- console.print(f"[yellow]Status: {data['status']}[/yellow]")
276
-
277
- except Exception as e:
278
- console.print(f"[red]Error: {e}[/red]")
279
- raise typer.Exit(1)
280
-
281
-
282
- @batch_app.command("download")
283
- def download_file(
284
- file_id: str = typer.Argument(..., help="File ID to download"),
285
- output_file: str | None = typer.Option(
286
- None, "--output", "-o", help="Output file path (prints to console if not specified)"
287
- ),
288
- ):
289
- """Download batch file content (input, output, or error files)"""
290
- try:
291
- console.print(f"[dim]⬇️ Downloading file {file_id}...[/dim]")
292
-
293
- response = httpx.get(
294
- f"{config.base_url}/api/v2/external/files/{file_id}/content",
295
- headers={"Authorization": f"Bearer {config.api_key}"},
296
- timeout=60.0
297
- )
298
-
299
- if response.status_code != 200:
300
- console.print(f"[red]Error: HTTP {response.status_code}[/red]")
301
- console.print(f"[red]{response.text}[/red]")
302
- raise typer.Exit(1)
303
-
304
- content = response.text
305
-
306
- if output_file:
307
- # Save to file
308
- with open(output_file, 'w') as f:
309
- f.write(content)
310
- console.print(f"[green]✅ Content saved to {output_file}[/green]")
311
- console.print(f"[dim]Size: {len(content)} characters[/dim]")
312
- else:
313
- # Print to console
314
- console.print("[green]📄 File Content:[/green]")
315
- console.print("-" * 50)
316
- console.print(content)
317
- console.print("-" * 50)
318
- console.print(f"[dim]Size: {len(content)} characters[/dim]")
32
+ timeout=30.0
33
+ )
34
+ if response.status_code != 200:
35
+ console.print(f"[red]Error fetching job: {response.text}[/red]")
36
+ raise typer.Exit(1)
37
+
38
+ data = response.json()
39
+ console.print(f"Job Status: [cyan]{data['status']}[/cyan]")
40
+ return
41
+
42
+ # 2. Result Job
43
+ if result_job_id:
44
+ console.print(f"[dim]Getting result for {result_job_id}...[/dim]")
45
+ response = httpx.get(
46
+ f"{config.base_url}/api/v2/external/batches/{result_job_id}",
47
+ headers={"Authorization": f"Bearer {config.api_key}"},
48
+ timeout=30.0
49
+ )
50
+ if response.status_code != 200:
51
+ console.print(f"[red]Error: {response.text}[/red]")
52
+ raise typer.Exit(1)
53
+
54
+ data = response.json()
55
+ if data.get('output_file_id'):
56
+ # Download output
57
+ file_resp = httpx.get(
58
+ f"{config.base_url}/api/v2/external/files/{data['output_file_id']}/content",
59
+ headers={"Authorization": f"Bearer {config.api_key}"},
60
+ timeout=60.0
61
+ )
62
+ if file_resp.status_code == 200:
63
+ console.print("[green]Result:[/green]")
64
+ console.print(file_resp.text)
65
+ else:
66
+ console.print(f"[red]Error downloading result: {file_resp.text}[/red]")
67
+ else:
68
+ console.print(f"[yellow]No output file yet. Status: {data['status']}[/yellow]")
69
+ return
70
+
71
+ # 3. History (List)
72
+ if history:
73
+ console.print("[dim]Fetching job history...[/dim]")
74
+ response = httpx.get(
75
+ f"{config.base_url}/api/v2/external/batches",
76
+ headers={"Authorization": f"Bearer {config.api_key}"},
77
+ params={"limit": 20},
78
+ timeout=30.0
79
+ )
80
+ if response.status_code != 200:
81
+ console.print(f"[red]Error: {response.text}[/red]")
82
+ raise typer.Exit(1)
83
+
84
+ data = response.json()
85
+ batches = data.get('data', [])
86
+
87
+ if not batches:
88
+ console.print("[yellow]No jobs found[/yellow]")
89
+ return
90
+
91
+ table = Table(title="Inference Jobs History")
92
+ table.add_column("Job ID", style="cyan", no_wrap=True, max_width=16)
93
+ table.add_column("Status", style="yellow")
94
+ table.add_column("Model/Endpoint", style="green")
95
+ table.add_column("Created", style="dim")
96
+
97
+ for batch in batches:
98
+ short_id = truncate_id(batch['id'], 12)
99
+ table.add_row(
100
+ short_id,
101
+ batch['status'],
102
+ batch.get('endpoint') or batch.get('model') or 'N/A',
103
+ format_timestamp(batch.get('created_at'))
104
+ )
105
+ console.print(table)
106
+ return
107
+
108
+ console.print("[yellow]Please use --stream, --result, or --history[/yellow]")
319
109
 
320
110
  except Exception as e:
321
111
  console.print(f"[red]Error: {e}[/red]")