Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/together/resources/audio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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:
Expand All @@ -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)
65 changes: 65 additions & 0 deletions src/together/resources/audio/voices.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 4 additions & 0 deletions src/together/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
AudioTranslationVerboseResponse,
AudioTranscriptionResponseFormat,
AudioTimestampGranularities,
ModelVoices,
VoiceListResponse,
)
from together.types.chat_completions import (
ChatCompletionChunk,
Expand Down Expand Up @@ -140,6 +142,8 @@
"AudioTranslationVerboseResponse",
"AudioTranscriptionResponseFormat",
"AudioTimestampGranularities",
"ModelVoices",
"VoiceListResponse",
"DedicatedEndpoint",
"ListEndpoint",
"Autoscaling",
Expand Down
16 changes: 15 additions & 1 deletion src/together/types/audio_speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]
122 changes: 122 additions & 0 deletions tests/integration/resources/test_voices.py
Original file line number Diff line number Diff line change
@@ -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