refactor(platform): ElevenLabs improvement

This commit is contained in:
Guillaume Loulier
2026-02-16 13:49:55 +01:00
parent d06ff59996
commit a741f682a7
19 changed files with 146 additions and 133 deletions

View File

@@ -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
=======================

View File

@@ -14,10 +14,7 @@ use Symfony\AI\Platform\Message\Content\Audio;
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'));

View File

@@ -14,10 +14,7 @@ use Symfony\AI\Platform\Message\Content\Text;
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)

View File

@@ -14,10 +14,7 @@ use Symfony\AI\Platform\Message\Content\Text;
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)

View File

@@ -1,6 +1,11 @@
CHANGELOG
=========
0.5
---
* [BC BREAK] The `host_url` configuration key for `ElevenLabs` has been renamed `endpoint`
0.4
---

View File

@@ -16,8 +16,8 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
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')

View File

@@ -36,6 +36,7 @@ use Symfony\AI\Platform\Bridge\Cerebras\ModelCatalog as CerebrasModelCatalog;
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;
@@ -83,6 +84,8 @@ return static function (ContainerConfigurator $container): void {
->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)
@@ -107,7 +110,7 @@ return static function (ContainerConfigurator $container): void {
->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)

View File

@@ -605,13 +605,29 @@ final class AiBundle extends AbstractBundle
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]);
@@ -623,10 +639,10 @@ final class AiBundle extends AbstractBundle
->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])

View File

@@ -3895,7 +3895,7 @@ class AiBundleTest extends TestCase
$this->assertTrue($foundOutput, 'Default tool processor should have output tag with full agent ID');
}
public function testElevenLabsPlatformCanBeRegistered()
public function testElevenLabsPlatformCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
@@ -3910,18 +3910,18 @@ class AiBundleTest extends TestCase
$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));
@@ -3940,16 +3940,13 @@ class AiBundleTest extends TestCase
$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',
],
],
],
@@ -3958,7 +3955,6 @@ class AiBundleTest extends TestCase
$this->assertTrue($container->hasDefinition('ai.platform.elevenlabs'));
$definition = $container->getDefinition('ai.platform.elevenlabs');
$this->assertTrue($definition->isLazy());
$this->assertSame([ElevenLabsPlatformFactory::class, 'create'], $definition->getFactory());
@@ -3966,10 +3962,11 @@ class AiBundleTest extends TestCase
$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));
@@ -3988,10 +3985,7 @@ class AiBundleTest extends TestCase
$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}
public function testElevenLabsPlatformWithCustomHttpClientCanBeRegistered()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
@@ -4012,12 +4006,13 @@ class AiBundleTest extends TestCase
$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));
@@ -4036,10 +4031,7 @@ class AiBundleTest extends TestCase
$this->assertTrue($modelCatalogDefinition->hasTag('proxy'));
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}
public function testElevenLabsPlatformWithApiCatalogCanBeRegistered()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
@@ -4061,12 +4053,13 @@ class AiBundleTest extends TestCase
$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));
@@ -4082,11 +4075,9 @@ class AiBundleTest extends TestCase
$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'));
@@ -7602,7 +7593,7 @@ class AiBundleTest extends TestCase
'api_key' => 'foo',
],
'elevenlabs' => [
'host' => 'https://api.elevenlabs.io/v1',
'endpoint' => 'https://api.elevenlabs.io/v1',
'api_key' => 'elevenlabs_key_full',
],
'failover' => [

View File

@@ -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
---

View File

@@ -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',
) {
}
@@ -45,11 +43,7 @@ final class ElevenLabsApiCatalog implements ModelCatalogInterface
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();

View File

@@ -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',
) {
}
@@ -42,18 +40,14 @@ final class ElevenLabsClient implements ModelClientInterface
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())),
};
}
/**
@@ -61,10 +55,7 @@ final class ElevenLabsClient implements ModelClientInterface
*/
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(),
@@ -90,15 +81,12 @@ final class ElevenLabsClient implements ModelClientInterface
$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(),

View File

@@ -43,9 +43,9 @@ final class ElevenLabsResultConverter implements ResultConverterInterface
$response = $result->getObject();
return match (true) {
\array_key_exists('stream', $options) && $options['stream'] => new StreamResult($this->convertToGenerator($response)),
str_contains($response->getInfo('url'), 'speech-to-text') => new TextResult($result->getData()['text']),
str_contains($response->getInfo('url'), 'text-to-speech') && \array_key_exists('stream', $options) && $options['stream'] => new StreamResult($this->convertToGenerator($response)),
str_contains($response->getInfo('url'), 'text-to-speech') => new BinaryResult($result->getObject()->getContent(), 'audio/mpeg'),
str_contains($response->getInfo('url'), 'speech-to-text') => new TextResult($result->getData()['text']),
default => throw new RuntimeException('Unsupported ElevenLabs response.'),
};
}

View File

@@ -17,6 +17,7 @@ use Symfony\AI\Platform\Contract;
use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface;
use Symfony\AI\Platform\Platform;
use Symfony\Component\HttpClient\EventSourceHttpClient;
use Symfony\Component\HttpClient\ScopingHttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
@@ -25,8 +26,8 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
final class PlatformFactory
{
public static function create(
string $apiKey,
string $hostUrl = 'https://api.elevenlabs.io/v1',
#[\SensitiveParameter] string $apiKey,
string $endpoint = 'https://api.elevenlabs.io/v1/',
?HttpClientInterface $httpClient = null,
ModelCatalogInterface $modelCatalog = new ModelCatalog(),
?Contract $contract = null,
@@ -34,8 +35,14 @@ final class PlatformFactory
): Platform {
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
$httpClient = ScopingHttpClient::forBaseUri($httpClient, $endpoint, [
'headers' => [
'xi-api-key' => $apiKey,
],
]);
return new Platform(
[new ElevenLabsClient($httpClient, $apiKey, $hostUrl)],
[new ElevenLabsClient($httpClient)],
[new ElevenLabsResultConverter($httpClient)],
$modelCatalog,
$contract ?? ElevenLabsContract::create(),

View File

@@ -3,6 +3,14 @@ ElevenLabs Platform
ElevenLabs platform bridge for Symfony AI.
Test Fixtures
-------------
The test fixtures in `Tests/Fixtures/` contain binary media content with the following owners and licenses:
* `audio.mp3`: davidbain, Creative Commons, see [freesound.org](https://freesound.org/people/davidbain/sounds/136777/)
Resources
---------

View File

@@ -27,7 +27,7 @@ final class ElevenLabsApiCatalogTest extends TestCase
new JsonMockResponse([]),
]);
$modelCatalog = new ElevenLabsApiCatalog($httpClient, 'foo');
$modelCatalog = new ElevenLabsApiCatalog($httpClient);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The model "foo" cannot be retrieved from the API.');
@@ -48,7 +48,7 @@ final class ElevenLabsApiCatalogTest extends TestCase
]),
]);
$modelCatalog = new ElevenLabsApiCatalog($httpClient, 'foo');
$modelCatalog = new ElevenLabsApiCatalog($httpClient);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The model "foo" is not supported, please check the ElevenLabs API.');
@@ -75,7 +75,7 @@ final class ElevenLabsApiCatalogTest extends TestCase
]),
]);
$modelCatalog = new ElevenLabsApiCatalog($httpClient, 'foo');
$modelCatalog = new ElevenLabsApiCatalog($httpClient);
$model = $modelCatalog->getModel('foo');
@@ -102,7 +102,7 @@ final class ElevenLabsApiCatalogTest extends TestCase
]),
]);
$modelCatalog = new ElevenLabsApiCatalog($httpClient, 'foo');
$modelCatalog = new ElevenLabsApiCatalog($httpClient);
$model = $modelCatalog->getModel('foo');
@@ -135,7 +135,7 @@ final class ElevenLabsApiCatalogTest extends TestCase
]),
]);
$modelCatalog = new ElevenLabsApiCatalog($httpClient, 'foo');
$modelCatalog = new ElevenLabsApiCatalog($httpClient);
$models = $modelCatalog->getModels();

View File

@@ -28,10 +28,7 @@ final class ElevenLabsClientTest extends TestCase
{
public function testSupportsModel()
{
$client = new ElevenLabsClient(
new MockHttpClient(),
'my-api-key',
);
$client = new ElevenLabsClient(new MockHttpClient());
$this->assertTrue($client->supports(new ElevenLabs('eleven_multilingual_v2')));
$this->assertFalse($client->supports(new Model('any-model')));
@@ -51,10 +48,7 @@ final class ElevenLabsClientTest extends TestCase
]);
$normalizer = new AudioNormalizer();
$client = new ElevenLabsClient(
$mockHttpClient,
'my-api-key',
);
$client = new ElevenLabsClient($mockHttpClient);
$payload = $normalizer->normalize(Audio::fromFile(\dirname(__DIR__, 6).'/fixtures/audio.mp3'));
@@ -68,7 +62,6 @@ final class ElevenLabsClientTest extends TestCase
{
$client = new ElevenLabsClient(
new MockHttpClient(),
'my-api-key',
);
$this->expectException(InvalidArgumentException::class);
@@ -86,14 +79,15 @@ final class ElevenLabsClientTest extends TestCase
]);
$normalizer = new AudioNormalizer();
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$payload = $normalizer->normalize(Audio::fromFile(\dirname(__DIR__, 6).'/fixtures/audio.mp3'));
$client->request(new ElevenLabs('scribe_v1', [Capability::INPUT_AUDIO, Capability::OUTPUT_TEXT, Capability::SPEECH_TO_TEXT]), $payload);
$client->request(new ElevenLabs('scribe_v1', [
Capability::INPUT_AUDIO,
Capability::OUTPUT_TEXT,
Capability::SPEECH_TO_TEXT,
]), $payload);
$this->assertSame(1, $httpClient->getRequestsCount());
}
@@ -107,14 +101,15 @@ final class ElevenLabsClientTest extends TestCase
]);
$normalizer = new AudioNormalizer();
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$payload = $normalizer->normalize(Audio::fromFile(\dirname(__DIR__, 6).'/fixtures/audio.mp3'));
$client->request(new ElevenLabs('scribe_v1_experimental', [Capability::INPUT_AUDIO, Capability::OUTPUT_TEXT, Capability::SPEECH_TO_TEXT]), $payload);
$client->request(new ElevenLabs('scribe_v1_experimental', [
Capability::INPUT_AUDIO,
Capability::OUTPUT_TEXT,
Capability::SPEECH_TO_TEXT,
]), $payload);
$this->assertSame(1, $httpClient->getRequestsCount());
}
@@ -125,10 +120,7 @@ final class ElevenLabsClientTest extends TestCase
new JsonMockResponse([]),
]);
$client = new ElevenLabsClient(
$mockHttpClient,
'my-api-key',
);
$client = new ElevenLabsClient($mockHttpClient);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The payload must contain a "text" key');
@@ -146,12 +138,9 @@ final class ElevenLabsClientTest extends TestCase
new MockResponse($payload->asBinary()),
]);
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$client->request(new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH], options: [
$client->request(new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH], [
'voice' => 'Dslrhjl3ZpzrctukrQSN',
]), [
'text' => 'foo',
@@ -168,10 +157,7 @@ final class ElevenLabsClientTest extends TestCase
new MockResponse($payload->asBinary()),
]);
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$client->request(new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH]), [
'text' => 'foo',
@@ -190,12 +176,9 @@ final class ElevenLabsClientTest extends TestCase
new MockResponse($payload->asBinary()),
]);
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$result = $client->request(new ElevenLabs('eleven_multilingual_v2', capabilities: [Capability::TEXT_TO_SPEECH], options: [
$result = $client->request(new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH], [
'voice' => 'Dslrhjl3ZpzrctukrQSN',
'stream' => true,
]), [
@@ -214,10 +197,7 @@ final class ElevenLabsClientTest extends TestCase
new MockResponse($payload->asBinary()),
]);
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$result = $client->request(new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH]), [
'text' => 'foo',
@@ -252,10 +232,7 @@ final class ElevenLabsClientTest extends TestCase
return new MockResponse($payload->asBinary());
});
$client = new ElevenLabsClient(
$httpClient,
'my-api-key',
);
$client = new ElevenLabsClient($httpClient);
$client->request(
new ElevenLabs('eleven_multilingual_v2', [Capability::TEXT_TO_SPEECH]),

View File

@@ -17,8 +17,10 @@ use Symfony\AI\Platform\Bridge\ElevenLabs\ElevenLabsResultConverter;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\Result\BinaryResult;
use Symfony\AI\Platform\Result\InMemoryRawResult;
use Symfony\AI\Platform\Result\StreamResult;
use Symfony\AI\Platform\Result\TextResult;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
final class ElevenLabsConverterTest extends TestCase
{
@@ -48,6 +50,20 @@ final class ElevenLabsConverterTest extends TestCase
$this->assertSame('Hello there', $result->getContent());
}
public function testConvertTextToSpeechAsStreamResponse()
{
$converter = new ElevenLabsResultConverter(new MockHttpClient([], 'https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/stream'));
$rawResult = new InMemoryRawResult([], [], MockResponse::fromFile(\dirname(__DIR__).'/Tests/Fixtures/audio.mp3', [
'url' => 'https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/stream',
]));
$result = $converter->convert($rawResult, [
'stream' => true,
]);
$this->assertInstanceOf(StreamResult::class, $result);
}
public function testConvertTextToSpeechResponse()
{
$converter = new ElevenLabsResultConverter(new MockHttpClient());