Skip to content

Commit 166ab20

Browse files
authored
fix(OpenAI): Add support for reasoning text streaming events (#673)
* Add support for `response.reasoning_text.delta` and `response.reasoning_text.done` events. * Remove `type` attribute from `ReasoningTextDelta` and `ReasoningTextDone` responses.
1 parent a00d775 commit 166ab20

File tree

7 files changed

+179
-1
lines changed

7 files changed

+179
-1
lines changed

src/Responses/Responses/CreateStreamedResponse.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use OpenAI\Responses\Responses\Streaming\ReasoningSummaryPart;
3030
use OpenAI\Responses\Responses\Streaming\ReasoningSummaryTextDelta;
3131
use OpenAI\Responses\Responses\Streaming\ReasoningSummaryTextDone;
32+
use OpenAI\Responses\Responses\Streaming\ReasoningTextDelta;
33+
use OpenAI\Responses\Responses\Streaming\ReasoningTextDone;
3234
use OpenAI\Responses\Responses\Streaming\RefusalDelta;
3335
use OpenAI\Responses\Responses\Streaming\RefusalDone;
3436
use OpenAI\Responses\Responses\Streaming\WebSearchCall;
@@ -50,7 +52,7 @@ final class CreateStreamedResponse implements ResponseContract
5052

5153
private function __construct(
5254
public readonly string $event,
53-
public readonly CreateResponse|OutputItem|ContentPart|OutputTextDelta|OutputTextAnnotationAdded|OutputTextDone|RefusalDelta|RefusalDone|FunctionCallArgumentsDelta|FunctionCallArgumentsDone|FileSearchCall|WebSearchCall|CodeInterpreterCall|CodeInterpreterCodeDelta|CodeInterpreterCodeDone|ReasoningSummaryPart|ReasoningSummaryTextDelta|ReasoningSummaryTextDone|McpListTools|McpListToolsInProgress|McpCall|McpCallArgumentsDelta|McpCallArgumentsDone|ImageGenerationPart|ImageGenerationPartialImage|Error $response,
55+
public readonly CreateResponse|OutputItem|ContentPart|OutputTextDelta|OutputTextAnnotationAdded|OutputTextDone|RefusalDelta|RefusalDone|FunctionCallArgumentsDelta|FunctionCallArgumentsDone|FileSearchCall|WebSearchCall|CodeInterpreterCall|CodeInterpreterCodeDelta|CodeInterpreterCodeDone|ReasoningSummaryPart|ReasoningSummaryTextDelta|ReasoningSummaryTextDone|ReasoningTextDelta|ReasoningTextDone|McpListTools|McpListToolsInProgress|McpCall|McpCallArgumentsDelta|McpCallArgumentsDone|ImageGenerationPart|ImageGenerationPartialImage|Error $response,
5456
) {}
5557

5658
/**
@@ -95,6 +97,8 @@ public static function from(array $attributes): self
9597
'response.reasoning_summary_part.done' => ReasoningSummaryPart::from($attributes, $meta), // @phpstan-ignore-line
9698
'response.reasoning_summary_text.delta' => ReasoningSummaryTextDelta::from($attributes, $meta), // @phpstan-ignore-line
9799
'response.reasoning_summary_text.done' => ReasoningSummaryTextDone::from($attributes, $meta), // @phpstan-ignore-line
100+
'response.reasoning_text.delta' => ReasoningTextDelta::from($attributes, $meta), // @phpstan-ignore-line
101+
'response.reasoning_text.done' => ReasoningTextDone::from($attributes, $meta), // @phpstan-ignore-line
98102
'response.mcp_list_tools.in_progress' => McpListToolsInProgress::from($attributes, $meta), // @phpstan-ignore-line
99103
'response.mcp_list_tools.failed',
100104
'response.mcp_list_tools.completed' => McpListTools::from($attributes, $meta), // @phpstan-ignore-line
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\Responses\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 ReasoningTextDeltaType array{content_index: int, delta: string, item_id: string, output_index: int, sequence_number: int}
16+
*
17+
* @implements ResponseContract<ReasoningTextDeltaType>
18+
*/
19+
final class ReasoningTextDelta implements ResponseContract, ResponseHasMetaInformationContract
20+
{
21+
/**
22+
* @use ArrayAccessible<ReasoningTextDeltaType>
23+
*/
24+
use ArrayAccessible;
25+
26+
use Fakeable;
27+
use HasMetaInformation;
28+
29+
private function __construct(
30+
public readonly int $contentIndex,
31+
public readonly string $delta,
32+
public readonly string $itemId,
33+
public readonly int $outputIndex,
34+
public readonly int $sequenceNumber,
35+
private readonly MetaInformation $meta,
36+
) {}
37+
38+
/**
39+
* @param ReasoningTextDeltaType $attributes
40+
*/
41+
public static function from(array $attributes, MetaInformation $meta): self
42+
{
43+
return new self(
44+
contentIndex: $attributes['content_index'],
45+
delta: $attributes['delta'],
46+
itemId: $attributes['item_id'],
47+
outputIndex: $attributes['output_index'],
48+
sequenceNumber: $attributes['sequence_number'],
49+
meta: $meta,
50+
);
51+
}
52+
53+
/**
54+
* {@inheritDoc}
55+
*/
56+
public function toArray(): array
57+
{
58+
return [
59+
'content_index' => $this->contentIndex,
60+
'delta' => $this->delta,
61+
'item_id' => $this->itemId,
62+
'output_index' => $this->outputIndex,
63+
'sequence_number' => $this->sequenceNumber,
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\Responses\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 ReasoningTextDoneType array{content_index: int, item_id: string, output_index: int, sequence_number: int, text: string}
16+
*
17+
* @implements ResponseContract<ReasoningTextDoneType>
18+
*/
19+
final class ReasoningTextDone implements ResponseContract, ResponseHasMetaInformationContract
20+
{
21+
/**
22+
* @use ArrayAccessible<ReasoningTextDoneType>
23+
*/
24+
use ArrayAccessible;
25+
26+
use Fakeable;
27+
use HasMetaInformation;
28+
29+
private function __construct(
30+
public readonly int $contentIndex,
31+
public readonly string $itemId,
32+
public readonly int $outputIndex,
33+
public readonly int $sequenceNumber,
34+
public readonly string $text,
35+
private readonly MetaInformation $meta,
36+
) {}
37+
38+
/**
39+
* @param ReasoningTextDoneType $attributes
40+
*/
41+
public static function from(array $attributes, MetaInformation $meta): self
42+
{
43+
return new self(
44+
contentIndex: $attributes['content_index'],
45+
itemId: $attributes['item_id'],
46+
outputIndex: $attributes['output_index'],
47+
sequenceNumber: $attributes['sequence_number'],
48+
text: $attributes['text'],
49+
meta: $meta,
50+
);
51+
}
52+
53+
/**
54+
* {@inheritDoc}
55+
*/
56+
public function toArray(): array
57+
{
58+
return [
59+
'content_index' => $this->contentIndex,
60+
'item_id' => $this->itemId,
61+
'output_index' => $this->outputIndex,
62+
'sequence_number' => $this->sequenceNumber,
63+
'text' => $this->text,
64+
];
65+
}
66+
}

tests/Fixtures/Responses.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,3 +760,13 @@ function responseCompletionSteamCreatedEvent()
760760
{
761761
return fopen(__DIR__.'/Streams/ResponseCreatedResponse.txt', 'r');
762762
}
763+
764+
function responseReasoningTextDeltaEvent()
765+
{
766+
return fopen(__DIR__.'/Streams/ResponseReasoningTextDelta.txt', 'r');
767+
}
768+
769+
function responseReasoningTextDoneEvent()
770+
{
771+
return fopen(__DIR__.'/Streams/ResponseReasoningTextDone.txt', 'r');
772+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data: {"type":"response.reasoning_text.delta","content_index":0,"delta":"Let me analyze","item_id":"item_123","output_index":0,"sequence_number":5}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data: {"type":"response.reasoning_text.done","content_index":0,"item_id":"item_123","output_index":0,"sequence_number":10,"text":"Let me analyze this problem step by step to provide the best solution."}

tests/Responses/Responses/CreateStreamedResponse.php

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

33
use OpenAI\Responses\Responses\CreateResponse;
44
use OpenAI\Responses\Responses\CreateStreamedResponse;
5+
use OpenAI\Responses\Responses\Streaming\ReasoningTextDelta;
6+
use OpenAI\Responses\Responses\Streaming\ReasoningTextDone;
57

68
test('fake', function () {
79
$response = CreateStreamedResponse::fake();
@@ -20,3 +22,31 @@
2022
->response->toBeInstanceOf(CreateResponse::class)
2123
->response->id->toBe('resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c');
2224
});
25+
26+
test('reasoning text delta event', function () {
27+
$response = CreateStreamedResponse::fake(responseReasoningTextDeltaEvent());
28+
29+
expect($response->getIterator()->current())
30+
->toBeInstanceOf(CreateStreamedResponse::class)
31+
->event->toBe('response.reasoning_text.delta')
32+
->response->toBeInstanceOf(ReasoningTextDelta::class)
33+
->response->delta->toBe('Let me analyze')
34+
->response->itemId->toBe('item_123')
35+
->response->outputIndex->toBe(0)
36+
->response->contentIndex->toBe(0)
37+
->response->sequenceNumber->toBe(5);
38+
});
39+
40+
test('reasoning text done event', function () {
41+
$response = CreateStreamedResponse::fake(responseReasoningTextDoneEvent());
42+
43+
expect($response->getIterator()->current())
44+
->toBeInstanceOf(CreateStreamedResponse::class)
45+
->event->toBe('response.reasoning_text.done')
46+
->response->toBeInstanceOf(ReasoningTextDone::class)
47+
->response->text->toBe('Let me analyze this problem step by step to provide the best solution.')
48+
->response->itemId->toBe('item_123')
49+
->response->outputIndex->toBe(0)
50+
->response->contentIndex->toBe(0)
51+
->response->sequenceNumber->toBe(10);
52+
});

0 commit comments

Comments
 (0)