lyceum-cli 1.0.28__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.
- lyceum/external/compute/execution/gpu_selection.py +1023 -0
- lyceum/external/compute/inference/batch.py +94 -304
- lyceum/external/compute/inference/chat.py +104 -189
- lyceum/external/compute/inference/infer.py +101 -0
- lyceum/external/compute/inference/models.py +26 -199
- lyceum/main.py +6 -1
- lyceum/shared/config.py +5 -9
- lyceum/shared/streaming.py +45 -17
- {lyceum_cli-1.0.28.dist-info → lyceum_cli-1.0.29.dist-info}/METADATA +1 -1
- {lyceum_cli-1.0.28.dist-info → lyceum_cli-1.0.29.dist-info}/RECORD +13 -11
- {lyceum_cli-1.0.28.dist-info → lyceum_cli-1.0.29.dist-info}/WHEEL +1 -1
- {lyceum_cli-1.0.28.dist-info → lyceum_cli-1.0.29.dist-info}/entry_points.txt +0 -0
- {lyceum_cli-1.0.28.dist-info → lyceum_cli-1.0.29.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
f"{config.base_url}/api/v2/external/
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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]")
|