Skip to content

Commit ce5b2f0

Browse files
committed
feat(agent): ElevenLabs TTS tool
1 parent e2c787d commit ce5b2f0

File tree

6 files changed

+146
-0
lines changed

6 files changed

+146
-0
lines changed

examples/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ TAVILY_API_KEY=
5252
# For using Brave (tool)
5353
BRAVE_API_KEY=
5454

55+
# For using ElevenLabs (tool)
56+
ELEVENLABS_API_KEY=
57+
5558
# For using MongoDB Atlas (store)
5659
MONGODB_URI=
5760

examples/misc/text-to-speech.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
14+
use Symfony\AI\Agent\Toolbox\Tool\ElevenLabs;
15+
use Symfony\AI\Agent\Toolbox\Toolbox;
16+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
17+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
18+
use Symfony\AI\Platform\Message\Message;
19+
use Symfony\AI\Platform\Message\MessageBag;
20+
21+
require_once dirname(__DIR__).'/bootstrap.php';
22+
23+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
24+
$model = new Gpt(Gpt::GPT_4O_MINI);
25+
26+
$elevenLabs = new ElevenLabs(
27+
http_client(),
28+
env('ELEVENLABS_API_KEY'),
29+
__DIR__.'/../tmp',
30+
'eleven_multilingual_v2',
31+
'Dslrhjl3ZpzrctukrQSN' // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
32+
);
33+
34+
$toolbox = new Toolbox([$elevenLabs], logger: logger());
35+
$toolProcessor = new AgentProcessor($toolbox);
36+
37+
$agent = new Agent($platform, $model, inputProcessors: [$toolProcessor], outputProcessors: [$toolProcessor]);
38+
39+
$messages = new MessageBag(Message::ofUser('Convert the following text to voice: "Hello world with voice!"'));
40+
$result = $agent->call($messages);
41+
42+
echo $result->getContent().\PHP_EOL;

src/agent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"symfony/css-selector": "^6.4 || ^7.1",
4242
"symfony/dom-crawler": "^6.4 || ^7.1",
4343
"symfony/event-dispatcher": "^6.4 || ^7.1",
44+
"symfony/filesystem": "^7.3",
4445
"symfony/http-foundation": "^6.4 || ^7.1"
4546
},
4647
"config": {

src/agent/doc/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ messages will be added to your MessageBag::
281281
* `Weather Tool with Event Listener`_
282282
* `Wikipedia Tool`_
283283
* `YouTube Transcriber Tool`_
284+
* `ElevenLabs Text to Speech`_
284285

285286
Retrieval Augmented Generation (RAG)
286287
------------------------------------
@@ -552,6 +553,7 @@ useful when certain interactions shouldn't be influenced by the memory context::
552553
.. _`Weather Tool with Event Listener`: https://github.com/symfony/ai/blob/main/examples/toolbox/weather-event.php
553554
.. _`Wikipedia Tool`: https://github.com/symfony/ai/blob/main/examples/openai/toolcall-stream.php
554555
.. _`YouTube Transcriber Tool`: https://github.com/symfony/ai/blob/main/examples/openai/toolcall.php
556+
.. _`ElevenLabs Text to Speech`: https://github.com/symfony/ai/blob/main/examples/misc/text-to-speech.php
555557
.. _`Store Component`: https://github.com/symfony/ai-store
556558
.. _`RAG with MongoDB`: https://github.com/symfony/ai/blob/main/examples/store/mongodb-similarity-search.php
557559
.. _`RAG with Pinecone`: https://github.com/symfony/ai/blob/main/examples/store/pinecone-similarity-search.php
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Agent\Toolbox\Tool;
13+
14+
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
15+
use Symfony\Component\Filesystem\Filesystem;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
18+
/**
19+
* @author Guillaume Loulier <[email protected]>
20+
*/
21+
#[AsTool('eleven_labs', description: 'convert text to speech / voice')]
22+
final readonly class ElevenLabs
23+
{
24+
public function __construct(
25+
private HttpClientInterface $httpClient,
26+
#[\SensitiveParameter] private string $apiKey,
27+
private string $path,
28+
private string $model,
29+
private string $voice,
30+
) {
31+
}
32+
33+
public function __invoke(string $text): array
34+
{
35+
$response = $this->httpClient->request('POST', \sprintf('https://api.elevenlabs.io/v1/text-to-speech/%s?output_format=mp3_44100_128', $this->voice), [
36+
'headers' => [
37+
'xi-api-key' => $this->apiKey,
38+
],
39+
'json' => [
40+
'text' => $text,
41+
'model_id' => $this->model,
42+
],
43+
]);
44+
45+
$file = \sprintf('%s/%s.mp3', $this->path, uniqid());
46+
47+
$filesystem = new Filesystem();
48+
$filesystem->dumpFile($file, $response->getContent());
49+
50+
return [
51+
'input' => $text,
52+
'path' => $file,
53+
];
54+
}
55+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Agent\Tests\Toolbox\Tool;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\AI\Agent\Toolbox\Tool\ElevenLabs;
17+
use Symfony\Component\HttpClient\MockHttpClient;
18+
use Symfony\Component\HttpClient\Response\MockResponse;
19+
20+
#[CoversClass(ElevenLabs::class)]
21+
final class ElevenLabsTest extends TestCase
22+
{
23+
public function testTextToSpeech()
24+
{
25+
$httpClient = new MockHttpClient(
26+
new MockResponse(file_get_contents(__DIR__.'/../../../../../fixtures/audio.mp3'), [
27+
'headers' => [
28+
'Content-Type' => 'audio/mpeg',
29+
],
30+
'http_code' => 200,
31+
]),
32+
);
33+
34+
$elevenLabs = new ElevenLabs($httpClient, 'foo', 'bar', 'baz', 'random');
35+
36+
$result = $elevenLabs('Hello World');
37+
38+
$this->assertCount(2, $result);
39+
$this->assertSame('Hello World', $result['input']);
40+
$this->assertNull($result['file']);
41+
$this->assertSame(1, $httpClient->getRequestsCount());
42+
}
43+
}

0 commit comments

Comments
 (0)