Skip to content

Commit 000c147

Browse files
committed
ref
1 parent 54a51dd commit 000c147

File tree

9 files changed

+73
-44
lines changed

9 files changed

+73
-44
lines changed

examples/elevenlabs/speech-to-text.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
require_once dirname(__DIR__).'/bootstrap.php';
1717

1818
$platform = PlatformFactory::create(
19-
env('ELEVEN_LABS_URL'),
20-
env('ELEVEN_LABS_API_KEY'),
21-
__DIR__.'/tmp',
22-
http_client()
19+
apiKey: env('ELEVEN_LABS_API_KEY'),
20+
outputPath: __DIR__.'/tmp',
21+
httpClient: http_client()
2322
);
24-
$model = new ElevenLabs(ElevenLabs::SPEECH_TO_TEXT, options: [
25-
'model' => 'scribe_v1',
26-
]);
23+
$model = new ElevenLabs(ElevenLabs::SCRIBE_V1);
2724
$file = Audio::fromFile(dirname(__DIR__, 2).'/fixtures/audio.mp3');
2825

2926
$result = $platform->invoke($model, $file);

examples/elevenlabs/text-to-speech.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616
require_once dirname(__DIR__).'/bootstrap.php';
1717

1818
$platform = PlatformFactory::create(
19-
env('ELEVEN_LABS_URL'),
20-
env('ELEVEN_LABS_API_KEY'),
21-
__DIR__.'/tmp',
22-
http_client(),
19+
apiKey: env('ELEVEN_LABS_API_KEY'),
20+
outputPath: __DIR__.'/tmp',
21+
httpClient: http_client(),
2322
);
2423
$model = new ElevenLabs(options: [
25-
'model' => 'Dslrhjl3ZpzrctukrQSN', // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
24+
'voice' => 'Dslrhjl3ZpzrctukrQSN', // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
2625
]);
2726

2827
$result = $platform->invoke($model, new Text('Hello world'));

src/ai-bundle/doc/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Configuration
9393
platform: 'ai.platform.eleven_labs'
9494
model:
9595
class: 'Symfony\AI\Platform\Bridge\ElevenLabs'
96-
name: !php/const Symfony\AI\Platform\Bridge\ElevenLabs\TEXT_TO_SPEECH
96+
name: !php/const Symfony\AI\Platform\Bridge\ElevenLabs::TEXT_TO_SPEECH
9797
tools: false
9898
store:
9999
# also azure_search, meilisearch, memory, mongodb, pinecone, qdrant and surrealdb are supported as store type

src/ai-bundle/src/AiBundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
216216
->setLazy(true)
217217
->addTag('proxy', ['interface' => PlatformInterface::class])
218218
->setArguments([
219-
$platform['host'],
220219
$platform['api_key'],
221220
$platform['output_path'],
221+
$platform['host'],
222222
new Reference('http_client', ContainerInterface::NULL_ON_INVALID_REFERENCE),
223223
new Reference('ai.platform.contract.default'),
224224
])

src/platform/src/Bridge/ElevenLabs/ElevenLabs.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,28 @@
1818
*/
1919
final class ElevenLabs extends Model
2020
{
21-
public const TEXT_TO_SPEECH = 'text-to-speech';
22-
public const SPEECH_TO_TEXT = 'speech-to-text';
21+
public const ELEVEN_V3 = 'eleven_v3';
22+
public const ELEVEN_TTV_V3 = 'eleven_ttv_v3';
23+
public const ELEVEN_MULTILINGUAL_V2 = 'eleven_multilingual_v2';
24+
public const ELEVEN_FLASH_V250 = 'eleven_flash_v2_5';
25+
public const ELEVEN_FLASH_V2 = 'eleven_flashv2';
26+
public const ELEVEN_TURBO_V2_5 = 'eleven_turbo_v2_5';
27+
public const ELEVEN_TURBO_v2 = 'eleven_turbo_v2';
28+
public const ELEVEN_MULTILINGUAL_STS_V2 = 'eleven_multilingual_sts_v2';
29+
public const ELEVEN_MULTILINGUAL_ttv_V2 = 'eleven_multilingual_ttv_v2';
30+
public const ELEVEN_ENGLISH_STS_V2 = 'eleven_english_sts_v2';
31+
public const SCRIBE_V1 = 'scribe_v1';
32+
public const SCRIBE_V1_EXPERIMENTAL = 'scribe_v1_experimental';
2333

34+
/**
35+
* @param array{
36+
* voice: string,
37+
* } $options
38+
*/
2439
public function __construct(
25-
string $name = self::TEXT_TO_SPEECH,
26-
array $capabilities = [],
40+
string $name = self::ELEVEN_MULTILINGUAL_V2,
2741
array $options = [],
2842
) {
29-
parent::__construct($name, $capabilities, $options);
43+
parent::__construct($name, [], $options);
3044
}
3145
}

src/platform/src/Bridge/ElevenLabs/ElevenLabsClient.php

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
{
2626
public function __construct(
2727
private HttpClientInterface $httpClient,
28-
private string $hostUrl,
29-
private string $apiKey,
28+
#[\SensitiveParameter] private string $apiKey,
29+
private string $hostUrl = 'https://api.elevenlabs.io/v1',
3030
) {
3131
}
3232

@@ -37,11 +37,17 @@ public function supports(Model $model): bool
3737

3838
public function request(Model $model, array|string $payload, array $options = []): RawResultInterface
3939
{
40-
return match ($model->getName()) {
41-
ElevenLabs::SPEECH_TO_TEXT => $this->doSpeechToTextRequest($model, $payload, $options),
42-
ElevenLabs::TEXT_TO_SPEECH => $this->doTextToSpeechRequest($model, $payload, $options),
43-
default => throw new InvalidArgumentException(\sprintf('The model "%s" is not supported.', $model->getName())),
44-
};
40+
if (\in_array($model->getName(), [ElevenLabs::SCRIBE_V1, ElevenLabs::SCRIBE_V1_EXPERIMENTAL], true)) {
41+
return $this->doSpeechToTextRequest($model, $payload, $options);
42+
}
43+
44+
$capabilities = $this->retrieveCapabilities($model);
45+
46+
if ([] === $capabilities) {
47+
throw new InvalidArgumentException(\sprintf('The model information could not be retrieved from the ElevenLabs API. Your model might not be supported. Try to use another one.'));
48+
}
49+
50+
return $this->doTextToSpeechRequest($model, $payload, $options);
4551
}
4652

4753
/**
@@ -50,23 +56,17 @@ public function request(Model $model, array|string $payload, array $options = []
5056
*/
5157
private function doSpeechToTextRequest(Model $model, array|string $payload, array $options): RawHttpResult
5258
{
53-
if (!\array_key_exists('model', $model->getOptions())) {
54-
throw new InvalidArgumentException('The model option is required.');
55-
}
56-
5759
if (!\is_array($payload)) {
5860
throw new InvalidArgumentException(\sprintf('The payload must be an array, received "%s".', get_debug_type($payload)));
5961
}
6062

61-
$model = $options['model'] ??= $model->getOptions()['model'];
62-
6363
return new RawHttpResult($this->httpClient->request('POST', \sprintf('%s/speech-to-text', $this->hostUrl), [
6464
'headers' => [
6565
'xi-api-key' => $this->apiKey,
6666
],
6767
'body' => [
6868
'file' => fopen($payload['input_audio']['path'], 'r'),
69-
'model_id' => $model,
69+
'model_id' => $model->getName(),
7070
],
7171
]));
7272
}
@@ -77,7 +77,7 @@ private function doSpeechToTextRequest(Model $model, array|string $payload, arra
7777
*/
7878
private function doTextToSpeechRequest(Model $model, array|string $payload, array $options): RawHttpResult
7979
{
80-
if (!\array_key_exists('model', $model->getOptions())) {
80+
if (!\array_key_exists('voice', $model->getOptions())) {
8181
throw new InvalidArgumentException('The model option is required.');
8282
}
8383

@@ -89,16 +89,31 @@ private function doTextToSpeechRequest(Model $model, array|string $payload, arra
8989
throw new InvalidArgumentException('The payload must contain a "text" key.');
9090
}
9191

92-
$model = $options['model'] ??= $model->getOptions()['model'];
92+
$voice = $options['voice'] ??= $model->getOptions()['voice'];
9393

94-
return new RawHttpResult($this->httpClient->request('POST', \sprintf('%s/text-to-speech/%s', $this->hostUrl, $model), [
94+
return new RawHttpResult($this->httpClient->request('POST', \sprintf('%s/text-to-speech/%s', $this->hostUrl, $voice), [
9595
'headers' => [
9696
'xi-api-key' => $this->apiKey,
9797
],
9898
'json' => [
9999
'text' => $payload['text'],
100-
'model_id' => 'eleven_multilingual_v2',
100+
'model_id' => $model->getName(),
101101
],
102102
]));
103103
}
104+
105+
private function retrieveCapabilities(Model $model): array
106+
{
107+
$capabilityResponse = $this->httpClient->request('GET', sprintf('%s/models', $this->hostUrl), [
108+
'headers' => [
109+
'xi-api-key' => $this->apiKey,
110+
],
111+
]);
112+
113+
$models = $capabilityResponse->toArray();
114+
115+
$currentModelConfiguration = array_filter($models, static fn (array $informations): bool => $informations['model_id'] === $model->getName());
116+
117+
return reset($currentModelConfiguration);
118+
}
104119
}

src/platform/src/Bridge/ElevenLabs/PlatformFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@
2222
final readonly class PlatformFactory
2323
{
2424
public static function create(
25-
string $hostUrl,
2625
string $apiKey,
2726
string $outputPath,
27+
string $hostUrl = 'https://api.elevenlabs.io/v1',
2828
?HttpClientInterface $httpClient = null,
2929
?Contract $contract = null,
3030
): Platform {
3131
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
3232

3333
return new Platform(
34-
[new ElevenLabsClient($httpClient, $hostUrl, $apiKey)],
34+
[new ElevenLabsClient($httpClient, $apiKey, $hostUrl)],
3535
[new ElevenLabsResultConverter($outputPath)],
3636
$contract ?? Contract::create(),
3737
);

src/platform/src/Capability.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,8 @@ enum Capability: string
3333

3434
// FUNCTIONALITY
3535
case TOOL_CALLING = 'tool-calling';
36+
37+
// VOICE
38+
case TEXT_TO_SPEECH = 'text-to-speech';
39+
case SPEECH_TO_TEXT = 'speech-to-text';
3640
}

src/platform/tests/Bridge/ElevenLabs/ElevenLabsClientTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,38 +33,38 @@ public function testSupportsModel()
3333
{
3434
$client = new ElevenLabsClient(
3535
new MockHttpClient(),
36-
'https://api.elevenlabs.io/v1',
3736
'my-api-key',
37+
'https://api.elevenlabs.io/v1',
3838
);
3939

4040
$this->assertTrue($client->supports(new ElevenLabs()));
4141
$this->assertFalse($client->supports(new Model('any-model')));
4242
}
4343

44-
public function testClientCannotPerformSpeechToTextRequestWithoutModel()
44+
public function testClientCannotPerformWithInvalidModel()
4545
{
4646
$normalizer = new AudioNormalizer();
4747

4848
$client = new ElevenLabsClient(
4949
new MockHttpClient(),
50-
'https://api.elevenlabs.io/v1',
5150
'my-api-key',
51+
'https://api.elevenlabs.io/v1',
5252
);
5353

5454
$payload = $normalizer->normalize(Audio::fromFile(\dirname(__DIR__, 5).'/fixtures/audio.mp3'));
5555

5656
$this->expectException(InvalidArgumentException::class);
5757
$this->expectExceptionMessage('The model option is required.');
5858
$this->expectExceptionCode(0);
59-
$client->request(new ElevenLabs(ElevenLabs::SPEECH_TO_TEXT), $payload);
59+
$client->request(new ElevenLabs('foo'), $payload);
6060
}
6161

6262
public function testClientCannotPerformSpeechToTextRequestWithInvalidPayload()
6363
{
6464
$client = new ElevenLabsClient(
6565
new MockHttpClient(),
66-
'https://api.elevenlabs.io/v1',
6766
'my-api-key',
67+
'https://api.elevenlabs.io/v1',
6868
);
6969

7070
$this->expectException(InvalidArgumentException::class);

0 commit comments

Comments
 (0)