Skip to content
Merged
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
10 changes: 10 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
UPGRADE FROM 0.4 to 0.5
=======================

Platform
--------

* The `hostUrl` parameter for `ElevenLabsClient` has been removed
* The `host` parameter for `ElevenLabsApiCatalog` has been removed
* The `hostUrl` parameter for `PlatformFactory::create()` in `ElevenLabs` has been renamed to `endpoint`

UPGRADE FROM 0.3 to 0.4
=======================

Expand Down
5 changes: 1 addition & 4 deletions examples/elevenlabs/speech-to-text.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(
apiKey: env('ELEVEN_LABS_API_KEY'),
httpClient: http_client()
);
$platform = PlatformFactory::create(env('ELEVEN_LABS_API_KEY'), httpClient: http_client());

$result = $platform->invoke('scribe_v1', Audio::fromFile(dirname(__DIR__, 2).'/fixtures/audio.mp3'));

Expand Down
5 changes: 1 addition & 4 deletions examples/elevenlabs/text-to-speech-as-stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(
apiKey: env('ELEVEN_LABS_API_KEY'),
httpClient: http_client(),
);
$platform = PlatformFactory::create(env('ELEVEN_LABS_API_KEY'), httpClient: http_client());

$result = $platform->invoke('eleven_multilingual_v2', new Text('The first move is what sets everything in motion.'), [
'voice' => 'Dslrhjl3ZpzrctukrQSN', // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
Expand Down
5 changes: 1 addition & 4 deletions examples/elevenlabs/text-to-speech.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(
apiKey: env('ELEVEN_LABS_API_KEY'),
httpClient: http_client(),
);
$platform = PlatformFactory::create(env('ELEVEN_LABS_API_KEY'), httpClient: http_client());

$result = $platform->invoke('eleven_multilingual_v2', new Text('Hello world'), [
'voice' => 'Dslrhjl3ZpzrctukrQSN', // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
Expand Down
5 changes: 5 additions & 0 deletions src/ai-bundle/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

0.5
---

* [BC BREAK] The `host_url` configuration key for `ElevenLabs` has been renamed `endpoint`

0.4
---

Expand Down
4 changes: 2 additions & 2 deletions src/ai-bundle/config/platform/elevenlabs.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
return (new ArrayNodeDefinition('elevenlabs'))
->children()
->stringNode('api_key')->isRequired()->end()
->stringNode('host')
->defaultValue('https://api.elevenlabs.io/v1')
->stringNode('endpoint')
->defaultValue('https://api.elevenlabs.io/v1/')
->end()
->stringNode('http_client')
->defaultValue('http_client')
Expand Down
5 changes: 4 additions & 1 deletion src/ai-bundle/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use Symfony\AI\Platform\Bridge\Decart\ModelCatalog as DecartModelCatalog;
use Symfony\AI\Platform\Bridge\DeepSeek\ModelCatalog as DeepSeekModelCatalog;
use Symfony\AI\Platform\Bridge\DockerModelRunner\ModelCatalog as DockerModelRunnerModelCatalog;
use Symfony\AI\Platform\Bridge\ElevenLabs\Contract\ElevenLabsContract;
use Symfony\AI\Platform\Bridge\ElevenLabs\ModelCatalog as ElevenLabsModelCatalog;
use Symfony\AI\Platform\Bridge\Gemini\Contract\GeminiContract;
use Symfony\AI\Platform\Bridge\Gemini\ModelCatalog as GeminiModelCatalog;
Expand Down Expand Up @@ -83,6 +84,8 @@
->factory([OpenAiContract::class, 'create'])
->set('ai.platform.contract.anthropic', Contract::class)
->factory([AnthropicContract::class, 'create'])
->set('ai.platform.contract.elevenlabs', Contract::class)
->factory([ElevenLabsContract::class, 'create'])
->set('ai.platform.contract.gemini', Contract::class)
->factory([GeminiContract::class, 'create'])
->set('ai.platform.contract.huggingface', Contract::class)
Expand All @@ -107,7 +110,7 @@
->set('ai.platform.model_catalog.deepseek', DeepSeekModelCatalog::class)
->set('ai.platform.model_catalog.dockermodelrunner', DockerModelRunnerModelCatalog::class)
->set('ai.platform.model_catalog.elevenlabs', ElevenLabsModelCatalog::class)
->lazy(true)
->lazy()
->tag('proxy', ['interface' => ModelCatalogInterface::class])
->set('ai.platform.model_catalog.gemini', GeminiModelCatalog::class)
->set('ai.platform.model_catalog.huggingface', HuggingFaceModelCatalog::class)
Expand Down
28 changes: 22 additions & 6 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,29 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
throw new RuntimeException('ElevenLabs platform configuration requires "symfony/ai-eleven-labs-platform" package. Try running "composer require symfony/ai-eleven-labs-platform".');
}

$httpClientReference = new Reference($platform['http_client']);

$scopedHttpClientDefinition = (new Definition(ScopingHttpClient::class))
->setFactory([ScopingHttpClient::class, 'forBaseUri'])
->setArguments([
$httpClientReference,
$platform['endpoint'],
[
'headers' => [
'x-api-key' => $platform['api_key'],
],
],
]);

$container->setDefinition('ai.platform.elevenlabs.scoped_http_client', $scopedHttpClientDefinition);

$httpClientReference = new Reference('ai.platform.elevenlabs.scoped_http_client');

if (\array_key_exists('api_catalog', $platform) && $platform['api_catalog']) {
$catalogDefinition = (new Definition(ElevenLabsApiCatalog::class))
->setLazy(true)
->setArguments([
new Reference($platform['http_client']),
$platform['api_key'],
$platform['host'],
$httpClientReference,
])
->addTag('proxy', ['interface' => ModelCatalogInterface::class]);

Expand All @@ -623,10 +639,10 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
->setLazy(true)
->setArguments([
$platform['api_key'],
$platform['host'],
new Reference($platform['http_client'], ContainerInterface::NULL_ON_INVALID_REFERENCE),
$platform['endpoint'],
$httpClientReference,
new Reference('ai.platform.model_catalog.'.$type),
null,
new Reference('ai.platform.contract.'.$type),
new Reference('event_dispatcher'),
])
->addTag('proxy', ['interface' => PlatformInterface::class])
Expand Down
49 changes: 20 additions & 29 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3895,7 +3895,7 @@ public function testToolboxWithoutExplicitToolsDefined()
$this->assertTrue($foundOutput, 'Default tool processor should have output tag with full agent ID');
}

public function testElevenLabsPlatformCanBeRegistered()
public function testElevenLabsPlatformCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
Expand All @@ -3910,18 +3910,18 @@ public function testElevenLabsPlatformCanBeRegistered()
$this->assertTrue($container->hasDefinition('ai.platform.elevenlabs'));

$definition = $container->getDefinition('ai.platform.elevenlabs');

$this->assertTrue($definition->isLazy());
$this->assertSame([ElevenLabsPlatformFactory::class, 'create'], $definition->getFactory());

$this->assertCount(6, $definition->getArguments());
$this->assertSame('foo', $definition->getArgument(0));
$this->assertSame('https://api.elevenlabs.io/v1', $definition->getArgument(1));
$this->assertSame('https://api.elevenlabs.io/v1/', $definition->getArgument(1));
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
$this->assertSame('http_client', (string) $definition->getArgument(2));
$this->assertSame('ai.platform.elevenlabs.scoped_http_client', (string) $definition->getArgument(2));
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
$this->assertSame('ai.platform.model_catalog.elevenlabs', (string) $definition->getArgument(3));
$this->assertNull($definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(4));
$this->assertSame('ai.platform.contract.elevenlabs', (string) $definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('event_dispatcher', (string) $definition->getArgument(5));

Expand All @@ -3940,16 +3940,13 @@ public function testElevenLabsPlatformCanBeRegistered()

$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}

public function testElevenLabsPlatformWithCustomEndpointCanBeRegistered()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
'elevenlabs' => [
'endpoint' => 'https://api.elevenlabs.io/v2',
'api_key' => 'foo',
'host' => 'https://api.elevenlabs.io/v2',
],
],
],
Expand All @@ -3958,18 +3955,18 @@ public function testElevenLabsPlatformWithCustomEndpointCanBeRegistered()
$this->assertTrue($container->hasDefinition('ai.platform.elevenlabs'));

$definition = $container->getDefinition('ai.platform.elevenlabs');

$this->assertTrue($definition->isLazy());
$this->assertSame([ElevenLabsPlatformFactory::class, 'create'], $definition->getFactory());

$this->assertCount(6, $definition->getArguments());
$this->assertSame('foo', $definition->getArgument(0));
$this->assertSame('https://api.elevenlabs.io/v2', $definition->getArgument(1));
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
$this->assertSame('http_client', (string) $definition->getArgument(2));
$this->assertSame('ai.platform.elevenlabs.scoped_http_client', (string) $definition->getArgument(2));
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
$this->assertSame('ai.platform.model_catalog.elevenlabs', (string) $definition->getArgument(3));
$this->assertNull($definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(4));
$this->assertSame('ai.platform.contract.elevenlabs', (string) $definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('event_dispatcher', (string) $definition->getArgument(5));

Expand All @@ -3988,10 +3985,7 @@ public function testElevenLabsPlatformWithCustomEndpointCanBeRegistered()

$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}

public function testElevenLabsPlatformWithCustomHttpClientCanBeRegistered()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
Expand All @@ -4012,12 +4006,13 @@ public function testElevenLabsPlatformWithCustomHttpClientCanBeRegistered()

$this->assertCount(6, $definition->getArguments());
$this->assertSame('foo', $definition->getArgument(0));
$this->assertSame('https://api.elevenlabs.io/v1', $definition->getArgument(1));
$this->assertSame('https://api.elevenlabs.io/v1/', $definition->getArgument(1));
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
$this->assertSame('foo', (string) $definition->getArgument(2));
$this->assertSame('ai.platform.elevenlabs.scoped_http_client', (string) $definition->getArgument(2));
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
$this->assertSame('ai.platform.model_catalog.elevenlabs', (string) $definition->getArgument(3));
$this->assertNull($definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(4));
$this->assertSame('ai.platform.contract.elevenlabs', (string) $definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('event_dispatcher', (string) $definition->getArgument(5));

Expand All @@ -4036,10 +4031,7 @@ public function testElevenLabsPlatformWithCustomHttpClientCanBeRegistered()

$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}

public function testElevenLabsPlatformWithApiCatalogCanBeRegistered()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
Expand All @@ -4061,12 +4053,13 @@ public function testElevenLabsPlatformWithApiCatalogCanBeRegistered()

$this->assertCount(6, $definition->getArguments());
$this->assertSame('foo', $definition->getArgument(0));
$this->assertSame('https://api.elevenlabs.io/v1', $definition->getArgument(1));
$this->assertSame('https://api.elevenlabs.io/v1/', $definition->getArgument(1));
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
$this->assertSame('http_client', (string) $definition->getArgument(2));
$this->assertSame('ai.platform.elevenlabs.scoped_http_client', (string) $definition->getArgument(2));
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
$this->assertSame('ai.platform.model_catalog.elevenlabs', (string) $definition->getArgument(3));
$this->assertNull($definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(4));
$this->assertSame('ai.platform.contract.elevenlabs', (string) $definition->getArgument(4));
$this->assertInstanceOf(Reference::class, $definition->getArgument(5));
$this->assertSame('event_dispatcher', (string) $definition->getArgument(5));

Expand All @@ -4082,11 +4075,9 @@ public function testElevenLabsPlatformWithApiCatalogCanBeRegistered()

$this->assertSame(ElevenLabsApiCatalog::class, $modelCatalogDefinition->getClass());
$this->assertTrue($modelCatalogDefinition->isLazy());
$this->assertCount(3, $modelCatalogDefinition->getArguments());
$this->assertCount(1, $modelCatalogDefinition->getArguments());
$this->assertInstanceOf(Reference::class, $modelCatalogDefinition->getArgument(0));
$this->assertSame('http_client', (string) $modelCatalogDefinition->getArgument(0));
$this->assertSame('foo', $modelCatalogDefinition->getArgument(1));
$this->assertSame('https://api.elevenlabs.io/v1', $modelCatalogDefinition->getArgument(2));
$this->assertSame('ai.platform.elevenlabs.scoped_http_client', (string) $modelCatalogDefinition->getArgument(0));

$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
Expand Down Expand Up @@ -7602,7 +7593,7 @@ private function getFullConfig(): array
'api_key' => 'foo',
],
'elevenlabs' => [
'host' => 'https://api.elevenlabs.io/v1',
'endpoint' => 'https://api.elevenlabs.io/v1',
'api_key' => 'elevenlabs_key_full',
],
'failover' => [
Expand Down
7 changes: 7 additions & 0 deletions src/platform/src/Bridge/ElevenLabs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
CHANGELOG
=========

0.5
---

* [BC BREAK] The `hostUrl` parameter for `ElevenLabsClient` has been removed
* [BC BREAK] The `host` parameter for `ElevenLabsApiCatalog` has been removed
* [BC BREAK] The `hostUrl` parameter for `PlatformFactory::create()` has been renamed to `endpoint`

0.3
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ final class ElevenLabsApiCatalog implements ModelCatalogInterface
{
public function __construct(
private readonly HttpClientInterface $httpClient,
#[\SensitiveParameter] private readonly string $apiKey,
private readonly string $hostUrl = 'https://api.elevenlabs.io/v1',
) {
}

Expand All @@ -45,11 +43,7 @@ public function getModel(string $modelName): ElevenLabs

public function getModels(): array
{
$response = $this->httpClient->request('GET', \sprintf('%s/models', $this->hostUrl), [
'headers' => [
'xi-api-key' => $this->apiKey,
],
]);
$response = $this->httpClient->request('GET', '/models');

$models = $response->toArray();

Expand Down
30 changes: 9 additions & 21 deletions src/platform/src/Bridge/ElevenLabs/ElevenLabsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ final class ElevenLabsClient implements ModelClientInterface
{
public function __construct(
private readonly HttpClientInterface $httpClient,
#[\SensitiveParameter] private readonly string $apiKey,
private readonly string $hostUrl = 'https://api.elevenlabs.io/v1',
) {
}

Expand All @@ -42,29 +40,22 @@ public function request(Model $model, array|string $payload, array $options = []
throw new InvalidArgumentException(\sprintf('The payload must be an array, received "%s".', get_debug_type($payload)));
}

if ($model->supports(Capability::SPEECH_TO_TEXT)) {
return $this->doSpeechToTextRequest($model, $payload);
}

if ($model->supports(Capability::TEXT_TO_SPEECH)) {
return $this->doTextToSpeechRequest($model, $payload, [
return match (true) {
$model->supports(Capability::SPEECH_TO_TEXT) => $this->doSpeechToTextRequest($model, $payload),
$model->supports(Capability::TEXT_TO_SPEECH) => $this->doTextToSpeechRequest($model, $payload, [
...$options,
...$model->getOptions(),
]);
}

throw new InvalidArgumentException(\sprintf('The model "%s" does not support text-to-speech or speech-to-text, please check the model information.', $model->getName()));
]),
default => throw new InvalidArgumentException(\sprintf('The model "%s" does not support text-to-speech or speech-to-text, please check the model information.', $model->getName())),
};
}

/**
* @param array<string|int, mixed> $payload
*/
private function doSpeechToTextRequest(Model $model, array|string $payload): RawHttpResult
{
return new RawHttpResult($this->httpClient->request('POST', \sprintf('%s/speech-to-text', $this->hostUrl), [
'headers' => [
'xi-api-key' => $this->apiKey,
],
return new RawHttpResult($this->httpClient->request('POST', 'speech-to-text', [
'body' => [
'file' => fopen($payload['input_audio']['path'], 'r'),
'model_id' => $model->getName(),
Expand All @@ -90,15 +81,12 @@ private function doTextToSpeechRequest(Model $model, array|string $payload, arra
$stream = $options['stream'] ?? false;

$url = $stream
? \sprintf('%s/text-to-speech/%s/stream', $this->hostUrl, $voice)
: \sprintf('%s/text-to-speech/%s', $this->hostUrl, $voice);
? \sprintf('text-to-speech/%s/stream', $voice)
: \sprintf('text-to-speech/%s', $voice);

unset($options['voice'], $options['stream']);

return new RawHttpResult($this->httpClient->request('POST', $url, [
'headers' => [
'xi-api-key' => $this->apiKey,
],
'json' => [
'text' => $payload['text'],
'model_id' => $model->getName(),
Expand Down
Loading