8000 feat(OpenAI): Add support for streaming for transcriptions by iBotPeaches · Pull Request #603 · openai-php/client · GitHub
[go: up one dir, main page]

Skip to content

feat(OpenAI): Add support for streaming for transcriptions #603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 17, 2025
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,21 @@ foreach ($response->words as $word) {
$response->toArray(); // ['task' => 'transcribe', ...]
```

#### `transcribe streamed`

Transcribes audio into the input language with streaming.

```php
$stream = OpenAI::audio()->transcribeStreamed([
'model' => 'gpt-4o-transcribe',
'file' => fopen('audio.mp3', 'r'),
]);

foreach ($stream as $event) {
echo json_encode($event->toArray()); // {"event":"transcript.text.delta","data":{"delta":"The"}}
}
```

#### `translate`

Translates audio into English.
Expand Down
12 changes: 12 additions & 0 deletions src/Contracts/Resources/AudioContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use OpenAI\Responses\Audio\SpeechStreamResponse;
use OpenAI\Responses\Audio\TranscriptionResponse;
use OpenAI\Responses\Audio\TranscriptionStreamResponse;
use OpenAI\Responses\Audio\TranslationResponse;
use OpenAI\Responses\StreamResponse;

interface AudioContract
{
Expand Down Expand Up @@ -35,6 +37,16 @@ public function speechStreamed(array $parameters): SpeechStreamResponse;
*/
public function transcribe(array $parameters): TranscriptionResponse;

/**
* Transcribes audio input the streamed events.
*
* @see https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-stream
*
* @param array<string, mixed> $parameters
* @return StreamResponse<TranscriptionStreamResponse>
*/
public function transcribeStreamed(array $parameters): StreamResponse;

/**
* Translates audio into English.
*
Expand Down
26 changes: 26 additions & 0 deletions src/Resources/Audio.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use OpenAI\Contracts\Resources\AudioContract;
use OpenAI\Responses\Audio\SpeechStreamResponse;
use OpenAI\Responses\Audio\TranscriptionResponse;
use OpenAI\Responses\Audio\TranscriptionStreamResponse;
use OpenAI\Responses\Audio\TranslationResponse;
use OpenAI\Responses\StreamResponse;
use OpenAI\ValueObjects\Transporter\Payload;
use OpenAI\ValueObjects\Transporter\Response;

final class Audio implements AudioContract
{
use Concerns\Streamable;
use Concerns\Transportable;

/**
Expand All @@ -24,6 +27,8 @@ final class Audio implements AudioContract
*/
public function speech(array $parameters): string
{
$this->ensureNotStreamed($parameters, 'speechStreamed');

$payload = Payload::create('audio/speech', $parameters);

return $this->transporter->requestContent($payload);
Expand Down Expand Up @@ -54,6 +59,8 @@ public function speechStreamed(array $parameters): SpeechStreamResponse
*/
public function transcribe(array $parameters): TranscriptionResponse
{
$this->ensureNotStreamed($parameters, 'transcribeStreamed');

$payload = Payload::upload('audio/transcriptions', $parameters);

/** @var Response<array{task: ?string, language: ?string, duration: ?float, segments: array<int, array{id: int, seek: int, start: float, end: float, text: string, tokens: array<int, int>, temperature: float, avg_logprob: float, compression_ratio: float, no_speech_prob: float, transient?: bool}>, words: array<int, array{word: string, start: float, end: float}>, text: string}> $response */
Expand All @@ -62,6 +69,25 @@ public function transcribe(array $parameters): TranscriptionResponse
return TranscriptionResponse::from($response->data(), $response->meta());
}

/**
* Transcribes audio input the streamed events.
*
* @see https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-stream
*
* @param array<string, mixed> $parameters
* @return StreamResponse<TranscriptionStreamResponse>
*/
public function transcribeStreamed(array $parameters): StreamResponse
{
$parameters = $this->setStreamParameter($parameters);

$payload = Payload::upload('audio/transcriptions', $parameters);

$response = $this->transporter->requestStream($payload);

return new StreamResponse(TranscriptionStreamResponse::class, $response);
}

/**
* Translates audio into English.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Resources/Concerns/Streamable.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trait Streamable
/**
* @param array<string, mixed> $parameters
*/
private function ensureNotStreamed(array $parameters): void
private function ensureNotStreamed(array $parameters, string $fallbackFunction = 'createStreamed'): void
{
if (! isset($parameters['stream'])) {
return;
Expand All @@ -19,7 +19,7 @@ private function ensureNotStreamed(array $parameters): void
return;
}

throw new InvalidArgumentException('Stream option is not supported. Please use the createStreamed() method instead.');
throw new InvalidArgumentException("Stream option is not supported. Please use the $fallbackFunction() method instead.");
}

/**
Expand Down
63 changes: 63 additions & 0 deletions src/Responses/Audio/Streaming/Logprobs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace OpenAI\Responses\Audio\Streaming;

use OpenAI\Contracts\ResponseContract;
use OpenAI\Contracts\ResponseHasMetaInformationContract;
use OpenAI\Responses\Concerns\ArrayAccessible;
use OpenAI\Responses\Concerns\HasMetaInformation;
use OpenAI\Responses\Meta\MetaInformation;
use OpenAI\Testing\Responses\Concerns\Fakeable;

/**
* @phpstan-type LogprobsType array{bytes: array<int, string>, logprobs: int, token: string}
*
* @implements ResponseContract<LogprobsType>
*/
final class Logprobs implements ResponseContract, ResponseHasMetaInformationContract
{
/**
* @use ArrayAccessible<LogprobsType>
*/
use ArrayAccessible;

use Fakeable;
use HasMetaInformation;

/**
* @param array<int, string> $bytes
*/
private function __construct(
public readonly array $bytes,
public readonly int $logprobs,
public readonly string $token,
public readonly MetaInformation $meta,
) {}

/**
* @param LogprobsType $attributes
*/
public static function from(array $attributes, MetaInformation $meta): self
{
return new self(
bytes: $attributes['bytes'],
logprobs: $attributes['logprobs'],
token: $attributes['token'],
meta: $meta,
);
}

/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [
'bytes' => $this->bytes,
'logprobs' => $this->logprobs,
'token' => $this->token,
];
}
}
72 changes: 72 additions & 0 deletions src/Responses/Audio/Streaming/TranscriptTextDelta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace OpenAI\Responses\Audio\Streaming;

use OpenAI\Contracts\ResponseContract;
use OpenAI\Contracts\ResponseHasMetaInformationContract;
use OpenAI\Responses\Concerns\ArrayAccessible;
use OpenAI\Responses\Concerns\HasMetaInformation;
use OpenAI\Responses\Meta\MetaInformation;
use OpenAI\Testing\Responses\Concerns\Fakeable;

/**
* @phpstan-import-type LogprobsType from Logprobs
*
* @phpstan-type TranscriptTextDeltaType array{logprobs?: array<int, LogprobsType>|null, delta: string}
*
* @implements ResponseContract<TranscriptTextDeltaType>
*/
final class TranscriptTextDelta implements ResponseContract, ResponseHasMetaInformationContract
{
/**
* @use ArrayAccessible<TranscriptTextDeltaType>
*/
use ArrayAccessible;

use Fakeable;
use HasMetaInformation;

/**
* @param array<int, Logprobs>|null $logprobs
*/
private function __construct(
public readonly ?array $logprobs,
public readonly string $delta,
public readonly MetaInformation $meta,
) {}

/**
* @param TranscriptTextDeltaType $attributes
*/
public static function from(array $attributes, MetaInformation $meta): self
{
if (isset($attributes['logprobs'])) {
$logprobs = array_map(
fn (array $logprob): Logprobs => Logprobs::from($logprob, $meta),
$attributes['logprobs']
);
}

return new self(
logprobs: $logprobs ?? null,
delta: $attributes['delta'],
meta: $meta,
);
}

/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [
'logprobs' => $this->logprobs ? array_map(
fn (Logprobs $logprob): array => $logprob->toArray(),
$this->logprobs
) : null,
'delta' => $this->delta,
];
}
}
72 changes: 72 additions & 0 deletions src/Responses/Audio/Streaming/TranscriptTextDone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace OpenAI\Responses\Audio\Streaming;

use OpenAI\Contracts\ResponseContract;
use OpenAI\Contracts\ResponseHasMetaInformationContract;
use OpenAI\Responses\Concerns\ArrayAccessible;
use OpenAI\Responses\Concerns\HasMetaInformation;
use OpenAI\Responses\Meta\MetaInformation;
use OpenAI\Testing\Responses\Concerns\Fakeable;

/**
* @phpstan-import-type LogprobsType from Logprobs
*
* @phpstan-type TranscriptTextDoneType array{logprobs?: array<int, LogprobsType>|null, text: string}
*
* @implements ResponseContract<TranscriptTextDoneType>
*/
final class TranscriptTextDone implements ResponseContract, ResponseHasMetaInformationContract
{
/**
* @use ArrayAccessible<TranscriptTextDoneType>
*/
use ArrayAccessible;

use Fakeable;
use HasMetaInformation;

/**
* @param array<int, Logprobs>|null $logprobs
*/
private function __construct(
public readonly ?array $logprobs,
public readonly string $text,
public readonly MetaInformation $meta,
) {}

/**
* @param TranscriptTextDoneType $attributes
*/
public static function from(array $attributes, MetaInformation $meta): self
{
if (isset($attributes['logprobs'])) {
$logprobs = array_map(
fn (array $logprob): Logprobs => Logprobs::from($logprob, $meta),
$attributes['logprobs']
);
}

return new self(
logprobs: $logprobs ?? null,
text: $attributes['text'],
meta: $meta,
);
}

/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [
'logprobs' => $this->logprobs ? array_map(
fn (Logprobs $logprob): array => $logprob->toArray(),
$this->logprobs
) : null,
'text' => $this->text,
];
}
}
Loading
0