[Platform][ElevenLabs] Implement speech-to-text functionality with support for additional formats and options#2272
[Platform][ElevenLabs] Implement speech-to-text functionality with support for additional formats and options#2272khaperets wants to merge 2 commits into
Conversation
…additional formats and options
…additional formats and options
chr-hertel
left a comment
There was a problem hiding this comment.
Hi @khaperets - thanks for working on this! 🙏
Added a first round of comments and tested the provided example - worked right away 😎👍
Thanks already for the follow-up patch!
| model: new ElevenLabs('scribe_v2', [ | ||
| Capability::SPEECH_TO_TEXT, | ||
| Capability::INPUT_AUDIO, | ||
| Capability::OUTPUT_TEXT, | ||
| ]), |
There was a problem hiding this comment.
we could just use $platform->invoke('scribe_v2', Audio::fromFile(...), [...])
no need for instantiating the ElevenLabs model class i guess
| $converter->convert($rawResult); | ||
| } | ||
|
|
||
| private function extractTranscription(ResultInterface $result): TranscriptionResult |
There was a problem hiding this comment.
let's go with "Transcript" instead of "Transcription" - in my understanding "Transcription" is what ElevenLabs does and "Transcript" is the result of it
| private function extractTranscription(ResultInterface $result): TranscriptionResult | |
| private function extractTranscript(ResultInterface $result): Transcript |
| * | ||
| * @author Dmytro Khaperets <khaperets@gmail.com> | ||
| */ | ||
| final class TranscriptionResult |
There was a problem hiding this comment.
Please rename to Transcript and drop the Result suffix - it is used quite heavily in the Platform component already in a different meaning
| model: new ElevenLabs('scribe_v2', [ | ||
| Capability::SPEECH_TO_TEXT, | ||
| Capability::INPUT_AUDIO, | ||
| Capability::OUTPUT_TEXT, | ||
| ]), |
There was a problem hiding this comment.
i don't think we need to instantiate the class here, just use the model identifier scribe_v2
| private function stringifySpeechToTextOption(int|string $key, mixed $value): string | ||
| { | ||
| if (\is_array($value)) { | ||
| return json_encode($value, \JSON_THROW_ON_ERROR); |
There was a problem hiding this comment.
please catch that JsonException and do an upcast to Symfony\AI\Platform\Exception\InvalidArgumentException while forwarding the original exception
| /** @var list<array<string, mixed>> $additionalFormats */ | ||
| $additionalFormats = \is_array($data['additional_formats'] ?? null) ? $data['additional_formats'] : []; | ||
|
|
||
| return new MultiPartResult([ | ||
| new TextResult($text), | ||
| new ObjectResult(new TranscriptionResult($text, $additionalFormats)), | ||
| ]); |
There was a problem hiding this comment.
i don't think we should be that ambiguous here, but
- if the user doesn't provide any
additional_formatsoption => returnTextResult - if the user requests
additions_formats=> returnObjectResultwith Transcript object
see the Whisper implementation in OpenAI bridge for reference
| } | ||
|
|
||
| /** | ||
| * @return list<array<string, mixed>> |
There was a problem hiding this comment.
we can type this as class as well, no matter which format is chosen, we always have:
- requested_format: string
- file_extension: string
- content_type: string
- is_base64_encoded: boolean
- content: string
|
btw, PHPStan issue in the demo should be gone after rebase on latest |
[ElevenLabs] Support SRT output for speech-to-text via
additional_formatsThis exposes ElevenLabs Scribe's speech-to-text options and additional
transcript formats (e.g. SRT subtitles) through
PlatformInterface::invoke(),so subtitle generation no longer requires bypassing the bridge and calling
ElevenLabs directly with the HTTP client.
Motivation
The ElevenLabs
/v1/speech-to-textendpoint accepts multipart fields such aslanguage_code,tag_audio_events,num_speakers,diarize,timestamps_granularityandadditional_formats, and returns the requestedexport formats under
additional_formats[].content. Until now the bridge sentonly
fileandmodel_id, andasText()exposed only the plain transcript,so generating SRT required a hand-rolled HttpClient call (see the workaround in
the issue).
Changes
1. Forwarding speech-to-text options —
ElevenLabsClientElevenLabsClient::doSpeechToTextRequest()now receives the merged request +model options (mirroring the text-to-speech branch) and forwards them as
multipart fields. Because Symfony HttpClient builds multipart bodies via
http_build_query, options are normalized to the wire format ElevenLabs expects:additional_formats) are JSON-encoded into a single field;"true"/"false"(not"1"/"");2. First-class result API —
Bridge\ElevenLabs\Result\TranscriptionResultA new bridge-scoped DTO carrying the transcript text alongside the returned
additional_formats. It transparently decodes base64-encoded content andexposes:
getText()getAdditionalFormats()getAdditionalFormat(string $format)— resolved byrequested_format(e.g.
srt,txt,html)asSubRipText()— convenience accessor for SRT3. Converter —
ElevenLabsResultConverterSpeech-to-text responses now return a
MultiPartResultcontaining both aTextResult(soasText()keeps working) and anObjectResultwrapping thenew
TranscriptionResult(soasObject()returns the structured result).This mirrors the pattern used by the Mistral OCR bridge and avoids adding
bridge-specific methods to the core
DeferredResult.Usage
Backward compatibility
$result->asText()is preserved unchanged for speech-to-text, so existingconsumers keep working. The converted result type changes from
TextResulttoMultiPartResult, but the public entry points (asText(),asObject(),getRawResult()) remain consistent:asText()behaves identically, andasObject()now succeeds (returning aTranscriptionResult) where itpreviously threw
UnexpectedResultTypeException. NoUPGRADE.mdentry istherefore required.
Tests
ElevenLabsConverterTestfor the newMultiPartResultshape, withdedicated cases for plain transcripts, additional formats (both raw and
base64-encoded content) and the absence of additional formats.
ElevenLabsClientTest::testClientCanPerformSpeechToTextRequestWithOptionsasserting that the multipart body forwards
language_code,diarize(
true),tag_audio_events(false),num_speakers(1),timestamps_granularityand the JSON-encodedadditional_formats.Documentation
docs/components/platform.rstcovering thebasic transcript and the SRT export flow (validated with
doctor-rst).examples/elevenlabs/speech-to-text-srt.php.CHANGELOG.mdentry under the unreleased0.11section.