Skip to content

Commit 69a19ba

Browse files
committed
Add stream support for Text To Speech
1 parent 44ac258 commit 69a19ba

File tree

15 files changed

+207
-6
lines changed

15 files changed

+207
-6
lines changed

src/Contracts/Resources/AudioContract.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace OpenAI\Contracts\Resources;
44

5+
use OpenAI\Responses\Audio\SpeechStreamResponse;
56
use OpenAI\Responses\Audio\TranscriptionResponse;
67
use OpenAI\Responses\Audio\TranslationResponse;
78

@@ -16,6 +17,15 @@ interface AudioContract
1617
*/
1718
public function speech(array $parameters): string;
1819

20+
/**
21+
* Generates streamed audio from the input text.
22+
*
23+
* @see https://platform.openai.com/docs/api-reference/audio/createSpeech
24+
*
25+
* @param array<string, mixed> $parameters
26+
*/
27+
public function speechStreamed(array $parameters): SpeechStreamResponse;
28+
1929
/**
2030
* Transcribes audio into the input language.
2131
*
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Contracts;
6+
7+
use IteratorAggregate;
8+
9+
/**
10+
* @template T
11+
*
12+
* @extends IteratorAggregate<int, T>
13+
*
14+
* @internal
15+
*/
16+
interface ResponseStreamContract extends IteratorAggregate
17+
{
18+
}

src/Resources/Audio.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace OpenAI\Resources;
66

77
use OpenAI\Contracts\Resources\AudioContract;
8+
use OpenAI\Responses\Audio\SpeechStreamResponse;
89
use OpenAI\Responses\Audio\TranscriptionResponse;
910
use OpenAI\Responses\Audio\TranslationResponse;
1011
use OpenAI\ValueObjects\Transporter\Payload;
@@ -28,6 +29,22 @@ public function speech(array $parameters): string
2829
return $this->transporter->requestContent($payload);
2930
}
3031

32+
/**
33+
* Generates streamed audio from the input text.
34+
*
35+
* @see https://platform.openai.com/docs/api-reference/audio/createSpeech
36+
*
37+
* @param array<string, mixed> $parameters
38+
*/
39+
public function speechStreamed(array $parameters): SpeechStreamResponse
40+
{
41+
$payload = Payload::create('audio/speech', $parameters);
42+
43+
$response = $this->transporter->requestStream($payload);
44+
45+
return new SpeechStreamResponse($response);
46+
}
47+
3148
/**
3249
* Transcribes audio into the input language.
3350
*
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace OpenAI\Responses\Audio;
4+
5+
use Generator;
6+
use Http\Discovery\Psr17Factory;
7+
use OpenAI\Contracts\ResponseHasMetaInformationContract;
8+
use OpenAI\Contracts\ResponseStreamContract;
9+
use OpenAI\Responses\Meta\MetaInformation;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
/**
13+
* @implements ResponseStreamContract<string>
14+
*/
15+
final class SpeechStreamResponse implements ResponseHasMetaInformationContract, ResponseStreamContract
16+
{
17+
public function __construct(
18+
private readonly ResponseInterface $response,
19+
) {
20+
//
21+
}
22+
23+
/**
24+
* {@inheritDoc}
25+
*/
26+
public function getIterator(): Generator
27+
{
28+
while (! $this->response->getBody()->eof()) {
29+
yield $this->response->getBody()->read(1024);
30+
}
31+
}
32+
33+
public function meta(): MetaInformation
34+
{
35+
// @phpstan-ignore-next-line
36+
return MetaInformation::from($this->response->getHeaders());
37+
}
38+
39+
public static function fake(string $content = null, MetaInformation $meta = null): static
40+
{
41+
$psr17Factory = new Psr17Factory();
42+
$response = $psr17Factory->createResponse()
43+
->withBody($psr17Factory->createStream($content ?? (string) file_get_contents(__DIR__.'/../../Testing/Responses/Fixtures/Audio/speech-streamed.mp3')));
44+
45+
if ($meta instanceof \OpenAI\Responses\Meta\MetaInformation) {
46+
foreach ($meta->toArray() as $key => $value) {
47+
$response = $response->withHeader($key, (string) $value);
48+
}
49+
}
50+
51+
return new self($response);
52+
}
53+
}

src/Responses/StreamResponse.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace OpenAI\Responses;
44

55
use Generator;
6-
use IteratorAggregate;
76
use OpenAI\Contracts\ResponseHasMetaInformationContract;
7+
use OpenAI\Contracts\ResponseStreamContract;
88
use OpenAI\Exceptions\ErrorException;
99
use OpenAI\Responses\Meta\MetaInformation;
1010
use Psr\Http\Message\ResponseInterface;
@@ -13,9 +13,9 @@
1313
/**
1414
* @template TResponse
1515
*
16-
* @implements IteratorAggregate<int, TResponse>
16+
* @implements ResponseStreamContract<TResponse>
1717
*/
18-
final class StreamResponse implements IteratorAggregate, ResponseHasMetaInformationContract
18+
final class StreamResponse implements ResponseHasMetaInformationContract, ResponseStreamContract
1919
{
2020
/**
2121
* Creates a new Stream Response instance.

src/Testing/ClientFake.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use OpenAI\Contracts\ClientContract;
66
use OpenAI\Contracts\ResponseContract;
7+
use OpenAI\Contracts\ResponseStreamContract;
78
use OpenAI\Responses\StreamResponse;
89
use OpenAI\Testing\Requests\TestRequest;
910
use OpenAI\Testing\Resources\AssistantsTestResource;
@@ -113,7 +114,7 @@ private function resourcesOf(string $type): array
113114
return array_filter($this->requests, fn (TestRequest $request): bool => $request->resource() === $type);
114115
}
115116

116-
public function record(TestRequest $request): ResponseContract|StreamResponse|string
117+
public function record(TestRequest $request): ResponseContract|ResponseStreamContract|string
117118
{
118119
$this->requests[] = $request;
119120

src/Testing/Resources/AudioTestResource.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use OpenAI\Contracts\Resources\AudioContract;
66
use OpenAI\Resources\Audio;
7+
use OpenAI\Responses\Audio\SpeechStreamResponse;
78
use OpenAI\Responses\Audio\TranscriptionResponse;
89
use OpenAI\Responses\Audio\TranslationResponse;
910
use OpenAI\Testing\Resources\Concerns\Testable;
@@ -22,6 +23,11 @@ public function speech(array $parameters): string
2223
return $this->record(__FUNCTION__, func_get_args());
2324
}
2425

26+
public function speechStreamed(array $parameters): SpeechStreamResponse
27+
{
28+
return $this->record(__FUNCTION__, func_get_args());
29+
}
30+
2531
public function transcribe(array $parameters): TranscriptionResponse
2632
{
2733
return $this->record(__FUNCTION__, func_get_args());

src/Testing/Resources/Concerns/Testable.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace OpenAI\Testing\Resources\Concerns;
44

55
use OpenAI\Contracts\ResponseContract;
6-
use OpenAI\Responses\StreamResponse;
6+
use OpenAI\Contracts\ResponseStreamContract;
77
use OpenAI\Testing\ClientFake;
88
use OpenAI\Testing\Requests\TestRequest;
99

@@ -18,7 +18,7 @@ abstract protected function resource(): string;
1818
/**
1919
* @param array<string, mixed> $args
2020
*/
21-
protected function record(string $method, array $args = []): ResponseContract|StreamResponse|string
21+
protected function record(string $method, array $args = []): ResponseContract|ResponseStreamContract|string
2222
{
2323
return $this->fake->record(new TestRequest($this->resource(), $method, $args));
2424
}
53.9 KB
Binary file not shown.

tests/Arch.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
]);
3030

3131
test('responses')->expect('OpenAI\Responses')->toOnlyUse([
32+
'Http\Discovery\Psr17Factory',
3233
'OpenAI\Enums',
3334
'OpenAI\Exceptions\ErrorException',
3435
'OpenAI\Contracts',

0 commit comments

Comments
 (0)