diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 36c19864..5b9391e4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.0.0-alpha.7" + ".": "2.0.0-alpha.8" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index cdc29db4..531bf782 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai%2Ftogetherai-e9e60279414ac3279c025d6318b5f67a8f6d01170e365612e791f3a1f259b94f.yml -openapi_spec_hash: 26c59292808c5ae9f222f95f056430cf -config_hash: 468163c38238466d0306b30eb29cb0d0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai%2Ftogetherai-a1f15d8f8f7326616ea246a73d53bda093da7a9a5e3fe50a9ec6a5a0b958ec63.yml +openapi_spec_hash: 7a03e5140a9a6668ff42c47ea0d03a07 +config_hash: 87a5832ab2ecefe567d22108531232f5 diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ad8138..fa615657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 2.0.0-alpha.8 (2025-11-26) + +Full Changelog: [v2.0.0-alpha.7...v2.0.0-alpha.8](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.7...v2.0.0-alpha.8) + +### Features + +* **api:** api update ([49bb5d4](https://github.com/togethercomputer/together-py/commit/49bb5d4ba69ca118ecc34be2d69c4253665e2e81)) +* **api:** Fix internal references for VideoJob spec ([fb5e7bb](https://github.com/togethercomputer/together-py/commit/fb5e7bb3dbaa9427d291de7440c201529b6cf528)) + + +### Bug Fixes + +* Address incorrect logic for `endpoint [command] --wait false` logic ([31236a9](https://github.com/togethercomputer/together-py/commit/31236a9df29c22fe7444c2dbb0d4bfc518bc79aa)) + + +### Chores + +* Remove incorrect file upload docs ([5bb847e](https://github.com/togethercomputer/together-py/commit/5bb847e33b55e5d0978c742e86cf931a2c08f919)) +* Remove incorrect file upload docs ([bb97093](https://github.com/togethercomputer/together-py/commit/bb970938650b6f9580538528979221d142f74b6a)) + ## 2.0.0-alpha.7 (2025-11-26) Full Changelog: [v2.0.0-alpha.6...v2.0.0-alpha.7](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) diff --git a/README.md b/README.md index 736ea1cd..b1aa433d 100644 --- a/README.md +++ b/README.md @@ -198,22 +198,6 @@ chat_completion = client.chat.completions.create( print(chat_completion.response_format) ``` -## File uploads - -Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. - -```python -from pathlib import Path -from together import Together - -client = Together() - -client.files.upload( - file=Path("/path/to/file"), - purpose="fine-tune", -) -``` - The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. ## Handling errors @@ -482,40 +466,6 @@ with Together() as client: ## Usage – CLI -### Chat Completions - -```bash -together chat.completions \ - --message "system" "You are a helpful assistant named Together" \ - --message "user" "What is your name?" \ - --model mistralai/Mixtral-8x7B-Instruct-v0.1 -``` - -The Chat Completions CLI enables streaming tokens to stdout by default. To disable streaming, use `--no-stream`. - -### Completions - -```bash -together completions \ - "Large language models are " \ - --model mistralai/Mixtral-8x7B-v0.1 \ - --max-tokens 512 \ - --stop "." -``` - -The Completions CLI enables streaming tokens to stdout by default. To disable streaming, use `--no-stream`. - -### Image Generations - -```bash -together images generate \ - "space robots" \ - --model stabilityai/stable-diffusion-xl-base-1.0 \ - --n 4 -``` - -The image is opened in the default image viewer by default. To disable this, use `--no-show`. - ### Files ```bash diff --git a/api.md b/api.md index f31a0a05..56e8517a 100644 --- a/api.md +++ b/api.md @@ -134,12 +134,12 @@ Methods: Types: ```python -from together.types import VideoJob, VideoCreateResponse +from together.types import VideoJob ``` Methods: -- client.videos.create(\*\*params) -> VideoCreateResponse +- client.videos.create(\*\*params) -> VideoJob - client.videos.retrieve(id) -> VideoJob # Audio diff --git a/pyproject.toml b/pyproject.toml index af85f39b..78ae8d26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "together" -version = "2.0.0-alpha.7" +version = "2.0.0-alpha.8" description = "The official Python library for the together API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/together/_version.py b/src/together/_version.py index 92c5f76f..bdd23e76 100644 --- a/src/together/_version.py +++ b/src/together/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "together" -__version__ = "2.0.0-alpha.7" # x-release-please-version +__version__ = "2.0.0-alpha.8" # x-release-please-version diff --git a/src/together/lib/cli/api/endpoints.py b/src/together/lib/cli/api/endpoints.py index cf3598ed..8383f629 100644 --- a/src/together/lib/cli/api/endpoints.py +++ b/src/together/lib/cli/api/endpoints.py @@ -128,8 +128,7 @@ def endpoints(ctx: click.Context) -> None: help="Start endpoint in specified availability zone (e.g., us-central-4b)", ) @click.option( - "--wait", - is_flag=True, + "--wait/--no-wait", default=True, help="Wait for the endpoint to be ready after creation", ) @@ -272,7 +271,7 @@ def fetch_and_print_hardware_options(client: Together, model: str | None, print_ @endpoints.command() @click.argument("endpoint-id", required=True) -@click.option("--wait", is_flag=True, default=True, help="Wait for the endpoint to stop") +@click.option("--wait/--no-wait", default=True, help="Wait for the endpoint to stop") @click.pass_obj @handle_api_errors def stop(client: Together, endpoint_id: str, wait: bool) -> None: @@ -293,7 +292,7 @@ def stop(client: Together, endpoint_id: str, wait: bool) -> None: @endpoints.command() @click.argument("endpoint-id", required=True) -@click.option("--wait", is_flag=True, default=True, help="Wait for the endpoint to start") +@click.option("--wait/--no-wait", default=True, help="Wait for the endpoint to start") @click.pass_obj @handle_api_errors def start(client: Together, endpoint_id: str, wait: bool) -> None: diff --git a/src/together/lib/types/fine_tuning.py b/src/together/lib/types/fine_tuning.py index 55327e5a..87f96857 100644 --- a/src/together/lib/types/fine_tuning.py +++ b/src/together/lib/types/fine_tuning.py @@ -70,9 +70,6 @@ class FinetuneEventLevels(str, Enum): INFO = "Info" WARNING = "Warning" ERROR = "Error" - LEGACY_INFO = "info" - LEGACY_IWARNING = "warning" - LEGACY_IERROR = "error" class FinetuneEvent(BaseModel): @@ -85,7 +82,7 @@ class FinetuneEvent(BaseModel): # created at datetime stamp created_at: Union[str, None] = None # event log level - level: Union[FinetuneEventLevels, None] = None + level: Union[FinetuneEventLevels, str, None] = None # event message string message: Union[str, None] = None # event type @@ -120,7 +117,20 @@ class LoRATrainingType(BaseModel): type: Literal["Lora"] = "Lora" -TrainingType: TypeAlias = Union[FullTrainingType, LoRATrainingType] +class UnknownTrainingType(BaseModel): + """ + Catch-all for unknown training types (forward compatibility). + Accepts any training type not explicitly defined. + """ + + type: str + + +TrainingType: TypeAlias = Union[ + FullTrainingType, + LoRATrainingType, + UnknownTrainingType, +] class FinetuneFullTrainingLimits(BaseModel): @@ -163,7 +173,19 @@ class TrainingMethodDPO(BaseModel): simpo_gamma: Union[float, None] = None -TrainingMethod: TypeAlias = Union[TrainingMethodSFT, TrainingMethodDPO] +class TrainingMethodUnknown(BaseModel): + """ + Catch-all for unknown training methods (forward compatibility). + Accepts any training method not explicitly defined. + """ + + method: str + +TrainingMethod: TypeAlias = Union[ + TrainingMethodSFT, + TrainingMethodDPO, + TrainingMethodUnknown, +] class FinetuneTrainingLimits(BaseModel): @@ -175,31 +197,67 @@ class FinetuneTrainingLimits(BaseModel): class LinearLRSchedulerArgs(BaseModel): + """ + Linear learning rate scheduler arguments + """ + min_lr_ratio: Union[float, None] = 0.0 class CosineLRSchedulerArgs(BaseModel): + """ + Cosine learning rate scheduler arguments + """ + min_lr_ratio: Union[float, None] = 0.0 num_cycles: Union[float, None] = 0.5 class LinearLRScheduler(BaseModel): + """ + Linear learning rate scheduler + """ + lr_scheduler_type: Literal["linear"] = "linear" lr_scheduler_args: Union[LinearLRSchedulerArgs, None] = None class CosineLRScheduler(BaseModel): + """ + Cosine learning rate scheduler + """ + lr_scheduler_type: Literal["cosine"] = "cosine" lr_scheduler_args: Union[CosineLRSchedulerArgs, None] = None -# placeholder for old fine-tuning jobs with no lr_scheduler_type specified class EmptyLRScheduler(BaseModel): + """ + Empty learning rate scheduler + + Placeholder for old fine-tuning jobs with no lr_scheduler_type specified + """ + lr_scheduler_type: Literal[""] lr_scheduler_args: None = None +class UnknownLRScheduler(BaseModel): + """ + Unknown learning rate scheduler + + Catch-all for unknown LR scheduler types (forward compatibility) + """ + + lr_scheduler_type: str + lr_scheduler_args: Optional[Any] = None + -FinetuneLRScheduler: TypeAlias = Union[LinearLRScheduler, CosineLRScheduler, EmptyLRScheduler] +FinetuneLRScheduler: TypeAlias = Union[ + LinearLRScheduler, + CosineLRScheduler, + EmptyLRScheduler, + UnknownLRScheduler, +] class FinetuneResponse(BaseModel): @@ -213,8 +271,8 @@ class FinetuneResponse(BaseModel): created_at: datetime """Creation timestamp of the fine-tune job""" - status: Optional[FinetuneJobStatus] = None - """Status of the fine-tune job""" + status: Optional[Union[FinetuneJobStatus, str]] = None + """Status of the fine-tune job (accepts known enum values or string for forward compatibility)""" updated_at: datetime """Last update timestamp of the fine-tune job""" @@ -222,8 +280,8 @@ class FinetuneResponse(BaseModel): batch_size: Optional[int] = None """Batch size used for training""" - events: Optional[List[FinetuneEvent]] = None - """Events related to this fine-tune job""" + events: Optional[List[Union[FinetuneEvent, str]]] = None + """Events related to this fine-tune job (accepts known enum values or string for forward compatibility)""" from_checkpoint: Optional[str] = None """Checkpoint used to continue training""" @@ -361,7 +419,7 @@ class FinetuneRequest(BaseModel): # training learning rate learning_rate: float # learning rate scheduler type and args - lr_scheduler: Union[LinearLRScheduler, CosineLRScheduler, None] = None + lr_scheduler: Union[FinetuneLRScheduler, None] = None # learning rate warmup ratio warmup_ratio: float # max gradient norm @@ -387,7 +445,7 @@ class FinetuneRequest(BaseModel): # training type training_type: Union[TrainingType, None] = None # training method - training_method: Union[TrainingMethodSFT, TrainingMethodDPO] = Field(default_factory=TrainingMethodSFT) + training_method: TrainingMethod = Field(default_factory=TrainingMethodSFT) # from step from_checkpoint: Union[str, None] = None from_hf_model: Union[str, None] = None diff --git a/src/together/resources/fine_tuning.py b/src/together/resources/fine_tuning.py index 47362d31..95984655 100644 --- a/src/together/resources/fine_tuning.py +++ b/src/together/resources/fine_tuning.py @@ -291,7 +291,7 @@ def delete( self, id: str, *, - force: bool, + force: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -374,7 +374,7 @@ def content( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BinaryAPIResponse: """ - Download a compressed fine-tuned model or checkpoint. + Receive a compressed fine-tuned model or checkpoint. Args: ft_id: Fine-tune ID to download. A string that starts with `ft-`. @@ -732,7 +732,7 @@ async def delete( self, id: str, *, - force: bool, + force: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -815,7 +815,7 @@ async def content( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncBinaryAPIResponse: """ - Download a compressed fine-tuned model or checkpoint. + Receive a compressed fine-tuned model or checkpoint. Args: ft_id: Fine-tune ID to download. A string that starts with `ft-`. diff --git a/src/together/resources/videos.py b/src/together/resources/videos.py index 7d6f9a20..76cbe5ec 100644 --- a/src/together/resources/videos.py +++ b/src/together/resources/videos.py @@ -20,7 +20,6 @@ ) from .._base_client import make_request_options from ..types.video_job import VideoJob -from ..types.video_create_response import VideoCreateResponse __all__ = ["VideosResource", "AsyncVideosResource"] @@ -68,7 +67,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoCreateResponse: + ) -> VideoJob: """ Create a video @@ -139,7 +138,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoCreateResponse, + cast_to=VideoJob, ) def retrieve( @@ -219,7 +218,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoCreateResponse: + ) -> VideoJob: """ Create a video @@ -290,7 +289,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoCreateResponse, + cast_to=VideoJob, ) async def retrieve( diff --git a/src/together/types/__init__.py b/src/together/types/__init__.py index c154ae85..b84f56be 100644 --- a/src/together/types/__init__.py +++ b/src/together/types/__init__.py @@ -46,7 +46,6 @@ from .image_generate_params import ImageGenerateParams as ImageGenerateParams from .job_retrieve_response import JobRetrieveResponse as JobRetrieveResponse from .model_upload_response import ModelUploadResponse as ModelUploadResponse -from .video_create_response import VideoCreateResponse as VideoCreateResponse from .endpoint_create_params import EndpointCreateParams as EndpointCreateParams from .endpoint_list_response import EndpointListResponse as EndpointListResponse from .endpoint_update_params import EndpointUpdateParams as EndpointUpdateParams diff --git a/src/together/types/fine_tuning_delete_params.py b/src/together/types/fine_tuning_delete_params.py index 5d1e7114..d5343a86 100644 --- a/src/together/types/fine_tuning_delete_params.py +++ b/src/together/types/fine_tuning_delete_params.py @@ -2,10 +2,10 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import TypedDict __all__ = ["FineTuningDeleteParams"] class FineTuningDeleteParams(TypedDict, total=False): - force: Required[bool] + force: bool diff --git a/src/together/types/video_create_response.py b/src/together/types/video_create_response.py deleted file mode 100644 index ab684862..00000000 --- a/src/together/types/video_create_response.py +++ /dev/null @@ -1,10 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["VideoCreateResponse"] - - -class VideoCreateResponse(BaseModel): - id: str - """Unique identifier for the video job.""" diff --git a/tests/api_resources/test_fine_tuning.py b/tests/api_resources/test_fine_tuning.py index 73be0fe1..b8a69478 100644 --- a/tests/api_resources/test_fine_tuning.py +++ b/tests/api_resources/test_fine_tuning.py @@ -97,6 +97,13 @@ def test_streaming_response_list(self, client: Together) -> None: @parametrize def test_method_delete(self, client: Together) -> None: + fine_tuning = client.fine_tuning.delete( + id="id", + ) + assert_matches_type(FineTuningDeleteResponse, fine_tuning, path=["response"]) + + @parametrize + def test_method_delete_with_all_params(self, client: Together) -> None: fine_tuning = client.fine_tuning.delete( id="id", force=True, @@ -107,7 +114,6 @@ def test_method_delete(self, client: Together) -> None: def test_raw_response_delete(self, client: Together) -> None: response = client.fine_tuning.with_raw_response.delete( id="id", - force=True, ) assert response.is_closed is True @@ -119,7 +125,6 @@ def test_raw_response_delete(self, client: Together) -> None: def test_streaming_response_delete(self, client: Together) -> None: with client.fine_tuning.with_streaming_response.delete( id="id", - force=True, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -134,7 +139,6 @@ def test_path_params_delete(self, client: Together) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.fine_tuning.with_raw_response.delete( id="", - force=True, ) @parametrize @@ -378,6 +382,13 @@ async def test_streaming_response_list(self, async_client: AsyncTogether) -> Non @parametrize async def test_method_delete(self, async_client: AsyncTogether) -> None: + fine_tuning = await async_client.fine_tuning.delete( + id="id", + ) + assert_matches_type(FineTuningDeleteResponse, fine_tuning, path=["response"]) + + @parametrize + async def test_method_delete_with_all_params(self, async_client: AsyncTogether) -> None: fine_tuning = await async_client.fine_tuning.delete( id="id", force=True, @@ -388,7 +399,6 @@ async def test_method_delete(self, async_client: AsyncTogether) -> None: async def test_raw_response_delete(self, async_client: AsyncTogether) -> None: response = await async_client.fine_tuning.with_raw_response.delete( id="id", - force=True, ) assert response.is_closed is True @@ -400,7 +410,6 @@ async def test_raw_response_delete(self, async_client: AsyncTogether) -> None: async def test_streaming_response_delete(self, async_client: AsyncTogether) -> None: async with async_client.fine_tuning.with_streaming_response.delete( id="id", - force=True, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -415,7 +424,6 @@ async def test_path_params_delete(self, async_client: AsyncTogether) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.fine_tuning.with_raw_response.delete( id="", - force=True, ) @parametrize diff --git a/tests/api_resources/test_videos.py b/tests/api_resources/test_videos.py index 62476221..2cac19c8 100644 --- a/tests/api_resources/test_videos.py +++ b/tests/api_resources/test_videos.py @@ -9,7 +9,7 @@ from together import Together, AsyncTogether from tests.utils import assert_matches_type -from together.types import VideoJob, VideoCreateResponse +from together.types import VideoJob base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_create(self, client: Together) -> None: video = client.videos.create( model="model", ) - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Together) -> None: @@ -47,7 +47,7 @@ def test_method_create_with_all_params(self, client: Together) -> None: steps=10, width=0, ) - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize def test_raw_response_create(self, client: Together) -> None: @@ -58,7 +58,7 @@ def test_raw_response_create(self, client: Together) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = response.parse() - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize def test_streaming_response_create(self, client: Together) -> None: @@ -69,7 +69,7 @@ def test_streaming_response_create(self, client: Together) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = response.parse() - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) assert cast(Any, response.is_closed) is True @@ -122,7 +122,7 @@ async def test_method_create(self, async_client: AsyncTogether) -> None: video = await async_client.videos.create( model="model", ) - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncTogether) -> None: @@ -147,7 +147,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncTogether) steps=10, width=0, ) - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncTogether) -> None: @@ -158,7 +158,7 @@ async def test_raw_response_create(self, async_client: AsyncTogether) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = await response.parse() - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncTogether) -> None: @@ -169,7 +169,7 @@ async def test_streaming_response_create(self, async_client: AsyncTogether) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = await response.parse() - assert_matches_type(VideoCreateResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/unit/test_fine_tuning_resources.py b/tests/unit/test_fine_tuning_resources.py index a8803ae4..4bd1de7b 100644 --- a/tests/unit/test_fine_tuning_resources.py +++ b/tests/unit/test_fine_tuning_resources.py @@ -5,6 +5,7 @@ from together.lib.types.fine_tuning import ( FullTrainingType, LoRATrainingType, + TrainingMethodSFT, FinetuneTrainingLimits, FinetuneFullTrainingLimits, FinetuneLoraTrainingLimits, @@ -320,6 +321,7 @@ def test_train_on_inputs_for_sft(train_on_inputs: Union[bool, Literal["auto"], N training_method="sft", train_on_inputs=train_on_inputs, ) + assert isinstance(request.training_method, TrainingMethodSFT) assert request.training_method.method == "sft" if isinstance(train_on_inputs, bool): assert request.training_method.train_on_inputs is train_on_inputs