together 2.0.0a15__py3-none-any.whl → 2.0.0a16__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.
Files changed (81) hide show
  1. together/_base_client.py +134 -11
  2. together/_client.py +7 -0
  3. together/_models.py +16 -1
  4. together/_types.py +9 -0
  5. together/_version.py +1 -1
  6. together/constants.py +1 -1
  7. together/error.py +1 -2
  8. together/lib/_google_colab.py +39 -0
  9. together/lib/cli/__init__.py +73 -0
  10. together/lib/cli/api/beta/clusters/__init__.py +47 -0
  11. together/lib/cli/api/beta/{clusters.py → clusters/create.py} +5 -179
  12. together/lib/cli/api/beta/clusters/delete.py +35 -0
  13. together/lib/cli/api/beta/clusters/get_credentials.py +151 -0
  14. together/lib/cli/api/beta/clusters/list.py +24 -0
  15. together/lib/cli/api/beta/clusters/list_regions.py +37 -0
  16. together/lib/cli/api/beta/clusters/retrieve.py +31 -0
  17. together/lib/cli/api/beta/clusters/storage/__init__.py +19 -0
  18. together/lib/cli/api/beta/clusters/storage/create.py +49 -0
  19. together/lib/cli/api/beta/clusters/storage/delete.py +38 -0
  20. together/lib/cli/api/beta/clusters/storage/list.py +42 -0
  21. together/lib/cli/api/beta/clusters/storage/retrieve.py +34 -0
  22. together/lib/cli/api/beta/clusters/update.py +54 -0
  23. together/lib/cli/api/endpoints/__init__.py +56 -0
  24. together/lib/cli/api/endpoints/availability_zones.py +26 -0
  25. together/lib/cli/api/endpoints/create.py +159 -0
  26. together/lib/cli/api/endpoints/delete.py +15 -0
  27. together/lib/cli/api/endpoints/hardware.py +40 -0
  28. together/lib/cli/api/endpoints/list.py +66 -0
  29. together/lib/cli/api/endpoints/retrieve.py +23 -0
  30. together/lib/cli/api/endpoints/start.py +25 -0
  31. together/lib/cli/api/endpoints/stop.py +25 -0
  32. together/lib/cli/api/endpoints/update.py +77 -0
  33. together/lib/cli/api/evals/__init__.py +19 -0
  34. together/lib/cli/api/{evals.py → evals/create.py} +6 -129
  35. together/lib/cli/api/evals/list.py +58 -0
  36. together/lib/cli/api/evals/retrieve.py +21 -0
  37. together/lib/cli/api/evals/status.py +20 -0
  38. together/lib/cli/api/files/__init__.py +23 -0
  39. together/lib/cli/api/files/check.py +21 -0
  40. together/lib/cli/api/files/delete.py +20 -0
  41. together/lib/cli/api/files/list.py +34 -0
  42. together/lib/cli/api/files/retrieve.py +20 -0
  43. together/lib/cli/api/files/retrieve_content.py +25 -0
  44. together/lib/cli/api/files/upload.py +38 -0
  45. together/lib/cli/api/fine_tuning/__init__.py +27 -0
  46. together/lib/cli/api/fine_tuning/cancel.py +28 -0
  47. together/lib/cli/api/{fine_tuning.py → fine_tuning/create.py} +5 -257
  48. together/lib/cli/api/fine_tuning/delete.py +29 -0
  49. together/lib/cli/api/fine_tuning/download.py +94 -0
  50. together/lib/cli/api/fine_tuning/list.py +44 -0
  51. together/lib/cli/api/fine_tuning/list_checkpoints.py +42 -0
  52. together/lib/cli/api/fine_tuning/list_events.py +35 -0
  53. together/lib/cli/api/fine_tuning/retrieve.py +27 -0
  54. together/lib/cli/api/models/__init__.py +15 -0
  55. together/lib/cli/api/models/list.py +51 -0
  56. together/lib/cli/api/{models.py → models/upload.py} +4 -51
  57. together/resources/beta/clusters/clusters.py +36 -28
  58. together/resources/beta/clusters/storage.py +30 -21
  59. together/types/__init__.py +4 -3
  60. together/types/beta/__init__.py +0 -2
  61. together/types/beta/cluster_create_params.py +3 -3
  62. together/types/beta/clusters/__init__.py +0 -1
  63. together/types/beta/clusters/cluster_storage.py +4 -0
  64. together/types/chat_completions.py +1 -1
  65. together/types/endpoints.py +1 -1
  66. {together-2.0.0a15.dist-info → together-2.0.0a16.dist-info}/METADATA +4 -2
  67. {together-2.0.0a15.dist-info → together-2.0.0a16.dist-info}/RECORD +72 -36
  68. together-2.0.0a16.dist-info/entry_points.txt +2 -0
  69. together/lib/cli/api/__init__.py +0 -0
  70. together/lib/cli/api/beta/clusters_storage.py +0 -152
  71. together/lib/cli/api/endpoints.py +0 -467
  72. together/lib/cli/api/files.py +0 -133
  73. together/lib/cli/cli.py +0 -73
  74. together/types/beta/cluster_create_response.py +0 -9
  75. together/types/beta/cluster_update_response.py +0 -9
  76. together/types/beta/clusters/storage_create_response.py +0 -9
  77. together-2.0.0a15.dist-info/entry_points.txt +0 -2
  78. /together/lib/cli/api/{utils.py → _utils.py} +0 -0
  79. /together/lib/cli/api/beta/{beta.py → __init__.py} +0 -0
  80. {together-2.0.0a15.dist-info → together-2.0.0a16.dist-info}/WHEEL +0 -0
  81. {together-2.0.0a15.dist-info → together-2.0.0a16.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
- import json
5
- from typing import Any, Dict, List, Union, Literal
6
- from pathlib import Path
7
- from datetime import datetime, timezone
8
- from textwrap import wrap
3
+ from typing import Any, Literal
9
4
 
10
5
  import click
11
6
  from rich import print as rprint
12
- from tabulate import tabulate
13
- from rich.json import JSON
14
- from click.core import ParameterSource # type: ignore[attr-defined]
7
+ from click.core import ParameterSource
15
8
 
16
9
  from together import Together
17
10
  from together.types import fine_tuning_estimate_price_params as pe_params
18
- from together._types import NOT_GIVEN, NotGiven
19
11
  from together.lib.utils import log_warn
20
- from together.lib.utils.tools import format_timestamp, finetune_price_to_dollars
21
- from together.lib.cli.api.utils import INT_WITH_MAX, BOOL_WITH_AUTO, generate_progress_bar
22
- from together.lib.resources.files import DownloadManager
23
- from together.lib.utils.serializer import datetime_serializer
24
- from together.types.finetune_response import TrainingTypeFullTrainingType, TrainingTypeLoRaTrainingType
12
+ from together.lib.cli.api._utils import INT_WITH_MAX, BOOL_WITH_AUTO, handle_api_errors
25
13
  from together.lib.resources.fine_tuning import get_model_limits
26
14
 
27
15
  _CONFIRMATION_MESSAGE = (
@@ -41,17 +29,8 @@ _WARNING_MESSAGE_INSUFFICIENT_FUNDS = (
41
29
  "Consider increasing your credit limit at https://api.together.xyz/settings/profile\n"
42
30
  )
43
31
 
44
- _FT_JOB_WITH_STEP_REGEX = r"^ft-[\dabcdef-]+:\d+$"
45
32
 
46
-
47
- @click.group(name="fine-tuning")
48
- @click.pass_context
49
- def fine_tuning(ctx: click.Context) -> None:
50
- """Fine-tunes API commands"""
51
- pass
52
-
53
-
54
- @fine_tuning.command()
33
+ @click.command()
55
34
  @click.pass_context
56
35
  @click.option(
57
36
  "--training-file",
@@ -216,6 +195,7 @@ def fine_tuning(ctx: click.Context) -> None:
216
195
  default=None,
217
196
  help="HF repo to upload the fine-tuned model to",
218
197
  )
198
+ @handle_api_errors("Fine-tuning")
219
199
  def create(
220
200
  ctx: click.Context,
221
201
  training_file: str,
@@ -411,235 +391,3 @@ def create(
411
391
  rprint(report_string)
412
392
  else:
413
393
  click.echo("No confirmation received, stopping job launch")
414
-
415
-
416
- @fine_tuning.command()
417
- @click.pass_context
418
- def list(ctx: click.Context) -> None:
419
- """List fine-tuning jobs"""
420
- client: Together = ctx.obj
421
-
422
- response = client.fine_tuning.list()
423
-
424
- response.data = response.data or []
425
-
426
- # Use a default datetime for None values to make sure the key function always returns a comparable value
427
- epoch_start = datetime.fromtimestamp(0, tz=timezone.utc)
428
- response.data.sort(key=lambda x: x.created_at or epoch_start)
429
-
430
- display_list: List[Dict[str, Any]] = []
431
- for i in response.data:
432
- display_list.append(
433
- {
434
- "Fine-tune ID": i.id,
435
- "Model Output Name": "\n".join(wrap(i.x_model_output_name or "", width=30)),
436
- "Status": i.status,
437
- "Created At": i.created_at,
438
- "Price": f"""${
439
- finetune_price_to_dollars(float(str(i.total_price)))
440
- }""", # convert to string for mypy typing
441
- "Progress": generate_progress_bar(i, datetime.now().astimezone(), use_rich=False),
442
- }
443
- )
444
- table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
445
-
446
- click.echo(table)
447
-
448
-
449
- @fine_tuning.command()
450
- @click.pass_context
451
- @click.argument("fine_tune_id", type=str, required=True)
452
- def retrieve(ctx: click.Context, fine_tune_id: str) -> None:
453
- """Retrieve fine-tuning job details"""
454
- client: Together = ctx.obj
455
-
456
- response = client.fine_tuning.retrieve(fine_tune_id)
457
-
458
- # remove events from response for cleaner output
459
- response.events = None
460
-
461
- rprint(JSON.from_data(response.model_json_schema()))
462
- progress_text = generate_progress_bar(response, datetime.now().astimezone(), use_rich=True)
463
- prefix = f"Status: [bold]{response.status}[/bold],"
464
- rprint(f"{prefix} {progress_text}")
465
-
466
-
467
- @fine_tuning.command()
468
- @click.pass_context
469
- @click.argument("fine_tune_id", type=str, required=True)
470
- @click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before cancelling job")
471
- def cancel(ctx: click.Context, fine_tune_id: str, quiet: bool = False) -> None:
472
- """Cancel fine-tuning job"""
473
- client: Together = ctx.obj
474
- if not quiet:
475
- confirm_response = input(
476
- "You will be billed for any completed training steps upon cancellation. "
477
- f"Do you want to cancel job {fine_tune_id}? [y/N]"
478
- )
479
- if "y" not in confirm_response.lower():
480
- click.echo({"status": "Cancel not submitted"})
481
- return
482
- response = client.fine_tuning.cancel(fine_tune_id)
483
-
484
- click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4, default=datetime_serializer))
485
-
486
-
487
- @fine_tuning.command()
488
- @click.pass_context
489
- @click.argument("fine_tune_id", type=str, required=True)
490
- def list_events(ctx: click.Context, fine_tune_id: str) -> None:
491
- """List fine-tuning events"""
492
- client: Together = ctx.obj
493
-
494
- response = client.fine_tuning.list_events(fine_tune_id)
495
-
496
- response.data = response.data or []
497
-
498
- display_list: List[Dict[str, Any]] = []
499
- for i in response.data:
500
- display_list.append(
501
- {
502
- "Message": "\n".join(wrap(i.message or "", width=50)),
503
- "Type": i.type,
504
- "Created At": i.created_at,
505
- "Hash": i.hash,
506
- }
507
- )
508
- table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
509
-
510
- click.echo(table)
511
-
512
-
513
- @fine_tuning.command()
514
- @click.pass_context
515
- @click.argument("fine_tune_id", type=str, required=True)
516
- def list_checkpoints(ctx: click.Context, fine_tune_id: str) -> None:
517
- """List available checkpoints for a fine-tuning job"""
518
- client: Together = ctx.obj
519
-
520
- checkpoints = client.fine_tuning.list_checkpoints(fine_tune_id)
521
-
522
- display_list: List[Dict[str, Any]] = []
523
- for checkpoint in checkpoints.data:
524
- name = (
525
- f"{fine_tune_id}:{checkpoint.step}"
526
- if "intermediate" in checkpoint.checkpoint_type.lower()
527
- else fine_tune_id
528
- )
529
- display_list.append(
530
- {
531
- "Type": checkpoint.checkpoint_type,
532
- "Timestamp": format_timestamp(checkpoint.created_at),
533
- "Name": name,
534
- }
535
- )
536
-
537
- if display_list:
538
- click.echo(f"Job {fine_tune_id} contains the following checkpoints:")
539
- table = tabulate(display_list, headers="keys", tablefmt="grid")
540
- click.echo(table)
541
- click.echo("\nTo download a checkpoint, use `together fine-tuning download`")
542
- else:
543
- click.echo(f"No checkpoints found for job {fine_tune_id}")
544
-
545
-
546
- @fine_tuning.command(name="download")
547
- @click.pass_context
548
- @click.argument("fine_tune_id", type=str, required=True)
549
- @click.option(
550
- "--output_dir",
551
- "-o",
552
- type=click.Path(exists=True, file_okay=False, resolve_path=True),
553
- required=False,
554
- default=None,
555
- help="Output directory",
556
- )
557
- @click.option(
558
- "--checkpoint-step",
559
- "-s",
560
- type=int,
561
- required=False,
562
- default=None,
563
- help="Download fine-tuning checkpoint. Defaults to latest.",
564
- )
565
- @click.option(
566
- "--checkpoint-type",
567
- type=click.Choice(["merged", "adapter", "default"]),
568
- required=False,
569
- default="merged",
570
- help="Specifies checkpoint type. 'merged' and 'adapter' options work only for LoRA jobs.",
571
- )
572
- def download(
573
- ctx: click.Context,
574
- fine_tune_id: str,
575
- output_dir: str | None = None,
576
- checkpoint_step: Union[int, NotGiven] = NOT_GIVEN,
577
- checkpoint_type: Literal["default", "merged", "adapter"] | NotGiven = NOT_GIVEN,
578
- ) -> None:
579
- """Download fine-tuning checkpoint"""
580
- client: Together = ctx.obj
581
-
582
- if re.match(_FT_JOB_WITH_STEP_REGEX, fine_tune_id) is not None:
583
- if checkpoint_step is NOT_GIVEN:
584
- checkpoint_step = int(fine_tune_id.split(":")[1])
585
- fine_tune_id = fine_tune_id.split(":")[0]
586
- else:
587
- raise ValueError(
588
- "Fine-tuning job ID {fine_tune_id} contains a colon to specify the step to download, but `checkpoint_step` "
589
- "was also set. Remove one of the step specifiers to proceed."
590
- )
591
-
592
- ft_job = client.fine_tuning.retrieve(fine_tune_id)
593
-
594
- loosely_typed_checkpoint_type: str | NotGiven = checkpoint_type
595
- if isinstance(ft_job.training_type, TrainingTypeFullTrainingType):
596
- if checkpoint_type != "default":
597
- raise ValueError("Only DEFAULT checkpoint type is allowed for FullTrainingType")
598
- loosely_typed_checkpoint_type = "model_output_path"
599
- elif isinstance(ft_job.training_type, TrainingTypeLoRaTrainingType):
600
- if checkpoint_type == "default":
601
- loosely_typed_checkpoint_type = "merged"
602
-
603
- if checkpoint_type not in {
604
- "merged",
605
- "adapter",
606
- }:
607
- raise ValueError(f"Invalid checkpoint type for LoRATrainingType: {checkpoint_type}")
608
-
609
- remote_name = ft_job.x_model_output_name
610
-
611
- url = f"/finetune/download?ft_id={fine_tune_id}&checkpoint={loosely_typed_checkpoint_type}"
612
- output: Path | None = None
613
- if isinstance(output_dir, str):
614
- output = Path(output_dir)
615
-
616
- file_path, file_size = DownloadManager(client).download(
617
- url=url,
618
- output=output,
619
- remote_name=remote_name,
620
- fetch_metadata=True,
621
- )
622
-
623
- click.echo(json.dumps({"object": "local", "id": fine_tune_id, "filename": file_path, "size": file_size}, indent=4))
624
-
625
-
626
- @fine_tuning.command()
627
- @click.pass_context
628
- @click.argument("fine_tune_id", type=str, required=True)
629
- @click.option("--force", is_flag=True, help="Force deletion without confirmation")
630
- @click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before deleting job")
631
- def delete(ctx: click.Context, fine_tune_id: str, force: bool = False, quiet: bool = False) -> None:
632
- """Delete fine-tuning job"""
633
- client: Together = ctx.obj
634
-
635
- if not quiet:
636
- confirm_response = input(
637
- f"Are you sure you want to delete fine-tuning job {fine_tune_id}? This action cannot be undone. [y/N] "
638
- )
639
- if confirm_response.lower() != "y":
640
- click.echo("Deletion cancelled")
641
- return
642
-
643
- response = client.fine_tuning.delete(fine_tune_id, force=force)
644
-
645
- click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4))
@@ -0,0 +1,29 @@
1
+ import json
2
+
3
+ import click
4
+
5
+ from together import Together
6
+ from together.lib.cli.api._utils import handle_api_errors
7
+
8
+
9
+ @click.command()
10
+ @click.pass_context
11
+ @click.argument("fine_tune_id", type=str, required=True)
12
+ @click.option("--force", is_flag=True, help="Force deletion without confirmation")
13
+ @click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before deleting job")
14
+ @handle_api_errors("Fine-tuning")
15
+ def delete(ctx: click.Context, fine_tune_id: str, force: bool = False, quiet: bool = False) -> None:
16
+ """Delete fine-tuning job"""
17
+ client: Together = ctx.obj
18
+
19
+ if not quiet:
20
+ confirm_response = input(
21
+ f"Are you sure you want to delete fine-tuning job {fine_tune_id}? This action cannot be undone. [y/N] "
22
+ )
23
+ if confirm_response.lower() != "y":
24
+ click.echo("Deletion cancelled")
25
+ return
26
+
27
+ response = client.fine_tuning.delete(fine_tune_id, force=force)
28
+
29
+ click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4))
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import json
5
+ from typing import Union, Literal
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from together import NOT_GIVEN, NotGiven, Together
11
+ from together.lib import DownloadManager
12
+ from together.types.finetune_response import TrainingTypeFullTrainingType, TrainingTypeLoRaTrainingType
13
+
14
+ _FT_JOB_WITH_STEP_REGEX = r"^ft-[\dabcdef-]+:\d+$"
15
+
16
+
17
+ @click.command()
18
+ @click.pass_context
19
+ @click.argument("fine_tune_id", type=str, required=True)
20
+ @click.option(
21
+ "--output_dir",
22
+ "-o",
23
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
24
+ required=False,
25
+ default=None,
26
+ help="Output directory",
27
+ )
28
+ @click.option(
29
+ "--checkpoint-step",
30
+ "-s",
31
+ type=int,
32
+ required=False,
33
+ default=None,
34
+ help="Download fine-tuning checkpoint. Defaults to latest.",
35
+ )
36
+ @click.option(
37
+ "--checkpoint-type",
38
+ type=click.Choice(["merged", "adapter", "default"]),
39
+ required=False,
40
+ default="merged",
41
+ help="Specifies checkpoint type. 'merged' and 'adapter' options work only for LoRA jobs.",
42
+ )
43
+ def download(
44
+ ctx: click.Context,
45
+ fine_tune_id: str,
46
+ output_dir: str | None = None,
47
+ checkpoint_step: Union[int, NotGiven] = NOT_GIVEN,
48
+ checkpoint_type: Literal["default", "merged", "adapter"] | NotGiven = NOT_GIVEN,
49
+ ) -> None:
50
+ """Download fine-tuning checkpoint"""
51
+ client: Together = ctx.obj
52
+
53
+ if re.match(_FT_JOB_WITH_STEP_REGEX, fine_tune_id) is not None:
54
+ if checkpoint_step is NOT_GIVEN:
55
+ checkpoint_step = int(fine_tune_id.split(":")[1])
56
+ fine_tune_id = fine_tune_id.split(":")[0]
57
+ else:
58
+ raise ValueError(
59
+ "Fine-tuning job ID {fine_tune_id} contains a colon to specify the step to download, but `checkpoint_step` "
60
+ "was also set. Remove one of the step specifiers to proceed."
61
+ )
62
+
63
+ ft_job = client.fine_tuning.retrieve(fine_tune_id)
64
+
65
+ loosely_typed_checkpoint_type: str | NotGiven = checkpoint_type
66
+ if isinstance(ft_job.training_type, TrainingTypeFullTrainingType):
67
+ if checkpoint_type != "default":
68
+ raise ValueError("Only DEFAULT checkpoint type is allowed for FullTrainingType")
69
+ loosely_typed_checkpoint_type = "model_output_path"
70
+ elif isinstance(ft_job.training_type, TrainingTypeLoRaTrainingType):
71
+ if checkpoint_type == "default":
72
+ loosely_typed_checkpoint_type = "merged"
73
+
74
+ if checkpoint_type not in {
75
+ "merged",
76
+ "adapter",
77
+ }:
78
+ raise ValueError(f"Invalid checkpoint type for LoRATrainingType: {checkpoint_type}")
79
+
80
+ remote_name = ft_job.x_model_output_name
81
+
82
+ url = f"/finetune/download?ft_id={fine_tune_id}&checkpoint={loosely_typed_checkpoint_type}"
83
+ output: Path | None = None
84
+ if isinstance(output_dir, str):
85
+ output = Path(output_dir)
86
+
87
+ file_path, file_size = DownloadManager(client).download(
88
+ url=url,
89
+ output=output,
90
+ remote_name=remote_name,
91
+ fetch_metadata=True,
92
+ )
93
+
94
+ click.echo(json.dumps({"object": "local", "id": fine_tune_id, "filename": file_path, "size": file_size}, indent=4))
@@ -0,0 +1,44 @@
1
+ from typing import Any, Dict, List
2
+ from datetime import datetime, timezone
3
+ from textwrap import wrap
4
+
5
+ import click
6
+ from tabulate import tabulate
7
+
8
+ from together import Together
9
+ from together.lib.utils import finetune_price_to_dollars
10
+ from together.lib.cli.api._utils import handle_api_errors, generate_progress_bar
11
+
12
+
13
+ @click.command()
14
+ @click.pass_context
15
+ @handle_api_errors("Fine-tuning")
16
+ def list(ctx: click.Context) -> None:
17
+ """List fine-tuning jobs"""
18
+ client: Together = ctx.obj
19
+
20
+ response = client.fine_tuning.list()
21
+
22
+ response.data = response.data or []
23
+
24
+ # Use a default datetime for None values to make sure the key function always returns a comparable value
25
+ epoch_start = datetime.fromtimestamp(0, tz=timezone.utc)
26
+ response.data.sort(key=lambda x: x.created_at or epoch_start)
27
+
28
+ display_list: List[Dict[str, Any]] = []
29
+ for i in response.data:
30
+ display_list.append(
31
+ {
32
+ "Fine-tune ID": i.id,
33
+ "Model Output Name": "\n".join(wrap(i.x_model_output_name or "", width=30)),
34
+ "Status": i.status,
35
+ "Created At": i.created_at,
36
+ "Price": f"""${
37
+ finetune_price_to_dollars(float(str(i.total_price)))
38
+ }""", # convert to string for mypy typing
39
+ "Progress": generate_progress_bar(i, datetime.now().astimezone(), use_rich=False),
40
+ }
41
+ )
42
+ table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
43
+
44
+ click.echo(table)
@@ -0,0 +1,42 @@
1
+ from typing import Any, Dict, List
2
+
3
+ import click
4
+ from tabulate import tabulate
5
+
6
+ from together import Together
7
+ from together.lib.utils.tools import format_timestamp
8
+ from together.lib.cli.api._utils import handle_api_errors
9
+
10
+
11
+ @click.command()
12
+ @click.pass_context
13
+ @click.argument("fine_tune_id", type=str, required=True)
14
+ @handle_api_errors("Fine-tuning")
15
+ def list_checkpoints(ctx: click.Context, fine_tune_id: str) -> None:
16
+ """List available checkpoints for a fine-tuning job"""
17
+ client: Together = ctx.obj
18
+
19
+ checkpoints = client.fine_tuning.list_checkpoints(fine_tune_id)
20
+
21
+ display_list: List[Dict[str, Any]] = []
22
+ for checkpoint in checkpoints.data:
23
+ name = (
24
+ f"{fine_tune_id}:{checkpoint.step}"
25
+ if "intermediate" in checkpoint.checkpoint_type.lower()
26
+ else fine_tune_id
27
+ )
28
+ display_list.append(
29
+ {
30
+ "Type": checkpoint.checkpoint_type,
31
+ "Timestamp": format_timestamp(checkpoint.created_at),
32
+ "Name": name,
33
+ }
34
+ )
35
+
36
+ if display_list:
37
+ click.echo(f"Job {fine_tune_id} contains the following checkpoints:")
38
+ table = tabulate(display_list, headers="keys", tablefmt="grid")
39
+ click.echo(table)
40
+ click.echo("\nTo download a checkpoint, use `together fine-tuning download`")
41
+ else:
42
+ click.echo(f"No checkpoints found for job {fine_tune_id}")
@@ -0,0 +1,35 @@
1
+ from typing import Any, Dict, List
2
+ from textwrap import wrap
3
+
4
+ import click
5
+ from tabulate import tabulate
6
+
7
+ from together import Together
8
+ from together.lib.cli.api._utils import handle_api_errors
9
+
10
+
11
+ @click.command()
12
+ @click.pass_context
13
+ @click.argument("fine_tune_id", type=str, required=True)
14
+ @handle_api_errors("Fine-tuning")
15
+ def list_events(ctx: click.Context, fine_tune_id: str) -> None:
16
+ """List fine-tuning events"""
17
+ client: Together = ctx.obj
18
+
19
+ response = client.fine_tuning.list_events(fine_tune_id)
20
+
21
+ response.data = response.data or []
22
+
23
+ display_list: List[Dict[str, Any]] = []
24
+ for i in response.data:
25
+ display_list.append(
26
+ {
27
+ "Message": "\n".join(wrap(i.message or "", width=50)),
28
+ "Type": i.type,
29
+ "Created At": i.created_at,
30
+ "Hash": i.hash,
31
+ }
32
+ )
33
+ table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
34
+
35
+ click.echo(table)
@@ -0,0 +1,27 @@
1
+ from datetime import datetime
2
+
3
+ import click
4
+ from rich import print as rprint
5
+ from rich.json import JSON
6
+
7
+ from together import Together
8
+ from together.lib.cli.api._utils import handle_api_errors, generate_progress_bar
9
+
10
+
11
+ @click.command()
12
+ @click.pass_context
13
+ @click.argument("fine_tune_id", type=str, required=True)
14
+ @handle_api_errors("Fine-tuning")
15
+ def retrieve(ctx: click.Context, fine_tune_id: str) -> None:
16
+ """Retrieve fine-tuning job details"""
17
+ client: Together = ctx.obj
18
+
19
+ response = client.fine_tuning.retrieve(fine_tune_id)
20
+
21
+ # remove events from response for cleaner output
22
+ response.events = None
23
+
24
+ rprint(JSON.from_data(response.model_json_schema()))
25
+ progress_text = generate_progress_bar(response, datetime.now().astimezone(), use_rich=True)
26
+ prefix = f"Status: [bold]{response.status}[/bold],"
27
+ rprint(f"{prefix} {progress_text}")
@@ -0,0 +1,15 @@
1
+ import click
2
+
3
+ from .list import list
4
+ from .upload import upload
5
+
6
+
7
+ @click.group()
8
+ @click.pass_context
9
+ def models(ctx: click.Context) -> None:
10
+ """Models API commands"""
11
+ pass
12
+
13
+
14
+ models.add_command(list)
15
+ models.add_command(upload)
@@ -0,0 +1,51 @@
1
+ import json as json_lib
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ import click
5
+ from tabulate import tabulate
6
+
7
+ from together import Together, omit
8
+ from together._models import BaseModel
9
+ from together._response import APIResponse as APIResponse
10
+ from together.lib.cli.api._utils import handle_api_errors
11
+
12
+
13
+ @click.command()
14
+ @click.option(
15
+ "--type",
16
+ type=click.Choice(["dedicated"]),
17
+ help="Filter models by type (dedicated: models that can be deployed as dedicated endpoints)",
18
+ )
19
+ @click.option(
20
+ "--json",
21
+ is_flag=True,
22
+ help="Output in JSON format",
23
+ )
24
+ @click.pass_context
25
+ @handle_api_errors("Models")
26
+ def list(ctx: click.Context, type: Optional[str], json: bool) -> None:
27
+ """List models"""
28
+ client: Together = ctx.obj
29
+
30
+ models_list = client.models.list(dedicated=type == "dedicated" if type else omit)
31
+
32
+ display_list: List[Dict[str, Any]] = []
33
+ model: BaseModel
34
+ for model in models_list:
35
+ display_list.append(
36
+ {
37
+ "ID": model.id,
38
+ "Name": model.display_name,
39
+ "Organization": model.organization,
40
+ "Type": model.type,
41
+ "Context Length": model.context_length,
42
+ "License": model.license,
43
+ "Input per 1M token": model.pricing.input if model.pricing else None,
44
+ "Output per 1M token": model.pricing.output if model.pricing else None,
45
+ }
46
+ )
47
+
48
+ if json:
49
+ click.echo(json_lib.dumps(display_list, indent=2))
50
+ else:
51
+ click.echo(tabulate(display_list, headers="keys", tablefmt="plain"))