Skip to content
Open
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
16 changes: 12 additions & 4 deletions src/groq/resources/audio/transcriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
__all__ = ["Transcriptions", "AsyncTranscriptions"]


def _get_response_format_type(
response_format: Literal["json", "text", "verbose_json"] | Omit,
) -> type[Transcription] | type[str]:
# `text` responses are served as text/plain, so casting to a model makes the parser
# fall back to json.loads() and return whatever type the transcript happens to parse as.
return str if response_format == "text" else Transcription


class Transcriptions(SyncAPIResource):
@cached_property
def with_raw_response(self) -> TranscriptionsWithRawResponse:
Expand Down Expand Up @@ -167,7 +175,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Transcription:
) -> Transcription | str:
"""
Transcribes audio into the input language.

Expand Down Expand Up @@ -239,7 +247,7 @@ def create(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Transcription,
cast_to=_get_response_format_type(response_format),
)


Expand Down Expand Up @@ -385,7 +393,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Transcription:
) -> Transcription | str:
"""
Transcribes audio into the input language.

Expand Down Expand Up @@ -457,7 +465,7 @@ async def create(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Transcription,
cast_to=_get_response_format_type(response_format),
)


Expand Down
16 changes: 12 additions & 4 deletions src/groq/resources/audio/translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
__all__ = ["Translations", "AsyncTranslations"]


def _get_response_format_type(
response_format: Literal["json", "text", "verbose_json"] | Omit,
) -> type[Translation] | type[str]:
# `text` responses are served as text/plain, so casting to a model makes the parser
# fall back to json.loads() and return whatever type the transcript happens to parse as.
return str if response_format == "text" else Translation


class Translations(SyncAPIResource):
@cached_property
def with_raw_response(self) -> TranslationsWithRawResponse:
Expand Down Expand Up @@ -60,7 +68,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Translation:
) -> Translation | str:
"""Translates audio into English.

Args:
Expand Down Expand Up @@ -119,7 +127,7 @@ def create(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Translation,
cast_to=_get_response_format_type(response_format),
)


Expand Down Expand Up @@ -158,7 +166,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Translation:
) -> Translation | str:
"""Translates audio into English.

Args:
Expand Down Expand Up @@ -217,7 +225,7 @@ async def create(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Translation,
cast_to=_get_response_format_type(response_format),
)


Expand Down
91 changes: 91 additions & 0 deletions tests/test_audio_response_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from __future__ import annotations

import os

import httpx
import pytest
from respx import MockRouter

from groq import Groq, AsyncGroq
from groq.types.audio import Translation, Transcription

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")

# A transcript is arbitrary user speech, so it may happen to be valid JSON.
TEXT_BODIES = ["Hello there.", "42", "true", "null", "3.5", "[1, 2]", '{"text": "hi"}']


def _text_response(body: str) -> httpx.Response:
return httpx.Response(200, headers={"Content-Type": "text/plain; charset=utf-8"}, content=body)


class TestTranscriptions:
@pytest.mark.parametrize("body", TEXT_BODIES)
@pytest.mark.respx(base_url=base_url)
def test_response_format_text_returns_str(self, body: str, respx_mock: MockRouter, client: Groq) -> None:
respx_mock.post("/openai/v1/audio/transcriptions").mock(return_value=_text_response(body))

transcription = client.audio.transcriptions.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="text"
)
assert transcription == body

@pytest.mark.respx(base_url=base_url)
def test_response_format_json_returns_model(self, respx_mock: MockRouter, client: Groq) -> None:
respx_mock.post("/openai/v1/audio/transcriptions").mock(
return_value=httpx.Response(200, json={"text": "Hello there."})
)

transcription = client.audio.transcriptions.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="json"
)
assert isinstance(transcription, Transcription)
assert transcription.text == "Hello there."

@pytest.mark.parametrize("body", TEXT_BODIES)
@pytest.mark.respx(base_url=base_url)
async def test_async_response_format_text_returns_str(
self, body: str, respx_mock: MockRouter, async_client: AsyncGroq
) -> None:
respx_mock.post("/openai/v1/audio/transcriptions").mock(return_value=_text_response(body))

transcription = await async_client.audio.transcriptions.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="text"
)
assert transcription == body


class TestTranslations:
@pytest.mark.parametrize("body", TEXT_BODIES)
@pytest.mark.respx(base_url=base_url)
def test_response_format_text_returns_str(self, body: str, respx_mock: MockRouter, client: Groq) -> None:
respx_mock.post("/openai/v1/audio/translations").mock(return_value=_text_response(body))

translation = client.audio.translations.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="text"
)
assert translation == body

@pytest.mark.respx(base_url=base_url)
def test_response_format_json_returns_model(self, respx_mock: MockRouter, client: Groq) -> None:
respx_mock.post("/openai/v1/audio/translations").mock(
return_value=httpx.Response(200, json={"text": "Hello there."})
)

translation = client.audio.translations.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="json"
)
assert isinstance(translation, Translation)
assert translation.text == "Hello there."

@pytest.mark.parametrize("body", TEXT_BODIES)
@pytest.mark.respx(base_url=base_url)
async def test_async_response_format_text_returns_str(
self, body: str, respx_mock: MockRouter, async_client: AsyncGroq
) -> None:
respx_mock.post("/openai/v1/audio/translations").mock(return_value=_text_response(body))

translation = await async_client.audio.translations.create(
model="whisper-large-v3", file=("audio.wav", b"RIFF"), response_format="text"
)
assert translation == body