Skip to content

feat(audio): add speech, transcriptions and translations - #11

Merged
theQuert merged 2 commits into
mainfrom
claude/audio-resource
Oct 7, 2026
Merged

theQuert merged 2 commits into
mainfrom
claude/audio-resource

Conversation

@theQuert

@theQuert theQuert commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Closes #5.

Adds client.audio (sync and async) for the gateway's /v1/audio/speech, /v1/audio/transcriptions and /v1/audio/translations. The spec is in openspec/changes/add-audio-resource/.

API

  • audio.speech.create(model, input, voice, instructions=, response_format=, speed=, extra_body=)
    • Sends a JSON body.
    • Returns BinaryResponseContent, which has .content (bytes) and .write_to_file(path).
  • audio.transcriptions.create(file, model, language=, prompt=, response_format=, temperature=, timestamp_granularities=, extra_body=)
  • audio.translations.create(file, model, prompt=, response_format=, temperature=, extra_body=)
    • Both upload multipart/form-data. timestamp_granularities is sent as repeated timestamp_granularities[] fields.
    • file can be a path, an open binary file, bytes, or a (filename, content[, content_type]) tuple. The filename is sent because the API uses its extension to detect the format.
    • For json and verbose_json they return Transcription or Translation, with text. Extra verbose fields such as segments and language are kept.
    • For text, srt and vtt they return a str.

HTTP client (_base_client.request)

  • Accepts data and files.
  • With files, it drops any Content-Type header, whatever its casing, so httpx can set the multipart boundary.
  • Form values: booleans are sent as true/false, and objects as JSON.
  • The file is read into bytes once, before the retry loop, so a retry resends the same bytes. Without this, a file object would be at EOF on the second attempt.
  • JSON requests are unchanged.

Proof

  • tests/test_audio.py covers:
    • speech request body, returned bytes and write_to_file
    • async speech
    • transcription from a path: filename, form fields, repeated granularities, and extra fields kept
    • a 503-then-200 retry that resends the file bytes
    • srt returning text from a (filename, bytes) upload
    • async transcription from bytes
    • translation, sync and async text
  • Before the change, the module failed to import.
  • Full suite, rebased on current main: 122 passed on each of:
    • Python 3.13
    • Python 3.9
    • Python 3.9 with the minimum declared dependencies (pydantic 2.0.3, httpx 0.25.0, typing-extensions 4.7.1)
  • mypy reports the same 12 findings as main.
  • New ruff findings are UP0xx style hints only. They match the repo's Optional/Dict style.
  • openspec validate add-audio-resource --strict passes.
  • Parameters follow openai-python 3.26's audio resources. All tests are respx mocks; no live calls were made.

Review: an independent read-only review found no blockers. Its findings are fixed in the second commit, each with a test:

  • A lowercase content-type set in default_headers forced JSON on multipart uploads. (P2)
  • Booleans and objects were sent as Python repr. (P2)
  • fdopen files failed, and 3-tuples were rejected. (P3)

Not included: streamed speech (stream_format="sse") and streamed transcriptions. They can be added later if needed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uy5vXsRpMhug7v3XwTMZHv

theQuert and others added 2 commits October 7, 2026 08:31
The gateway serves /v1/audio/speech, /v1/audio/transcriptions and
/v1/audio/translations, but the SDK had no audio resource.

- client.audio.speech.create() posts JSON and returns BinaryResponseContent
  (.content, .write_to_file()).
- client.audio.transcriptions.create() and translations.create() upload the
  file as multipart form data. They accept a path, an open binary file,
  bytes, or a (filename, bytes) tuple. They return Transcription /
  Translation for json and verbose_json, keeping extra fields, and the
  response text for text, srt and vtt.
- The HTTP client accepts data and files. It drops the JSON content type so
  httpx can set the multipart boundary. The file is read once before the
  retry loop, so a retry resends the same bytes.

Closes #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy5vXsRpMhug7v3XwTMZHv
- Drop any casing of a default Content-Type header when sending files, so a
  lowercase content-type in default_headers no longer forces JSON.
- Encode booleans as true / false and objects as JSON in form fields;
  only name[] keys are sent as repeated fields.
- Accept a (filename, content, content_type) tuple, and fall back to
  "upload" when a file object's name is not a string (for example fdopen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy5vXsRpMhug7v3XwTMZHv
@theQuert
theQuert merged commit 3ffc231 into main Oct 7, 2026
3 checks passed
@theQuert theQuert mentioned this pull request Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No audio resource (speech / transcriptions / translations)

1 participant