Skip to content

Commit ed7800f

Browse files
njbrakeclaude
andcommitted
refactor: return a structured TranscriptionResult from transcription
Replace the loosely typed Any return (a dict or a str depending on the response format) with a TranscriptionResult dataclass exposing two fields: json (set for json / verbose_json formats) and text (set for text / srt / vtt formats). This gives the transcription method a single explicit return type and keeps it uniform with the other otari SDKs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 483c1dc commit ed7800f

7 files changed

Lines changed: 46 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ result = client.transcription(
263263
model="openai:whisper-1",
264264
file=Path("speech.mp3").read_bytes(),
265265
)
266-
print(result["text"])
266+
print(result.json["text"])
267267
```
268268

269269
### Batch operations

src/otari/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
ModerationResponse,
5252
OtariClientOptions,
5353
RerankResponse,
54+
TranscriptionResult,
5455
)
5556

5657
try:
@@ -84,6 +85,7 @@
8485
"OtariError",
8586
"RateLimitError",
8687
"RerankResponse",
88+
"TranscriptionResult",
8789
"UnsupportedCapabilityError",
8890
"UpstreamProviderError",
8991
]

src/otari/async_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
BatchResult,
7070
CreateBatchParams,
7171
ListBatchesOptions,
72+
TranscriptionResult,
7273
)
7374

7475

@@ -367,11 +368,12 @@ async def transcription(
367368
file: bytes,
368369
filename: str = "audio",
369370
**kwargs: Any,
370-
) -> Any:
371+
) -> TranscriptionResult:
371372
"""Transcribe audio to text.
372373
373374
``file`` is the raw audio bytes uploaded as multipart form data. Returns
374-
the parsed JSON (a dict) for JSON response formats, or the raw text for
375+
a :class:`~otari.types.TranscriptionResult` whose ``json`` field is set
376+
for JSON response formats and whose ``text`` field is set for the
375377
``text`` / ``srt`` / ``vtt`` formats.
376378
377379
Args:
@@ -382,12 +384,14 @@ async def transcription(
382384
**kwargs: Additional parameters (``language``, ``prompt``,
383385
``response_format``, ``temperature``, ``user``).
384386
"""
387+
from otari.types import TranscriptionResult # noqa: PLC0415
388+
385389
data = {"model": model, **{key: str(value) for key, value in kwargs.items()}}
386390
files = {"file": (filename, file)}
387391
response = await self._post("/audio/transcriptions", data=data, files=files)
388392
if "application/json" in response.headers.get("content-type", ""):
389-
return response.json()
390-
return response.text
393+
return TranscriptionResult(json=response.json())
394+
return TranscriptionResult(text=response.text)
391395

392396
# -- Models -------------------------------------------------------------
393397

src/otari/client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
BatchResult,
7070
CreateBatchParams,
7171
ListBatchesOptions,
72+
TranscriptionResult,
7273
)
7374

7475

@@ -391,11 +392,12 @@ def transcription(
391392
file: bytes,
392393
filename: str = "audio",
393394
**kwargs: Any,
394-
) -> Any:
395+
) -> TranscriptionResult:
395396
"""Transcribe audio to text.
396397
397398
``file`` is the raw audio bytes uploaded as multipart form data. Returns
398-
the parsed JSON (a dict) for JSON response formats, or the raw text for
399+
a :class:`~otari.types.TranscriptionResult` whose ``json`` field is set
400+
for JSON response formats and whose ``text`` field is set for the
399401
``text`` / ``srt`` / ``vtt`` formats.
400402
401403
Args:
@@ -406,12 +408,14 @@ def transcription(
406408
**kwargs: Additional parameters (``language``, ``prompt``,
407409
``response_format``, ``temperature``, ``user``).
408410
"""
411+
from otari.types import TranscriptionResult # noqa: PLC0415
412+
409413
data = {"model": model, **{key: str(value) for key, value in kwargs.items()}}
410414
files = {"file": (filename, file)}
411415
response = self._post("/audio/transcriptions", data=data, files=files)
412416
if "application/json" in response.headers.get("content-type", ""):
413-
return response.json()
414-
return response.text
417+
return TranscriptionResult(json=response.json())
418+
return TranscriptionResult(text=response.text)
415419

416420
# -- Models -------------------------------------------------------------
417421

src/otari/types.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,24 @@ class BatchResult:
109109
"""Aggregated results of a completed batch job."""
110110

111111
results: list[BatchResultItem] = field(default_factory=list)
112+
113+
114+
# ---------------------------------------------------------------------------
115+
# Audio types
116+
# ---------------------------------------------------------------------------
117+
118+
119+
@dataclass
120+
class TranscriptionResult:
121+
"""Result of an audio transcription request.
122+
123+
Exactly one field is populated, chosen by the gateway response's content
124+
type: ``json`` for the default ``json`` / ``verbose_json`` formats, ``text``
125+
for the plain ``text`` / ``srt`` / ``vtt`` formats.
126+
"""
127+
128+
json: dict[str, Any] | None = None
129+
"""Parsed JSON response, for ``json`` / ``verbose_json`` formats."""
130+
131+
text: str | None = None
132+
"""Raw text response, for ``text`` / ``srt`` / ``vtt`` formats."""

tests/unit/test_async_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ async def test_transcription_returns_json(self) -> None:
208208
)
209209
client = AsyncOtariClient(api_base="http://localhost:8000", api_key="vk")
210210
result = await client.transcription(model="openai:whisper-1", file=b"\x00\x01")
211-
assert result == TRANSCRIPTION_RESPONSE
211+
assert result.json == TRANSCRIPTION_RESPONSE
212+
assert result.text is None
212213
request = route.calls.last.request
213214
assert request.headers["content-type"].startswith("multipart/form-data")
214215
assert b'name="file"' in request.content

tests/unit/test_client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ def test_transcription_returns_json(self) -> None:
433433
)
434434
client = OtariClient(api_base="http://localhost:8000", api_key="vk")
435435
result = client.transcription(model="openai:whisper-1", file=b"\x00\x01")
436-
assert result == TRANSCRIPTION_RESPONSE
436+
assert result.json == TRANSCRIPTION_RESPONSE
437+
assert result.text is None
437438
request = route.calls.last.request
438439
assert request.headers["content-type"].startswith("multipart/form-data")
439440
assert b'name="model"' in request.content
@@ -450,7 +451,8 @@ def test_transcription_returns_text(self) -> None:
450451
result = client.transcription(
451452
model="m", file=b"\x00", response_format="text"
452453
)
453-
assert result == "hello"
454+
assert result.text == "hello"
455+
assert result.json is None
454456

455457

456458
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)