From 6b99a07b12d0b0141c49521fe674a3d36d158e4d Mon Sep 17 00:00:00 2001 From: Sahil Yadav Date: Mon, 3 Nov 2025 22:33:07 +0530 Subject: [PATCH] Add voices support to the sdk --- src/together/resources/audio/__init__.py | 9 ++ src/together/resources/audio/voices.py | 65 +++++++++++ src/together/types/__init__.py | 4 + src/together/types/audio_speech.py | 16 ++- tests/integration/resources/test_voices.py | 122 +++++++++++++++++++++ 5 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/together/resources/audio/voices.py create mode 100644 tests/integration/resources/test_voices.py diff --git a/src/together/resources/audio/__init__.py b/src/together/resources/audio/__init__.py index 5c8abc4..1c8026b 100644 --- a/src/together/resources/audio/__init__.py +++ b/src/together/resources/audio/__init__.py @@ -3,6 +3,7 @@ from together.resources.audio.speech import AsyncSpeech, Speech from together.resources.audio.transcriptions import AsyncTranscriptions, Transcriptions from together.resources.audio.translations import AsyncTranslations, Translations +from together.resources.audio.voices import AsyncVoices, Voices from together.types import ( TogetherClient, ) @@ -24,6 +25,10 @@ def transcriptions(self) -> Transcriptions: def translations(self) -> Translations: return Translations(self._client) + @cached_property + def voices(self) -> Voices: + return Voices(self._client) + class AsyncAudio: def __init__(self, client: TogetherClient) -> None: @@ -40,3 +45,7 @@ def transcriptions(self) -> AsyncTranscriptions: @cached_property def translations(self) -> AsyncTranslations: return AsyncTranslations(self._client) + + @cached_property + def voices(self) -> AsyncVoices: + return AsyncVoices(self._client) diff --git a/src/together/resources/audio/voices.py b/src/together/resources/audio/voices.py new file mode 100644 index 0000000..7055b0b --- /dev/null +++ b/src/together/resources/audio/voices.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from together.abstract import api_requestor +from together.together_response import TogetherResponse +from together.types import ( + TogetherClient, + TogetherRequest, + VoiceListResponse, +) + + +class Voices: + def __init__(self, client: TogetherClient) -> None: + self._client = client + + def list(self) -> VoiceListResponse: + """ + Method to return list of available voices on the API + + Returns: + VoiceListResponse: Response containing models and their available voices + """ + requestor = api_requestor.APIRequestor( + client=self._client, + ) + + response, _, _ = requestor.request( + options=TogetherRequest( + method="GET", + url="voices", + ), + stream=False, + ) + + assert isinstance(response, TogetherResponse) + + return VoiceListResponse(**response.data) + + +class AsyncVoices: + def __init__(self, client: TogetherClient) -> None: + self._client = client + + async def list(self) -> VoiceListResponse: + """ + Async method to return list of available voices on the API + + Returns: + VoiceListResponse: Response containing models and their available voices + """ + requestor = api_requestor.APIRequestor( + client=self._client, + ) + + response, _, _ = await requestor.arequest( + options=TogetherRequest( + method="GET", + url="voices", + ), + stream=False, + ) + + assert isinstance(response, TogetherResponse) + + return VoiceListResponse(**response.data) diff --git a/src/together/types/__init__.py b/src/together/types/__init__.py index e30fe1b..f4dd737 100644 --- a/src/together/types/__init__.py +++ b/src/together/types/__init__.py @@ -15,6 +15,8 @@ AudioTranslationVerboseResponse, AudioTranscriptionResponseFormat, AudioTimestampGranularities, + ModelVoices, + VoiceListResponse, ) from together.types.chat_completions import ( ChatCompletionChunk, @@ -140,6 +142,8 @@ "AudioTranslationVerboseResponse", "AudioTranscriptionResponseFormat", "AudioTimestampGranularities", + "ModelVoices", + "VoiceListResponse", "DedicatedEndpoint", "ListEndpoint", "Autoscaling", diff --git a/src/together/types/audio_speech.py b/src/together/types/audio_speech.py index 3bb4e00..82636e5 100644 --- a/src/together/types/audio_speech.py +++ b/src/together/types/audio_speech.py @@ -2,7 +2,8 @@ import base64 from enum import Enum -from typing import BinaryIO, Iterator, List, Optional, Union +from re import S +from typing import BinaryIO, Dict, Iterator, List, Optional, Union from pydantic import BaseModel, ConfigDict @@ -295,3 +296,16 @@ class AudioTranslationVerboseResponse(BaseModel): text: str segments: Optional[List[AudioTranscriptionSegment]] = None words: Optional[List[AudioTranscriptionWord]] = None + + +class ModelVoices(BaseModel): + """Represents a model with its available voices.""" + + model: str + voices: List[Dict[str, str]] # Each voice is a dict with 'name' key + + +class VoiceListResponse(BaseModel): + """Response containing a list of models and their available voices.""" + + data: List[ModelVoices] diff --git a/tests/integration/resources/test_voices.py b/tests/integration/resources/test_voices.py new file mode 100644 index 0000000..ba5c3f9 --- /dev/null +++ b/tests/integration/resources/test_voices.py @@ -0,0 +1,122 @@ +import os + +import pytest + +from together.client import AsyncTogether, Together +from together.types.audio_speech import ModelVoices, VoiceListResponse + + +class TestTogetherVoices: + @pytest.fixture + def sync_together_client(self) -> Together: + """ + Initialize sync client with API key from environment + """ + TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY") + return Together(api_key=TOGETHER_API_KEY) + + @pytest.fixture + def async_together_client(self) -> AsyncTogether: + """ + Initialize async client with API key from environment + """ + TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY") + return AsyncTogether(api_key=TOGETHER_API_KEY) + + def test_sync_voices_list(self, sync_together_client): + """ + Test sync voices list endpoint + """ + response = sync_together_client.audio.voices.list() + + # Verify response type + assert isinstance(response, VoiceListResponse) + + # Verify data structure + assert hasattr(response, "data") + assert isinstance(response.data, list) + assert len(response.data) > 0 + + # Verify each model has the correct structure + for model_voices in response.data: + assert isinstance(model_voices, ModelVoices) + assert hasattr(model_voices, "model") + assert isinstance(model_voices.model, str) + assert len(model_voices.model) > 0 + print(model_voices) + assert hasattr(model_voices, "voices") + assert isinstance(model_voices.voices, list) + assert len(model_voices.voices) > 0 + + # Verify each voice has a name + for voice in model_voices.voices: + assert isinstance(voice, dict) + assert "name" in voice + assert isinstance(voice["name"], str) + assert len(voice["name"]) > 0 + + @pytest.mark.asyncio + async def test_async_voices_list(self, async_together_client): + """ + Test async voices list endpoint + """ + response = await async_together_client.audio.voices.list() + + # Verify response type + assert isinstance(response, VoiceListResponse) + + # Verify data structure + assert hasattr(response, "data") + assert isinstance(response.data, list) + assert len(response.data) > 0 + + # Verify each model has the correct structure + for model_voices in response.data: + assert isinstance(model_voices, ModelVoices) + assert hasattr(model_voices, "model") + assert isinstance(model_voices.model, str) + assert len(model_voices.model) > 0 + print(model_voices) + assert hasattr(model_voices, "voices") + assert isinstance(model_voices.voices, list) + assert len(model_voices.voices) > 0 + + # Verify each voice has a name + for voice in model_voices.voices: + assert isinstance(voice, dict) + assert "name" in voice + assert isinstance(voice["name"], str) + assert len(voice["name"]) > 0 + + def test_sync_voices_content(self, sync_together_client): + """ + Test that sync voices list returns expected models + """ + response = sync_together_client.audio.voices.list() + + # Get list of model names + model_names = [model_voices.model for model_voices in response.data] + + # Verify we have at least some known models + assert len(model_names) > 0 + + # Check that each model has at least one voice + for model_voices in response.data: + assert len(model_voices.voices) > 0 + + @pytest.mark.asyncio + async def test_async_voices_content(self, async_together_client): + """ + Test that async voices list returns expected models + """ + response = await async_together_client.audio.voices.list() + + # Get list of model names + model_names = [model_voices.model for model_voices in response.data] + + # Verify we have at least some known models + assert len(model_names) > 0 + + # Check that each model has at least one voice + for model_voices in response.data: + assert len(model_voices.voices) > 0