lyceum-cli 1.2.2__tar.gz → 1.2.3__tar.gz

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.
Files changed (49) hide show
  1. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/PKG-INFO +1 -1
  2. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/_version.py +1 -1
  3. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/claude_code.py +1 -1
  4. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/infer.py +53 -10
  5. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/PKG-INFO +1 -1
  6. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/pyproject.toml +5 -0
  7. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_dedicated_deploy.py +26 -2
  8. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/README.md +0 -0
  9. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/__main__.py +0 -0
  10. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/__init__.py +0 -0
  11. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/__init__.py +0 -0
  12. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/environment.py +0 -0
  13. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/keys.py +0 -0
  14. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/login.py +0 -0
  15. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/__init__.py +0 -0
  16. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/__init__.py +0 -0
  17. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/config.py +0 -0
  18. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/docker.py +0 -0
  19. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/docker_compose.py +0 -0
  20. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/gpu_selection.py +0 -0
  21. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/jobs.py +0 -0
  22. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/notebook.py +0 -0
  23. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/python.py +0 -0
  24. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/__init__.py +0 -0
  25. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/chat.py +0 -0
  26. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/orgs/__init__.py +0 -0
  27. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/orgs/management.py +0 -0
  28. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/__init__.py +0 -0
  29. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/block.py +0 -0
  30. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/files.py +0 -0
  31. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/vms/__init__.py +0 -0
  32. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/vms/management.py +0 -0
  33. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/main.py +0 -0
  34. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/__init__.py +0 -0
  35. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/config.py +0 -0
  36. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/http.py +0 -0
  37. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/imports.py +0 -0
  38. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/orgs.py +0 -0
  39. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/run_options.py +0 -0
  40. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/streaming.py +0 -0
  41. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/ui.py +0 -0
  42. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/SOURCES.txt +0 -0
  43. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/dependency_links.txt +0 -0
  44. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/entry_points.txt +0 -0
  45. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/requires.txt +0 -0
  46. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/top_level.txt +0 -0
  47. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/setup.cfg +0 -0
  48. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_block_storage.py +0 -0
  49. {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_orgs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lyceum-cli
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: Command-line interface for Lyceum Cloud
5
5
  Author-email: Lyceum <support@lyceum.technology>
6
6
  License: MIT
@@ -7,4 +7,4 @@ __version__ lives in this module. A future refactor could move these modules
7
7
  under lyceum/cli/, claiming one subpackage the way the other two do.
8
8
  """
9
9
 
10
- __version__ = "1.2.2"
10
+ __version__ = "1.2.3"
@@ -19,7 +19,7 @@ console = Console()
19
19
  # or ~/.claude settings on this machine.
20
20
  CLAUDE_PROFILE_DIR = Path.home() / ".lyceum" / "claude"
21
21
 
22
- DEFAULT_MODEL = "moonshotai/kimi-k3"
22
+ DEFAULT_MODEL = "moonshotai/kimi-k3.1-pro"
23
23
  DEFAULT_FAST_MODEL = "z-ai/glm-5.3-flash"
24
24
 
25
25
  # Use explicit Lyceum IDs so the backend never has to guess a substitute.
@@ -74,7 +74,7 @@ def _is_dedicated_inference_hidden() -> bool:
74
74
 
75
75
  def _fetch_dedicated_models() -> list[dict]:
76
76
  """Serverless models a dedicated deployment may run, on their default GPU shape."""
77
- return api_request("GET", "/api/v2/external/inference/models", timeout=20.0).get("models", [])
77
+ return api_request("GET", "/external/inference/models", timeout=20.0).get("models", [])
78
78
 
79
79
 
80
80
  def _format_gpus(gpu_type: str, gpu_count: int) -> str:
@@ -181,14 +181,27 @@ def deploy_model(
181
181
  target_latency: float = typer.Option(None, "--target-latency", hidden=True),
182
182
  stabilisation: int = typer.Option(None, "--stabilisation", hidden=True),
183
183
  gpu_count: int = typer.Option(
184
- None, "--gpu-count", help="GPUs per replica, defaults per model (also sets --tensor-parallel-size)"
184
+ None,
185
+ "--gpu-count",
186
+ help="GPUs per replica, defaults per model (also sets --tensor-parallel-size)",
187
+ ),
188
+ offload: bool = typer.Option(
189
+ None,
190
+ "--offload/--no-offload",
191
+ help=(
192
+ "Serve requests the deployment cannot from Lyceum's serverless capacity (default off). That may include a "
193
+ "third-party provider under Lyceum's data terms unless your org is restricted to Lyceum's own"
194
+ ),
185
195
  ),
186
196
  yes: bool = typer.Option(False, "--yes", "-y", help="Do not ask before creating"),
187
197
  wait: bool = typer.Option(False, "--wait", "-w", help="Wait until deployment has healthy replicas"),
188
198
  ):
189
199
  """Deploy a serverless model on dedicated GPUs."""
190
200
  models = _fetch_dedicated_models()
191
- model = next((m for m in models if model_id.lower() in (m["serving_name"].lower(), m["hf_model_id"].lower())), None)
201
+ model = next(
202
+ (m for m in models if model_id.lower() in (m["serving_name"].lower(), m["hf_model_id"].lower())),
203
+ None,
204
+ )
192
205
  if model is None:
193
206
  console.print(f"[red]{model_id} cannot be deployed on dedicated GPUs.[/red]")
194
207
  console.print("[dim]See the models that can: lyceum infer models --dedicated[/dim]")
@@ -222,7 +235,10 @@ def deploy_model(
222
235
  raise typer.Exit(0)
223
236
 
224
237
  if hf_token is None and not yes and not (os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")):
225
- if typer.confirm("No HF token detected. Provide one (required for gated models)?", default=False):
238
+ if typer.confirm(
239
+ "No HF token detected. Provide one (required for gated models)?",
240
+ default=False,
241
+ ):
226
242
  hf_token = typer.prompt("HuggingFace Token", hide_input=True)
227
243
 
228
244
  payload = {
@@ -233,6 +249,7 @@ def deploy_model(
233
249
  overrides = {
234
250
  "gpu_count": gpu_count,
235
251
  "gpu_type": gpu_type,
252
+ "offload": offload,
236
253
  "stabilisation_window": stabilisation,
237
254
  "target_latency_p95_ms": target_latency,
238
255
  "target_rps": target_rps,
@@ -241,7 +258,7 @@ def deploy_model(
241
258
  if hf_token:
242
259
  payload["hf_token"] = hf_token
243
260
 
244
- data = api_request("POST", "/api/v2/external/inference/create", json=payload)
261
+ data = api_request("POST", "/external/inference/create", json=payload)
245
262
  deployment_id = data.get("deployment_id", "?")
246
263
 
247
264
  console.print("\n[green]Deployment created![/green]")
@@ -261,6 +278,30 @@ def deploy_model(
261
278
  _wait_for_healthy(deployment_id, timeout=model["ready_timeout_s"])
262
279
 
263
280
 
281
+ def _offload_label(enabled: bool | None) -> str:
282
+ return "enabled" if enabled else "disabled"
283
+
284
+
285
+ @infer_app.command("offload", hidden=_is_dedicated_inference_hidden())
286
+ def set_offload(
287
+ deployment_id: str = typer.Argument(..., help="Deployment ID"),
288
+ enabled: bool = typer.Option(..., "--enable/--disable", help="Turn offloading on or off"),
289
+ ):
290
+ """Turn offloading on or off for a deployment.
291
+
292
+ Enabled, requests the deployment cannot serve go to Lyceum's serverless capacity,
293
+ which may include a third-party provider under Lyceum's data terms unless your
294
+ org is restricted to Lyceum's own.
295
+ """
296
+ data = api_request(
297
+ "POST",
298
+ "/external/inference/offload",
299
+ json={"deployment_id": deployment_id, "offload": enabled},
300
+ )
301
+ console.print(f" Deployment ID: [cyan]{data.get('deployment_id')}[/cyan]")
302
+ console.print(f" Offload: [yellow]{_offload_label(data.get('offload'))}[/yellow]")
303
+
304
+
264
305
  @infer_app.command("stop")
265
306
  def stop_model(
266
307
  deployment_id: str = typer.Argument(..., help="Deployment ID to stop"),
@@ -268,7 +309,7 @@ def stop_model(
268
309
  """Stop a deployed model and all its replicas."""
269
310
  console.print(f"[dim]Stopping deployment {deployment_id[:12]}...[/dim]")
270
311
 
271
- data = api_request("DELETE", "/api/v2/external/inference/stop", json={"deployment_id": deployment_id})
312
+ data = api_request("DELETE", "/external/inference/stop", json={"deployment_id": deployment_id})
272
313
  console.print("[green]Deployment stopped![/green]")
273
314
  console.print(f" Deployment ID: [cyan]{data.get('deployment_id')}[/cyan]")
274
315
  console.print(f" Status: [yellow]{data.get('status')}[/yellow]")
@@ -283,7 +324,7 @@ def deployment_status(
283
324
  """Show detailed status of a deployment."""
284
325
  dep = api_request(
285
326
  "GET",
286
- "/api/v2/external/inference/get",
327
+ "/external/inference/get",
287
328
  params={"deployment_id": deployment_id, "include_terminated": str(include_terminated).lower()},
288
329
  timeout=15.0,
289
330
  )
@@ -307,6 +348,8 @@ def deployment_status(
307
348
  console.print(f" Target p95: {dep['target_latency_p95_ms']} ms")
308
349
  if dep.get("stabilisation_window") is not None:
309
350
  console.print(f" Stabilisation: {dep['stabilisation_window']} s")
351
+ if dep.get("offload") is not None:
352
+ console.print(f" Offload: {_offload_label(dep['offload'])}")
310
353
 
311
354
  replicas = dep.get("replicas", [])
312
355
  if replicas:
@@ -359,7 +402,7 @@ def list_deployments(
359
402
  """List your dedicated deployments."""
360
403
  data = api_request(
361
404
  "GET",
362
- "/api/v2/external/inference/list",
405
+ "/external/inference/list",
363
406
  params={"include_terminated": str(include_terminated).lower()},
364
407
  timeout=15.0,
365
408
  )
@@ -425,7 +468,7 @@ def replica_logs(
425
468
  replica_id: str = typer.Argument(..., help="Replica ID"),
426
469
  ):
427
470
  """Show last container logs for a failed replica."""
428
- data = api_request("GET", f"/api/v2/external/inference/replica/{replica_id}/logs", timeout=15.0)
471
+ data = api_request("GET", f"/external/inference/replica/{replica_id}/logs", timeout=15.0)
429
472
  error_logs = data.get("error_logs")
430
473
  if not error_logs:
431
474
  console.print(f"[yellow]No logs available for replica {replica_id}[/yellow]")
@@ -445,7 +488,7 @@ def _wait_for_healthy(deployment_id: str, timeout: int = 600, poll_interval: int
445
488
  while time.time() < deadline:
446
489
  try:
447
490
  response = httpx.get(
448
- f"{config.base_url}/api/v2/external/inference/get",
491
+ f"{config.base_url}/external/inference/get",
449
492
  headers=headers,
450
493
  params={"deployment_id": deployment_id},
451
494
  timeout=15.0,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lyceum-cli
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: Command-line interface for Lyceum Cloud
5
5
  Author-email: Lyceum <support@lyceum.technology>
6
6
  License: MIT
@@ -40,6 +40,11 @@ Dashboard = "https://dashboard.lyceum.technology"
40
40
  [project.scripts]
41
41
  lyceum = "lyceum.main:app"
42
42
 
43
+ [dependency-groups]
44
+ test = [
45
+ "pytest==9.0.2",
46
+ ]
47
+
43
48
  [tool.setuptools.dynamic]
44
49
  version = { attr = "lyceum._version.__version__" }
45
50
 
@@ -29,11 +29,14 @@ def gateway(monkeypatch):
29
29
  creates = []
30
30
 
31
31
  def api_request(method, path, **kw):
32
- if path == "/api/v2/external/inference/models":
32
+ if path == "/external/inference/models":
33
33
  return {"models": [GLM52, FLASH]}
34
- if path == "/api/v2/external/inference/create":
34
+ if path == "/external/inference/create":
35
35
  creates.append(kw["json"])
36
36
  return {"deployment_id": "d1", "status": "pending"}
37
+ if path == "/external/inference/offload":
38
+ creates.append(kw["json"])
39
+ return kw["json"]
37
40
  raise AssertionError(path)
38
41
 
39
42
  monkeypatch.setattr(infer, "api_request", api_request)
@@ -103,3 +106,24 @@ def test_wait_uses_the_model_ready_timeout(gateway, monkeypatch):
103
106
  monkeypatch.setattr(infer, "_wait_for_healthy", lambda d, timeout: waited.append((d, timeout)))
104
107
  assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--wait").exit_code == 0
105
108
  assert waited == [("d1", 4800)]
109
+
110
+
111
+ def test_deploy_offload_is_sent_only_when_given(gateway):
112
+ assert _run("deploy", "deepseek/deepseek-v4-flash-0731").exit_code == 0
113
+ assert "offload" not in gateway[0]
114
+ assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--offload").exit_code == 0
115
+ assert gateway[1]["offload"] is True
116
+ assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--no-offload").exit_code == 0
117
+ assert gateway[2]["offload"] is False
118
+
119
+
120
+ def test_offload_command_posts_the_switch(gateway):
121
+ result = _run("offload", "d1", "--enable")
122
+ assert result.exit_code == 0
123
+ assert "enabled" in result.output
124
+ assert _run("offload", "d1", "--disable").exit_code == 0
125
+ assert gateway == [
126
+ {"deployment_id": "d1", "offload": True},
127
+ {"deployment_id": "d1", "offload": False},
128
+ ]
129
+ assert _run("offload", "d1").exit_code == 2
File without changes
File without changes
File without changes