Skip to content

Commit a3cb384

Browse files
feat(OpenAI): add image streaming support (#720)
1 parent bcd6c06 commit a3cb384

File tree

12 files changed

+572
-1
lines changed

12 files changed

+572
-1
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,24 @@ foreach ($response->data as $data) {
12931293
$response->toArray(); // ['created' => 1589478378, data => ['url' => 'https://oaidalleapiprodscus...', ...]]
12941294
```
12951295

1296+
#### `create streamed`
1297+
1298+
When you create an image with stream set to true, the server will emit server-sent events to the client as the image is generated. All events and their payloads can be found in [OpenAI docs](https://platform.openai.com/docs/api-reference/images-streaming).
1299+
1300+
```php
1301+
$stream = $client->images()->createStreamed([
1302+
'model' => 'gpt-image-1',
1303+
'prompt' => 'A cute baby sea otter',
1304+
'n' => 1,
1305+
'size' => '1024x1024',
1306+
'response_format' => 'url',
1307+
]);
1308+
1309+
foreach ($stream as $image) {
1310+
$image->type; // 'image_generation.partial_image'
1311+
}
1312+
```
1313+
12961314
#### `edit`
12971315

12981316
Creates an edited or extended image given an original image and a prompt.
@@ -1317,6 +1335,24 @@ foreach ($response->data as $data) {
13171335
$response->toArray(); // ['created' => 1589478378, data => ['url' => 'https://oaidalleapiprodscus...', ...]]
13181336
```
13191337

1338+
#### `edit streamed`
1339+
1340+
When you edit an image with stream set to true, the server will emit server-sent events to the client as the image is generated. All events and their payloads can be found in [OpenAI docs](https://platform.openai.com/docs/api-reference/images-streaming).
1341+
1342+
```php
1343+
$stream = $client->images()->editStreamed([
1344+
'model' => 'gpt-image-1',
1345+
'prompt' => 'A cute baby sea otter',
1346+
'n' => 1,
1347+
'size' => '1024x1024',
1348+
'response_format' => 'url',
1349+
]);
1350+
1351+
foreach ($stream as $image) {
1352+
$image->type; // 'image_generation.partial_image'
1353+
}
1354+
```
1355+
13201356
#### `variation`
13211357

13221358
Creates a variation of a given image.

src/Resources/Images.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66

77
use OpenAI\Contracts\Resources\ImagesContract;
88
use OpenAI\Responses\Images\CreateResponse;
9+
use OpenAI\Responses\Images\CreateStreamedResponse;
910
use OpenAI\Responses\Images\EditResponse;
11+
use OpenAI\Responses\Images\EditStreamedResponse;
1012
use OpenAI\Responses\Images\VariationResponse;
13+
use OpenAI\Responses\StreamResponse;
1114
use OpenAI\ValueObjects\Transporter\Payload;
1215
use OpenAI\ValueObjects\Transporter\Response;
1316

1417
final class Images implements ImagesContract
1518
{
19+
use Concerns\Streamable;
1620
use Concerns\Transportable;
1721

1822
/**
@@ -24,6 +28,8 @@ final class Images implements ImagesContract
2428
*/
2529
public function create(array $parameters): CreateResponse
2630
{
31+
$this->ensureNotStreamed($parameters);
32+
2733
$payload = Payload::create('images/generations', $parameters);
2834

2935
/** @var Response<array{created: int, data: array<int, array{url?: string, b64_json?: string, revised_prompt?: string}>, usage?: array{total_tokens: int, input_tokens: int, output_tokens: int, input_tokens_details: array{text_tokens: int, image_tokens: int}}}> $response */
@@ -32,6 +38,25 @@ public function create(array $parameters): CreateResponse
3238
return CreateResponse::from($response->data(), $response->meta());
3339
}
3440

41+
/**
42+
* Creates a streamed image given a prompt.
43+
*
44+
* @see https://platform.openai.com/docs/api-reference/images/create
45+
*
46+
* @param array<string, mixed> $parameters
47+
* @return StreamResponse<CreateStreamedResponse>
48+
*/
49+
public function createStreamed(array $parameters): StreamResponse
50+
{
51+
$parameters = $this->setStreamParameter($parameters);
52+
53+
$payload = Payload::create('images/generations', $parameters);
54+
55+
$response = $this->transporter->requestStream($payload);
56+
57+
return new StreamResponse(CreateStreamedResponse::class, $response);
58+
}
59+
3560
/**
3661
* Creates an edited or extended image given an original image and a prompt.
3762
*
@@ -49,6 +74,24 @@ public function edit(array $parameters): EditResponse
4974
return EditResponse::from($response->data(), $response->meta());
5075
}
5176

77+
/**
78+
* Creates a streamed image edit given a prompt.
79+
*
80+
* @see https://platform.openai.com/docs/api-reference/images/create
81+
*
82+
* @param array<string, mixed> $parameters
83+
* @return StreamResponse<EditStreamedResponse>
84+
*/
85+
public function editStreamed(array $parameters): StreamResponse
86+
{
87+
$parameters = $this->setStreamParameter($parameters, 'true'); // Ensure the parameter is a string for upload
88+
89+
$payload = Payload::upload('images/edits', $parameters);
90+
$response = $this->transporter->requestStream($payload);
91+
92+
return new StreamResponse(EditStreamedResponse::class, $response);
93+
}
94+
5295
/**
5396
* Creates a variation of a given image.
5497
*
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Images;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Exceptions\UnknownEventException;
9+
use OpenAI\Responses\Concerns\ArrayAccessible;
10+
use OpenAI\Responses\Images\Streaming\Error;
11+
use OpenAI\Responses\Images\Streaming\ImageGenerationCompleted;
12+
use OpenAI\Responses\Images\Streaming\ImageGenerationPartialImage;
13+
use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse;
14+
15+
/**
16+
* @phpstan-type CreateStreamedResponseType array{event: string, data: array<string, mixed>}
17+
*
18+
* @implements ResponseContract<CreateStreamedResponseType>
19+
*/
20+
final class CreateStreamedResponse implements ResponseContract
21+
{
22+
/**
23+
* @use ArrayAccessible<CreateStreamedResponseType>
24+
*/
25+
use ArrayAccessible;
26+
27+
use FakeableForStreamedResponse;
28+
29+
private function __construct(
30+
public readonly string $event,
31+
public readonly ImageGenerationPartialImage|ImageGenerationCompleted|Error $response,
32+
) {}
33+
34+
/**
35+
* @param array<string, mixed> $attributes
36+
*/
37+
public static function from(array $attributes): self
38+
{
39+
$event = $attributes['type'] ?? throw new UnknownEventException('Missing event type in streamed response');
40+
$meta = $attributes['__meta'];
41+
unset($attributes['__meta']);
42+
43+
$response = match ($event) {
44+
'image_generation.partial_image' => ImageGenerationPartialImage::from($attributes, $meta), // @phpstan-ignore-line
45+
'image_generation.completed' => ImageGenerationCompleted::from($attributes, $meta), // @phpstan-ignore-line
46+
'error' => Error::from($attributes, $meta), // @phpstan-ignore-line
47+
default => throw new UnknownEventException('Unknown Images streaming event: '.$event),
48+
};
49+
50+
return new self(
51+
event: $event, // @phpstan-ignore-line
52+
response: $response,
53+
);
54+
}
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
public function toArray(): array
60+
{
61+
return [
62+
'event' => $this->event,
63+
'data' => $this->response->toArray(),
64+
];
65+
}
66+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Images;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Exceptions\UnknownEventException;
9+
use OpenAI\Responses\Concerns\ArrayAccessible;
10+
use OpenAI\Responses\Images\Streaming\Error;
11+
use OpenAI\Responses\Images\Streaming\ImageGenerationCompleted;
12+
use OpenAI\Responses\Images\Streaming\ImageGenerationPartialImage;
13+
use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse;
14+
15+
/**
16+
* @phpstan-type EditStreamedResponseType array{event: string, data: array<string, mixed>}
17+
*
18+
* @implements ResponseContract<EditStreamedResponseType>
19+
*/
20+
final class EditStreamedResponse implements ResponseContract
21+
{
22+
/**
23+
* @use ArrayAccessible<EditStreamedResponseType>
24+
*/
25+
use ArrayAccessible;
26+
27+
use FakeableForStreamedResponse;
28+
29+
private function __construct(
30+
public readonly string $event,
31+
public readonly ImageGenerationPartialImage|ImageGenerationCompleted|Error $response,
32+
) {}
33+
34+
/**
35+
* @param array<string, mixed> $attributes
36+
*/
37+
public static function from(array $attributes): self
38+
{
39+
$event = $attributes['type'] ?? throw new UnknownEventException('Missing event type in streamed response');
40+
$meta = $attributes['__meta'];
41+
unset($attributes['__meta']);
42+
43+
$response = match ($event) {
44+
'image_edit.partial_image' => ImageGenerationPartialImage::from($attributes, $meta), // @phpstan-ignore-line
45+
'image_edit.completed' => ImageGenerationCompleted::from($attributes, $meta), // @phpstan-ignore-line
46+
'error' => Error::from($attributes, $meta), // @phpstan-ignore-line
47+
default => throw new UnknownEventException('Unknown Images streaming event: '.$event),
48+
};
49+
50+
return new self(
51+
event: $event, // @phpstan-ignore-line
52+
response: $response,
53+
);
54+
}
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
public function toArray(): array
60+
{
61+
return [
62+
'event' => $this->event,
63+
'data' => $this->response->toArray(),
64+
];
65+
}
66+
}

src/Responses/Images/ImageResponseUsage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private function __construct(
1414
) {}
1515

1616
/**
17-
* @param array{total_tokens: int, input_tokens?: int, output_tokens?: int, input_tokens_details?: array{text_tokens: int, image_tokens: int}} $attributes
17+
* @param array{total_tokens: int, input_tokens?: int|null, output_tokens?: int|null, input_tokens_details?: array{text_tokens: int, image_tokens: int}|null} $attributes
1818
*/
1919
public static function from(array $attributes): self
2020
{
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Images\Streaming;
6+
7+
use OpenAI\Contracts\ResponseContract;
8+
use OpenAI\Contracts\ResponseHasMetaInformationContract;
9+
use OpenAI\Responses\Concerns\ArrayAccessible;
10+
use OpenAI\Responses\Concerns\HasMetaInformation;
11+
use OpenAI\Responses\Meta\MetaInformation;
12+
use OpenAI\Testing\Responses\Concerns\Fakeable;
13+
14+
/**
15+
* @phpstan-type ErrorType array{type: string, code: string|null, message: string, param: string|null}
16+
*
17+
* @implements ResponseContract<ErrorType>
18+
*/
19+
final class Error implements ResponseContract, ResponseHasMetaInformationContract
20+
{
21+
/**
22+
* @use ArrayAccessible<ErrorType>
23+
*/
24+
use ArrayAccessible;
25+
26+
use Fakeable;
27+
use HasMetaInformation;
28+
29+
private function __construct(
30+
public readonly string $type,
31+
public readonly ?string $code,
32+
public readonly string $message,
33+
public readonly ?string $param,
34+
private readonly MetaInformation $meta,
35+
) {}
36+
37+
/**
38+
* @param ErrorType $attributes
39+
*/
40+
public static function from(array $attributes, MetaInformation $meta): self
41+
{
42+
return new self(
43+
type: $attributes['type'],
44+
code: $attributes['code'],
45+
message: $attributes['message'],
46+
param: $attributes['param'],
47+
meta: $meta,
48+
);
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
public function toArray(): array
55+
{
56+
return [
57+
'type' => $this->type,
58+
'code' => $this->code,
59+
'message' => $this->message,
60+
'param' => $this->param,
61+
];
62+
}
63+
}

0 commit comments

Comments
 (0)