Skip to content

Commit 16f5994

Browse files
committed
ref
1 parent 8dd5cd5 commit 16f5994

File tree

18 files changed

+152
-190
lines changed

18 files changed

+152
-190
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ Symfony AI consists of several lower and higher level **components** and the res
1313
* **[Agent](src/agent/README.md)**: Framework for building AI agents that can interact with users and perform tasks.
1414
* **[Chat](src/chat/README.md)**: An unified interface to send messages to agents and store long-term context.
1515
* **[Store](src/store/README.md)**: Data storage abstraction with indexing and retrieval for AI applications.
16-
* **[Voice](src/voice/README.md)**: An unified interface to provide TTS / STT / STS for agents and more.
1716
* **Bundles**
1817
* **[AI Bundle](src/ai-bundle/README.md)**: Symfony integration for AI Platform, Store and Agent components.
1918
* **[MCP Bundle](src/mcp-bundle/README.md)**: Symfony integration for official MCP SDK, allowing them to act as MCP servers or clients.

docs/components/voice.rst

Lines changed: 0 additions & 2 deletions
This file was deleted.

examples/voice/agent-eleven-labs-voice.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@
3333
'voice' => 'Dslrhjl3ZpzrctukrQSN', // Brad (https://elevenlabs.io/app/voice-library?voiceId=Dslrhjl3ZpzrctukrQSN)
3434
]);
3535

36-
echo $answer->asVoice();
36+
echo $result->asVoice();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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\Voice\Bridge\ElevenLabs;
13+
14+
use Symfony\AI\Agent\Output;
15+
use Symfony\AI\Platform\Voice\Voice;
16+
use Symfony\AI\Platform\Voice\VoiceListenerInterface;
17+
use Symfony\AI\Platform\Voice\VoiceProviderInterface;
18+
use Symfony\AI\Platform\Platform;
19+
20+
/**
21+
* @author Guillaume Loulier <[email protected]>
22+
*/
23+
final class VoiceProvider implements VoiceProviderInterface, VoiceListenerInterface
24+
{
25+
public const ELEVEN_LABS_STT_MODEL = 'eleven_labs.stt_model';
26+
27+
public function __construct(
28+
private readonly Platform $platform,
29+
private readonly string $model,
30+
) {
31+
}
32+
33+
public function addVoice(Output $output): void
34+
{
35+
$result = $output->getResult();
36+
37+
$voice = $this->platform->invoke($this->model, $result->getContent());
38+
39+
$output->setVoice(new Voice($voice->asBinary(), $this->getName()));
40+
}
41+
42+
public function listen(object|array|string $input, array $options): Voice
43+
{
44+
$model = $options[self::ELEVEN_LABS_STT_MODEL];
45+
46+
unset($options[self::ELEVEN_LABS_STT_MODEL]);
47+
48+
$text = $this->platform->invoke($model, $input);
49+
50+
return new Voice($input, $text);
51+
}
52+
53+
public function supportListening(object|array|string $input, array $options): bool
54+
{
55+
return \array_key_exists(self::ELEVEN_LABS_STT_MODEL, $options);
56+
}
57+
}

src/platform/src/Platform.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,28 @@ final class Platform implements PlatformInterface
3434
*/
3535
private readonly array $resultConverters;
3636

37+
/**
38+
* @var VoiceListenerInterface[]
39+
*/
40+
private iterable $voiceListeners;
41+
3742
/**
3843
* @param iterable<ModelClientInterface> $modelClients
3944
* @param iterable<ResultConverterInterface> $resultConverters
45+
* @param iterable<VoiceListenerInterface> $voiceListeners
4046
*/
4147
public function __construct(
4248
iterable $modelClients,
4349
iterable $resultConverters,
50+
iterable $voiceListeners,
4451
private readonly ModelCatalogInterface $modelCatalog,
4552
private ?Contract $contract = null,
4653
private readonly ?EventDispatcherInterface $eventDispatcher = null,
4754
) {
4855
$this->contract = $contract ?? Contract::create();
4956
$this->modelClients = $modelClients instanceof \Traversable ? iterator_to_array($modelClients) : $modelClients;
5057
$this->resultConverters = $resultConverters instanceof \Traversable ? iterator_to_array($resultConverters) : $resultConverters;
58+
$this->voiceListeners = $voiceListeners instanceof \Traversable ? iterator_to_array($voiceListeners) : $voiceListeners;
5159
}
5260

5361
public function invoke(string $model, array|string|object $input, array $options = []): DeferredResult
@@ -65,8 +73,9 @@ public function invoke(string $model, array|string|object $input, array $options
6573
}
6674

6775
$result = $this->convertResult($model, $this->doInvoke($model, $payload, $options), $options);
76+
$finalResult = $this->listen($result, $payload, $options);
6877

69-
$event = new ResultEvent($model, $result, $options);
78+
$event = new ResultEvent($model, $finalResult, $options);
7079
$this->eventDispatcher?->dispatch($event);
7180

7281
return $event->getDeferredResult();
@@ -105,4 +114,17 @@ private function convertResult(Model $model, RawResultInterface $result, array $
105114

106115
throw new RuntimeException(\sprintf('No ResultConverter registered for model "%s" with given input.', $model::class));
107116
}
117+
118+
private function listen(DeferredResult $result, array|string $payload, array $options): DeferredResult
119+
{
120+
foreach ($this->voiceListeners as $voiceListener) {
121+
if ($voiceListener->supportListening($payload, $options)) {
122+
$result->setVoice($voiceListener->listen($payload, $options));
123+
124+
return $result;
125+
}
126+
}
127+
128+
throw new RuntimeException('No VoiceListener registered.');
129+
}
108130
}

src/platform/src/Result/DeferredResult.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
use Symfony\AI\Platform\Metadata\MetadataAwareTrait;
1717
use Symfony\AI\Platform\ResultConverterInterface;
1818
use Symfony\AI\Platform\Vector\Vector;
19+
use Symfony\AI\Platform\Voice\VoiceAwareTrait;
1920

2021
/**
2122
* @author Christopher Hertel <[email protected]>
2223
*/
2324
final class DeferredResult
2425
{
2526
use MetadataAwareTrait;
27+
use VoiceAwareTrait;
2628

2729
private bool $isConverted = false;
2830
private ResultInterface $convertedResult;
@@ -132,6 +134,14 @@ public function asToolCalls(): array
132134
return $this->as(ToolCallResult::class)->getContent();
133135
}
134136

137+
/**
138+
* @throws ExceptionInterface
139+
*/
140+
public function asVoice(): string
141+
{
142+
return $this->as(VoiceResult::class)->getContent();
143+
}
144+
135145
/**
136146
* @param class-string $type
137147
*

src/platform/src/Voice/Voice.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Platform\Voice;
13+
14+
use Symfony\AI\Platform\Result\DeferredResult;
15+
16+
/**
17+
* @author Guillaume Loulier <[email protected]>
18+
*/
19+
final class Voice
20+
{
21+
public function __construct(
22+
public readonly string|array $payload,
23+
public DeferredResult $result,
24+
) {
25+
}
26+
}

src/platform/src/Result/VoiceAwareTrait.php renamed to src/platform/src/Voice/VoiceAwareTrait.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\AI\Platform\Result;
12+
namespace Symfony\AI\Platform\Voice;
1313

1414
/**
1515
* @author Guillaume Loulier <[email protected]>
1616
*/
1717
trait VoiceAwareTrait
1818
{
19+
private Voice $voice;
1920

21+
public function setVoice(Voice $voice): void
22+
{
23+
$this->voice = $voice;
24+
}
25+
26+
public function getVoice(): Voice
27+
{
28+
return $this->voice;
29+
}
2030
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Platform\Voice;
13+
14+
/**
15+
* @author Guillaume Loulier <[email protected]>
16+
*/
17+
interface VoiceListenerInterface
18+
{
19+
public function listen(array|string|object $input, array $options): Voice;
20+
21+
public function supportListening(array|string|object $input, array $options): bool;
22+
}

src/voice/src/VoiceProviderInterface.php renamed to src/platform/src/Voice/VoiceProviderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\AI\Voice;
12+
namespace Symfony\AI\Platform\Voice;
1313

1414
use Symfony\AI\Agent\Output;
1515

0 commit comments

Comments
 (0)