together 1.5.29__py3-none-any.whl → 1.5.31__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.
@@ -13,13 +13,18 @@ class Endpoints:
13
13
  self._client = client
14
14
 
15
15
  def list(
16
- self, type: Optional[Literal["dedicated", "serverless"]] = None
16
+ self,
17
+ type: Optional[Literal["dedicated", "serverless"]] = None,
18
+ usage_type: Optional[Literal["on-demand", "reserved"]] = None,
19
+ mine: Optional[bool] = None,
17
20
  ) -> List[ListEndpoint]:
18
21
  """
19
- List all endpoints, can be filtered by type.
22
+ List all endpoints, can be filtered by endpoint type and ownership.
20
23
 
21
24
  Args:
22
- type (str, optional): Filter endpoints by type ("dedicated" or "serverless"). Defaults to None.
25
+ type (str, optional): Filter endpoints by endpoint type ("dedicated" or "serverless"). Defaults to None.
26
+ usage_type (str, optional): Filter endpoints by usage type ("on-demand" or "reserved"). Defaults to None.
27
+ mine (bool, optional): If True, return only endpoints owned by the caller. Defaults to None.
23
28
 
24
29
  Returns:
25
30
  List[ListEndpoint]: List of endpoint objects
@@ -28,9 +33,20 @@ class Endpoints:
28
33
  client=self._client,
29
34
  )
30
35
 
31
- params = {}
36
+ params: Dict[
37
+ str,
38
+ Union[
39
+ Literal["dedicated", "serverless"],
40
+ Literal["on-demand", "reserved"],
41
+ bool,
42
+ ],
43
+ ] = {}
32
44
  if type is not None:
33
45
  params["type"] = type
46
+ if usage_type is not None:
47
+ params["usage_type"] = usage_type
48
+ if mine is not None:
49
+ params["mine"] = mine
34
50
 
35
51
  response, _, _ = requestor.request(
36
52
  options=TogetherRequest(
@@ -60,6 +76,7 @@ class Endpoints:
60
76
  disable_speculative_decoding: bool = True,
61
77
  state: Literal["STARTED", "STOPPED"] = "STARTED",
62
78
  inactive_timeout: Optional[int] = None,
79
+ availability_zone: Optional[str] = None,
63
80
  ) -> DedicatedEndpoint:
64
81
  """
65
82
  Create a new dedicated endpoint.
@@ -74,6 +91,7 @@ class Endpoints:
74
91
  disable_speculative_decoding (bool, optional): Whether to disable speculative decoding. Defaults to False.
75
92
  state (str, optional): The desired state of the endpoint. Defaults to "STARTED".
76
93
  inactive_timeout (int, optional): The number of minutes of inactivity after which the endpoint will be automatically stopped. Set to 0 to disable automatic timeout.
94
+ availability_zone (str, optional): Start endpoint in specified availability zone (e.g., us-central-4b).
77
95
 
78
96
  Returns:
79
97
  DedicatedEndpoint: Object containing endpoint information
@@ -100,6 +118,9 @@ class Endpoints:
100
118
  if inactive_timeout is not None:
101
119
  data["inactive_timeout"] = inactive_timeout
102
120
 
121
+ if availability_zone is not None:
122
+ data["availability_zone"] = availability_zone
123
+
103
124
  response, _, _ = requestor.request(
104
125
  options=TogetherRequest(
105
126
  method="POST",
@@ -257,19 +278,49 @@ class Endpoints:
257
278
 
258
279
  return [HardwareWithStatus(**item) for item in response.data["data"]]
259
280
 
281
+ def list_avzones(self) -> List[str]:
282
+ """
283
+ List all available availability zones.
284
+
285
+ Returns:
286
+ List[str]: List of unique availability zones
287
+ """
288
+ requestor = api_requestor.APIRequestor(
289
+ client=self._client,
290
+ )
291
+
292
+ response, _, _ = requestor.request(
293
+ options=TogetherRequest(
294
+ method="GET",
295
+ url="clusters/availability-zones",
296
+ ),
297
+ stream=False,
298
+ )
299
+
300
+ assert isinstance(response, TogetherResponse)
301
+ assert isinstance(response.data, dict)
302
+ assert isinstance(response.data["avzones"], list)
303
+
304
+ return response.data["avzones"]
305
+
260
306
 
261
307
  class AsyncEndpoints:
262
308
  def __init__(self, client: TogetherClient) -> None:
263
309
  self._client = client
264
310
 
265
311
  async def list(
266
- self, type: Optional[Literal["dedicated", "serverless"]] = None
312
+ self,
313
+ type: Optional[Literal["dedicated", "serverless"]] = None,
314
+ usage_type: Optional[Literal["on-demand", "reserved"]] = None,
315
+ mine: Optional[bool] = None,
267
316
  ) -> List[ListEndpoint]:
268
317
  """
269
- List all endpoints, can be filtered by type.
318
+ List all endpoints, can be filtered by type and ownership.
270
319
 
271
320
  Args:
272
321
  type (str, optional): Filter endpoints by type ("dedicated" or "serverless"). Defaults to None.
322
+ usage_type (str, optional): Filter endpoints by usage type ("on-demand" or "reserved"). Defaults to None.
323
+ mine (bool, optional): If True, return only endpoints owned by the caller. Defaults to None.
273
324
 
274
325
  Returns:
275
326
  List[ListEndpoint]: List of endpoint objects
@@ -278,9 +329,20 @@ class AsyncEndpoints:
278
329
  client=self._client,
279
330
  )
280
331
 
281
- params = {}
332
+ params: Dict[
333
+ str,
334
+ Union[
335
+ Literal["dedicated", "serverless"],
336
+ Literal["on-demand", "reserved"],
337
+ bool,
338
+ ],
339
+ ] = {}
282
340
  if type is not None:
283
341
  params["type"] = type
342
+ if usage_type is not None:
343
+ params["usage_type"] = usage_type
344
+ if mine is not None:
345
+ params["mine"] = mine
284
346
 
285
347
  response, _, _ = await requestor.arequest(
286
348
  options=TogetherRequest(
@@ -308,6 +370,7 @@ class AsyncEndpoints:
308
370
  disable_speculative_decoding: bool = True,
309
371
  state: Literal["STARTED", "STOPPED"] = "STARTED",
310
372
  inactive_timeout: Optional[int] = None,
373
+ availability_zone: Optional[str] = None,
311
374
  ) -> DedicatedEndpoint:
312
375
  """
313
376
  Create a new dedicated endpoint.
@@ -348,6 +411,9 @@ class AsyncEndpoints:
348
411
  if inactive_timeout is not None:
349
412
  data["inactive_timeout"] = inactive_timeout
350
413
 
414
+ if availability_zone is not None:
415
+ data["availability_zone"] = availability_zone
416
+
351
417
  response, _, _ = await requestor.arequest(
352
418
  options=TogetherRequest(
353
419
  method="POST",
@@ -506,3 +572,28 @@ class AsyncEndpoints:
506
572
  assert isinstance(response.data["data"], list)
507
573
 
508
574
  return [HardwareWithStatus(**item) for item in response.data["data"]]
575
+
576
+ async def list_avzones(self) -> List[str]:
577
+ """
578
+ List all availability zones.
579
+
580
+ Returns:
581
+ List[str]: List of unique availability zones
582
+ """
583
+ requestor = api_requestor.APIRequestor(
584
+ client=self._client,
585
+ )
586
+
587
+ response, _, _ = await requestor.arequest(
588
+ options=TogetherRequest(
589
+ method="GET",
590
+ url="clusters/availability-zones",
591
+ ),
592
+ stream=False,
593
+ )
594
+
595
+ assert isinstance(response, TogetherResponse)
596
+ assert isinstance(response.data, dict)
597
+ assert isinstance(response.data["avzones"], list)
598
+
599
+ return response.data["avzones"]
@@ -15,6 +15,8 @@ from together.types.audio_speech import (
15
15
  AudioTranslationVerboseResponse,
16
16
  AudioTranscriptionResponseFormat,
17
17
  AudioTimestampGranularities,
18
+ ModelVoices,
19
+ VoiceListResponse,
18
20
  )
19
21
  from together.types.chat_completions import (
20
22
  ChatCompletionChunk,
@@ -140,6 +142,8 @@ __all__ = [
140
142
  "AudioTranslationVerboseResponse",
141
143
  "AudioTranscriptionResponseFormat",
142
144
  "AudioTimestampGranularities",
145
+ "ModelVoices",
146
+ "VoiceListResponse",
143
147
  "DedicatedEndpoint",
144
148
  "ListEndpoint",
145
149
  "Autoscaling",
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import base64
4
4
  from enum import Enum
5
- from typing import BinaryIO, Iterator, List, Optional, Union
5
+ from re import S
6
+ from typing import BinaryIO, Dict, Iterator, List, Optional, Union
6
7
 
7
8
  from pydantic import BaseModel, ConfigDict
8
9
 
@@ -82,27 +83,126 @@ class AudioSpeechStreamResponse(BaseModel):
82
83
 
83
84
  model_config = ConfigDict(arbitrary_types_allowed=True)
84
85
 
85
- def stream_to_file(self, file_path: str) -> None:
86
+ def stream_to_file(
87
+ self, file_path: str, response_format: AudioResponseFormat | str | None = None
88
+ ) -> None:
89
+ """
90
+ Save the audio response to a file.
91
+
92
+ For non-streaming responses, writes the complete file as received.
93
+ For streaming responses, collects binary chunks and constructs a valid
94
+ file format based on the response_format parameter.
95
+
96
+ Args:
97
+ file_path: Path where the audio file should be saved.
98
+ response_format: Format of the audio (wav, mp3, or raw). If not provided,
99
+ will attempt to infer from file extension or default to wav.
100
+ """
101
+ # Determine response format
102
+ if response_format is None:
103
+ # Infer from file extension
104
+ ext = file_path.lower().split(".")[-1] if "." in file_path else ""
105
+ if ext in ["wav"]:
106
+ response_format = AudioResponseFormat.WAV
107
+ elif ext in ["mp3", "mpeg"]:
108
+ response_format = AudioResponseFormat.MP3
109
+ elif ext in ["raw", "pcm"]:
110
+ response_format = AudioResponseFormat.RAW
111
+ else:
112
+ # Default to WAV if unknown
113
+ response_format = AudioResponseFormat.WAV
114
+
115
+ if isinstance(response_format, str):
116
+ response_format = AudioResponseFormat(response_format)
117
+
86
118
  if isinstance(self.response, TogetherResponse):
87
- # save response to file
119
+ # Non-streaming: save complete file
88
120
  with open(file_path, "wb") as f:
89
121
  f.write(self.response.data)
90
122
 
91
123
  elif isinstance(self.response, Iterator):
124
+ # Streaming: collect binary chunks
125
+ audio_chunks = []
126
+ for chunk in self.response:
127
+ if isinstance(chunk.data, bytes):
128
+ audio_chunks.append(chunk.data)
129
+ elif isinstance(chunk.data, dict):
130
+ # SSE format with JSON/base64
131
+ try:
132
+ stream_event = AudioSpeechStreamEventResponse(
133
+ response={"data": chunk.data}
134
+ )
135
+ if isinstance(stream_event.response, StreamSentinel):
136
+ break
137
+ audio_chunks.append(
138
+ base64.b64decode(stream_event.response.data.b64)
139
+ )
140
+ except Exception:
141
+ continue # Skip malformed chunks
142
+
143
+ if not audio_chunks:
144
+ raise ValueError("No audio data received in streaming response")
145
+
146
+ # Concatenate all chunks
147
+ audio_data = b"".join(audio_chunks)
148
+
92
149
  with open(file_path, "wb") as f:
93
- for chunk in self.response:
94
- # Try to parse as stream chunk
95
- stream_event_response = AudioSpeechStreamEventResponse(
96
- response={"data": chunk.data}
150
+ if response_format == AudioResponseFormat.WAV:
151
+ if audio_data.startswith(b"RIFF"):
152
+ # Already a valid WAV file
153
+ f.write(audio_data)
154
+ else:
155
+ # Raw PCM - add WAV header
156
+ self._write_wav_header(f, audio_data)
157
+ elif response_format == AudioResponseFormat.MP3:
158
+ # MP3 format: Check if data is actually MP3 or raw PCM
159
+ # MP3 files start with ID3 tag or sync word (0xFF 0xFB/0xFA/0xF3/0xF2)
160
+ is_mp3 = audio_data.startswith(b"ID3") or (
161
+ len(audio_data) > 0
162
+ and audio_data[0:1] == b"\xff"
163
+ and len(audio_data) > 1
164
+ and audio_data[1] & 0xE0 == 0xE0
97
165
  )
98
166
 
99
- if isinstance(stream_event_response.response, StreamSentinel):
100
- break
101
-
102
- # decode base64
103
- audio = base64.b64decode(stream_event_response.response.data.b64)
104
-
105
- f.write(audio)
167
+ if is_mp3:
168
+ f.write(audio_data)
169
+ else:
170
+ raise ValueError("Invalid MP3 data received.")
171
+ else:
172
+ # RAW format: write PCM data as-is
173
+ f.write(audio_data)
174
+
175
+ @staticmethod
176
+ def _write_wav_header(file_handle: BinaryIO, audio_data: bytes) -> None:
177
+ """
178
+ Write WAV file header for raw PCM audio data.
179
+
180
+ Uses default TTS parameters: 16-bit PCM, mono, 24000 Hz sample rate.
181
+ """
182
+ import struct
183
+
184
+ sample_rate = 24000
185
+ num_channels = 1
186
+ bits_per_sample = 16
187
+ byte_rate = sample_rate * num_channels * bits_per_sample // 8
188
+ block_align = num_channels * bits_per_sample // 8
189
+ data_size = len(audio_data)
190
+
191
+ # Write WAV header
192
+ file_handle.write(b"RIFF")
193
+ file_handle.write(struct.pack("<I", 36 + data_size)) # File size - 8
194
+ file_handle.write(b"WAVE")
195
+ file_handle.write(b"fmt ")
196
+ file_handle.write(struct.pack("<I", 16)) # fmt chunk size
197
+ file_handle.write(struct.pack("<H", 1)) # Audio format (1 = PCM)
198
+ file_handle.write(struct.pack("<H", num_channels))
199
+ file_handle.write(struct.pack("<I", sample_rate))
200
+ file_handle.write(struct.pack("<I", byte_rate))
201
+ file_handle.write(struct.pack("<H", block_align))
202
+ file_handle.write(struct.pack("<H", bits_per_sample))
203
+ file_handle.write(b"data")
204
+ file_handle.write(struct.pack("<I", data_size))
205
+ file_handle.write(audio_data)
106
206
 
107
207
 
108
208
  class AudioTranscriptionResponseFormat(str, Enum):
@@ -196,3 +296,16 @@ class AudioTranslationVerboseResponse(BaseModel):
196
296
  text: str
197
297
  segments: Optional[List[AudioTranscriptionSegment]] = None
198
298
  words: Optional[List[AudioTranscriptionWord]] = None
299
+
300
+
301
+ class ModelVoices(BaseModel):
302
+ """Represents a model with its available voices."""
303
+
304
+ model: str
305
+ voices: List[Dict[str, str]] # Each voice is a dict with 'name' key
306
+
307
+
308
+ class VoiceListResponse(BaseModel):
309
+ """Response containing a list of models and their available voices."""
310
+
311
+ data: List[ModelVoices]
@@ -46,6 +46,7 @@ class ChatCompletionMessageContentType(str, Enum):
46
46
  TEXT = "text"
47
47
  IMAGE_URL = "image_url"
48
48
  VIDEO_URL = "video_url"
49
+ AUDIO_URL = "audio_url"
49
50
 
50
51
 
51
52
  class ChatCompletionMessageContentImageURL(BaseModel):
@@ -56,11 +57,16 @@ class ChatCompletionMessageContentVideoURL(BaseModel):
56
57
  url: str
57
58
 
58
59
 
60
+ class ChatCompletionMessageContentAudioURL(BaseModel):
61
+ url: str
62
+
63
+
59
64
  class ChatCompletionMessageContent(BaseModel):
60
65
  type: ChatCompletionMessageContentType
61
66
  text: str | None = None
62
67
  image_url: ChatCompletionMessageContentImageURL | None = None
63
68
  video_url: ChatCompletionMessageContentVideoURL | None = None
69
+ audio_url: ChatCompletionMessageContentAudioURL | None = None
64
70
 
65
71
 
66
72
  class ChatCompletionMessage(BaseModel):
together/types/common.py CHANGED
@@ -26,6 +26,7 @@ class UsageData(BaseModel):
26
26
 
27
27
 
28
28
  class ObjectType(str, Enum):
29
+ TextCompletion = "text_completion"
29
30
  Completion = "text.completion"
30
31
  CompletionChunk = "completion.chunk"
31
32
  ChatCompletion = "chat.completion"
together/types/files.py CHANGED
@@ -15,6 +15,7 @@ class FilePurpose(str, Enum):
15
15
  FineTune = "fine-tune"
16
16
  BatchAPI = "batch-api"
17
17
  Eval = "eval"
18
+ EvalOutput = "eval-output"
18
19
 
19
20
 
20
21
  class FileType(str, Enum):