From 9c17199e753af85d9e7e203f277a8660b76f7974 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Wed, 26 Nov 2025 19:15:30 +0400 Subject: [PATCH 1/7] MOSH-973: Make Stainless Python client forward-compatible for fine-tuning custom injections (#181) * fix: make Stainless Python client forward-compatible for fine-tuning custom injections --- src/together/lib/types/fine_tuning.py | 86 ++++++++++++++++++++---- tests/unit/test_fine_tuning_resources.py | 2 + 2 files changed, 74 insertions(+), 14 deletions(-) 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/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 From 31236a9df29c22fe7444c2dbb0d4bfc518bc79aa Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Wed, 26 Nov 2025 09:36:59 -0600 Subject: [PATCH 2/7] fix: Address incorrect logic for `endpoint [command] --wait false` logic --- src/together/lib/cli/api/endpoints.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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: From bb970938650b6f9580538528979221d142f74b6a Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Wed, 26 Nov 2025 09:45:16 -0600 Subject: [PATCH 3/7] chore: Remove incorrect file upload docs --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index 736ea1cd..a105bdc0 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 From 5bb847e33b55e5d0978c742e86cf931a2c08f919 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Wed, 26 Nov 2025 09:46:06 -0600 Subject: [PATCH 4/7] chore: Remove incorrect file upload docs --- README.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/README.md b/README.md index a105bdc0..b1aa433d 100644 --- a/README.md +++ b/README.md @@ -466,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 From 49bb5d4ba69ca118ecc34be2d69c4253665e2e81 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:49:10 +0000 Subject: [PATCH 5/7] feat(api): api update --- .stats.yml | 4 +- api.md | 4 +- src/together/resources/fine_tuning.py | 8 +-- src/together/resources/videos.py | 10 ++-- src/together/types/__init__.py | 2 +- .../types/fine_tuning_delete_params.py | 4 +- src/together/types/video_create_response.py | 49 ++++++++++++++++++- ...ideo_job.py => video_retrieve_response.py} | 4 +- tests/api_resources/test_fine_tuning.py | 20 +++++--- tests/api_resources/test_videos.py | 14 +++--- 10 files changed, 87 insertions(+), 32 deletions(-) rename src/together/types/{video_job.py => video_retrieve_response.py} (93%) diff --git a/.stats.yml b/.stats.yml index cdc29db4..cb2295da 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 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai%2Ftogetherai-a1f15d8f8f7326616ea246a73d53bda093da7a9a5e3fe50a9ec6a5a0b958ec63.yml +openapi_spec_hash: 7a03e5140a9a6668ff42c47ea0d03a07 config_hash: 468163c38238466d0306b30eb29cb0d0 diff --git a/api.md b/api.md index f31a0a05..bb22566c 100644 --- a/api.md +++ b/api.md @@ -134,13 +134,13 @@ Methods: Types: ```python -from together.types import VideoJob, VideoCreateResponse +from together.types import VideoCreateResponse, VideoRetrieveResponse ``` Methods: - client.videos.create(\*\*params) -> VideoCreateResponse -- client.videos.retrieve(id) -> VideoJob +- client.videos.retrieve(id) -> VideoRetrieveResponse # Audio 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..d0d8cf58 100644 --- a/src/together/resources/videos.py +++ b/src/together/resources/videos.py @@ -19,8 +19,8 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.video_job import VideoJob from ..types.video_create_response import VideoCreateResponse +from ..types.video_retrieve_response import VideoRetrieveResponse __all__ = ["VideosResource", "AsyncVideosResource"] @@ -152,7 +152,7 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoJob: + ) -> VideoRetrieveResponse: """ Fetch video metadata @@ -172,7 +172,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoJob, + cast_to=VideoRetrieveResponse, ) @@ -303,7 +303,7 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoJob: + ) -> VideoRetrieveResponse: """ Fetch video metadata @@ -323,7 +323,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoJob, + cast_to=VideoRetrieveResponse, ) diff --git a/src/together/types/__init__.py b/src/together/types/__init__.py index c154ae85..74397022 100644 --- a/src/together/types/__init__.py +++ b/src/together/types/__init__.py @@ -7,7 +7,6 @@ from .file_list import FileList as FileList from .file_type import FileType as FileType from .log_probs import LogProbs as LogProbs -from .video_job import VideoJob as VideoJob from .completion import Completion as Completion from .image_file import ImageFile as ImageFile from .autoscaling import Autoscaling as Autoscaling @@ -53,6 +52,7 @@ from .hardware_list_response import HardwareListResponse as HardwareListResponse from .rerank_create_response import RerankCreateResponse as RerankCreateResponse from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams +from .video_retrieve_response import VideoRetrieveResponse as VideoRetrieveResponse from .completion_create_params import CompletionCreateParams as CompletionCreateParams from .audio_speech_stream_chunk import AudioSpeechStreamChunk as AudioSpeechStreamChunk from .fine_tuning_delete_params import FineTuningDeleteParams as FineTuningDeleteParams 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 index ab684862..19c65269 100644 --- a/src/together/types/video_create_response.py +++ b/src/together/types/video_create_response.py @@ -1,10 +1,57 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional +from typing_extensions import Literal + from .._models import BaseModel -__all__ = ["VideoCreateResponse"] +__all__ = ["VideoCreateResponse", "Error", "Outputs"] + + +class Error(BaseModel): + message: str + + code: Optional[str] = None + + +class Outputs(BaseModel): + cost: int + """The cost of generated video charged to the owners account.""" + + video_url: str + """URL hosting the generated video""" class VideoCreateResponse(BaseModel): id: str """Unique identifier for the video job.""" + + created_at: float + """Unix timestamp (seconds) for when the job was created.""" + + model: str + """The video generation model that produced the job.""" + + seconds: str + """Duration of the generated clip in seconds.""" + + size: str + """The resolution of the generated video.""" + + status: Literal["in_progress", "completed", "failed"] + """Current lifecycle status of the video job.""" + + completed_at: Optional[float] = None + """Unix timestamp (seconds) for when the job completed, if finished.""" + + error: Optional[Error] = None + """Error payload that explains why generation failed, if applicable.""" + + object: Optional[Literal["video"]] = None + """The object type, which is always video.""" + + outputs: Optional[Outputs] = None + """ + Available upon completion, the outputs provides the cost charged and the hosted + url to access the video + """ diff --git a/src/together/types/video_job.py b/src/together/types/video_retrieve_response.py similarity index 93% rename from src/together/types/video_job.py rename to src/together/types/video_retrieve_response.py index 58fab304..96174f66 100644 --- a/src/together/types/video_job.py +++ b/src/together/types/video_retrieve_response.py @@ -5,7 +5,7 @@ from .._models import BaseModel -__all__ = ["VideoJob", "Error", "Outputs"] +__all__ = ["VideoRetrieveResponse", "Error", "Outputs"] class Error(BaseModel): @@ -22,7 +22,7 @@ class Outputs(BaseModel): """URL hosting the generated video""" -class VideoJob(BaseModel): +class VideoRetrieveResponse(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..907b54d4 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 VideoCreateResponse, VideoRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -78,7 +78,7 @@ def test_method_retrieve(self, client: Together) -> None: video = client.videos.retrieve( "id", ) - assert_matches_type(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Together) -> None: @@ -89,7 +89,7 @@ def test_raw_response_retrieve(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(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Together) -> None: @@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Together) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = response.parse() - assert_matches_type(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) assert cast(Any, response.is_closed) is True @@ -178,7 +178,7 @@ async def test_method_retrieve(self, async_client: AsyncTogether) -> None: video = await async_client.videos.retrieve( "id", ) - assert_matches_type(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncTogether) -> None: @@ -189,7 +189,7 @@ async def test_raw_response_retrieve(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(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncTogether) -> None: @@ -200,7 +200,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncTogether) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = await response.parse() - assert_matches_type(VideoJob, video, path=["response"]) + assert_matches_type(VideoRetrieveResponse, video, path=["response"]) assert cast(Any, response.is_closed) is True From fb5e7bb3dbaa9427d291de7440c201529b6cf528 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:28:39 +0000 Subject: [PATCH 6/7] feat(api): Fix internal references for VideoJob spec --- .stats.yml | 2 +- api.md | 6 +- src/together/resources/videos.py | 19 +++---- src/together/types/__init__.py | 3 +- ...{video_create_response.py => video_job.py} | 4 +- src/together/types/video_retrieve_response.py | 57 ------------------- tests/api_resources/test_videos.py | 30 +++++----- 7 files changed, 31 insertions(+), 90 deletions(-) rename src/together/types/{video_create_response.py => video_job.py} (93%) delete mode 100644 src/together/types/video_retrieve_response.py diff --git a/.stats.yml b/.stats.yml index cb2295da..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-a1f15d8f8f7326616ea246a73d53bda093da7a9a5e3fe50a9ec6a5a0b958ec63.yml openapi_spec_hash: 7a03e5140a9a6668ff42c47ea0d03a07 -config_hash: 468163c38238466d0306b30eb29cb0d0 +config_hash: 87a5832ab2ecefe567d22108531232f5 diff --git a/api.md b/api.md index bb22566c..56e8517a 100644 --- a/api.md +++ b/api.md @@ -134,13 +134,13 @@ Methods: Types: ```python -from together.types import VideoCreateResponse, VideoRetrieveResponse +from together.types import VideoJob ``` Methods: -- client.videos.create(\*\*params) -> VideoCreateResponse -- client.videos.retrieve(id) -> VideoRetrieveResponse +- client.videos.create(\*\*params) -> VideoJob +- client.videos.retrieve(id) -> VideoJob # Audio diff --git a/src/together/resources/videos.py b/src/together/resources/videos.py index d0d8cf58..76cbe5ec 100644 --- a/src/together/resources/videos.py +++ b/src/together/resources/videos.py @@ -19,8 +19,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.video_create_response import VideoCreateResponse -from ..types.video_retrieve_response import VideoRetrieveResponse +from ..types.video_job import VideoJob __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( @@ -152,7 +151,7 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoRetrieveResponse: + ) -> VideoJob: """ Fetch video metadata @@ -172,7 +171,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoRetrieveResponse, + cast_to=VideoJob, ) @@ -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( @@ -303,7 +302,7 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> VideoRetrieveResponse: + ) -> VideoJob: """ Fetch video metadata @@ -323,7 +322,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=VideoRetrieveResponse, + cast_to=VideoJob, ) diff --git a/src/together/types/__init__.py b/src/together/types/__init__.py index 74397022..b84f56be 100644 --- a/src/together/types/__init__.py +++ b/src/together/types/__init__.py @@ -7,6 +7,7 @@ from .file_list import FileList as FileList from .file_type import FileType as FileType from .log_probs import LogProbs as LogProbs +from .video_job import VideoJob as VideoJob from .completion import Completion as Completion from .image_file import ImageFile as ImageFile from .autoscaling import Autoscaling as Autoscaling @@ -45,14 +46,12 @@ 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 from .hardware_list_response import HardwareListResponse as HardwareListResponse from .rerank_create_response import RerankCreateResponse as RerankCreateResponse from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams -from .video_retrieve_response import VideoRetrieveResponse as VideoRetrieveResponse from .completion_create_params import CompletionCreateParams as CompletionCreateParams from .audio_speech_stream_chunk import AudioSpeechStreamChunk as AudioSpeechStreamChunk from .fine_tuning_delete_params import FineTuningDeleteParams as FineTuningDeleteParams diff --git a/src/together/types/video_create_response.py b/src/together/types/video_job.py similarity index 93% rename from src/together/types/video_create_response.py rename to src/together/types/video_job.py index 19c65269..58fab304 100644 --- a/src/together/types/video_create_response.py +++ b/src/together/types/video_job.py @@ -5,7 +5,7 @@ from .._models import BaseModel -__all__ = ["VideoCreateResponse", "Error", "Outputs"] +__all__ = ["VideoJob", "Error", "Outputs"] class Error(BaseModel): @@ -22,7 +22,7 @@ class Outputs(BaseModel): """URL hosting the generated video""" -class VideoCreateResponse(BaseModel): +class VideoJob(BaseModel): id: str """Unique identifier for the video job.""" diff --git a/src/together/types/video_retrieve_response.py b/src/together/types/video_retrieve_response.py deleted file mode 100644 index 96174f66..00000000 --- a/src/together/types/video_retrieve_response.py +++ /dev/null @@ -1,57 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["VideoRetrieveResponse", "Error", "Outputs"] - - -class Error(BaseModel): - message: str - - code: Optional[str] = None - - -class Outputs(BaseModel): - cost: int - """The cost of generated video charged to the owners account.""" - - video_url: str - """URL hosting the generated video""" - - -class VideoRetrieveResponse(BaseModel): - id: str - """Unique identifier for the video job.""" - - created_at: float - """Unix timestamp (seconds) for when the job was created.""" - - model: str - """The video generation model that produced the job.""" - - seconds: str - """Duration of the generated clip in seconds.""" - - size: str - """The resolution of the generated video.""" - - status: Literal["in_progress", "completed", "failed"] - """Current lifecycle status of the video job.""" - - completed_at: Optional[float] = None - """Unix timestamp (seconds) for when the job completed, if finished.""" - - error: Optional[Error] = None - """Error payload that explains why generation failed, if applicable.""" - - object: Optional[Literal["video"]] = None - """The object type, which is always video.""" - - outputs: Optional[Outputs] = None - """ - Available upon completion, the outputs provides the cost charged and the hosted - url to access the video - """ diff --git a/tests/api_resources/test_videos.py b/tests/api_resources/test_videos.py index 907b54d4..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 VideoCreateResponse, VideoRetrieveResponse +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 @@ -78,7 +78,7 @@ def test_method_retrieve(self, client: Together) -> None: video = client.videos.retrieve( "id", ) - assert_matches_type(VideoRetrieveResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Together) -> None: @@ -89,7 +89,7 @@ def test_raw_response_retrieve(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(VideoRetrieveResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Together) -> None: @@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Together) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = response.parse() - assert_matches_type(VideoRetrieveResponse, 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 @@ -178,7 +178,7 @@ async def test_method_retrieve(self, async_client: AsyncTogether) -> None: video = await async_client.videos.retrieve( "id", ) - assert_matches_type(VideoRetrieveResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncTogether) -> None: @@ -189,7 +189,7 @@ async def test_raw_response_retrieve(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(VideoRetrieveResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncTogether) -> None: @@ -200,7 +200,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncTogether) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" video = await response.parse() - assert_matches_type(VideoRetrieveResponse, video, path=["response"]) + assert_matches_type(VideoJob, video, path=["response"]) assert cast(Any, response.is_closed) is True From faea969855a2001dbc05e82c32d63ab11165e6df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:28:56 +0000 Subject: [PATCH 7/7] release: 2.0.0-alpha.8 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ pyproject.toml | 2 +- src/together/_version.py | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) 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/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/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