Skip to content

Commit a0cc3d6

Browse files
authored
feat(OpenAI) - Add helper property 'output_text' to Responses API (#579)
* feat: support 'output_text' on Responses API * fix: support null on 'output_text'
1 parent 3992298 commit a0cc3d6

3 files changed

Lines changed: 53 additions & 2 deletions

File tree

src/Responses/Responses/CreateResponse.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OpenAI\Responses\Responses\Output\OutputFileSearchToolCall;
1414
use OpenAI\Responses\Responses\Output\OutputFunctionToolCall;
1515
use OpenAI\Responses\Responses\Output\OutputMessage;
16+
use OpenAI\Responses\Responses\Output\OutputMessageContentOutputText;
1617
use OpenAI\Responses\Responses\Output\OutputReasoning;
1718
use OpenAI\Responses\Responses\Output\OutputWebSearchToolCall;
1819
use OpenAI\Responses\Responses\Tool\ComputerUseTool;
@@ -45,7 +46,7 @@
4546
* @phpstan-type ToolChoiceType 'none'|'auto'|'required'|FunctionToolChoiceType|HostedToolChoiceType
4647
* @phpstan-type ToolsType array<int, ComputerUseToolType|FileSearchToolType|FunctionToolType|WebSearchToolType>
4748
* @phpstan-type OutputType array<int, OutputComputerToolCallType|OutputFileSearchToolCallType|OutputFunctionToolCallType|OutputMessageType|OutputReasoningType|OutputWebSearchToolCallType>
48-
* @phpstan-type CreateResponseType array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: ErrorType|null, incomplete_details: IncompleteDetailsType|null, instructions: string|null, max_output_tokens: int|null, model: string, output: OutputType, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ReasoningType|null, store: bool, temperature: float|null, text: ResponseFormatType, tool_choice: ToolChoiceType, tools: ToolsType, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: UsageType|null, user: string|null, metadata: array<string, string>|null}
49+
* @phpstan-type CreateResponseType array{id: string, object: 'response', created_at: int, status: 'completed'|'failed'|'in_progress'|'incomplete', error: ErrorType|null, incomplete_details: IncompleteDetailsType|null, instructions: string|null, max_output_tokens: int|null, model: string, output: OutputType, output_text: string|null, parallel_tool_calls: bool, previous_response_id: string|null, reasoning: ReasoningType|null, store: bool, temperature: float|null, text: ResponseFormatType, tool_choice: ToolChoiceType, tools: ToolsType, top_p: float|null, truncation: 'auto'|'disabled'|null, usage: UsageType|null, user: string|null, metadata: array<string, string>|null}
4950
*
5051
* @implements ResponseContract<CreateResponseType>
5152
*/
@@ -78,6 +79,7 @@ private function __construct(
7879
public readonly ?int $maxOutputTokens,
7980
public readonly string $model,
8081
public readonly array $output,
82+
public readonly ?string $outputText,
8183
public readonly bool $parallelToolCalls,
8284
public readonly ?string $previousResponseId,
8385
public readonly ?CreateResponseReasoning $reasoning,
@@ -128,6 +130,18 @@ public static function from(array $attributes, MetaInformation $meta): self
128130
$attributes['tools'],
129131
);
130132

133+
// Remake the sdk only property output_text.
134+
$texts = [];
135+
foreach ($output as $item) {
136+
if ($item instanceof OutputMessage) {
137+
foreach ($item->content as $content) {
138+
if ($content instanceof OutputMessageContentOutputText) {
139+
$texts[] = $content->text;
140+
}
141+
}
142+
}
143+
}
144+
131145
return new self(
132146
id: $attributes['id'],
133147
object: $attributes['object'],
@@ -143,6 +157,7 @@ public static function from(array $attributes, MetaInformation $meta): self
143157
maxOutputTokens: $attributes['max_output_tokens'],
144158
model: $attributes['model'],
145159
output: $output,
160+
outputText: empty($texts) ? null : implode(' ', $texts),
146161
parallelToolCalls: $attributes['parallel_tool_calls'],
147162
previousResponseId: $attributes['previous_response_id'],
148163
reasoning: isset($attributes['reasoning'])
@@ -203,6 +218,7 @@ public function toArray(): array
203218
'truncation' => $this->truncation,
204219
'usage' => $this->usage?->toArray(),
205220
'user' => $this->user,
221+
'output_text' => $this->outputText,
206222
];
207223
}
208224
}

tests/Fixtures/Responses.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,25 @@ function outputMessage(): array
290290
];
291291
}
292292

293+
/**
294+
* @return array<string, mixed>
295+
*/
296+
function outputMessageOnlyRefusal(): array
297+
{
298+
return [
299+
'content' => [
300+
[
301+
'refusal' => 'The assistant refused to answer.',
302+
'type' => 'refusal',
303+
],
304+
],
305+
'id' => 'msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c',
306+
'role' => 'assistant',
307+
'status' => 'completed',
308+
'type' => 'message',
309+
];
310+
}
311+
293312
/**
294313
* @return array<string, mixed>
295314
*/

tests/Responses/Responses/CreateResponse.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,25 @@
4848
test('to array', function () {
4949
$response = CreateResponse::from(createResponseResource(), meta());
5050

51+
$expected = createResponseResource();
52+
$expected['output_text'] = 'As of today, March 9, 2025, one notable positive news story...';
53+
54+
expect($response->toArray())
55+
->toBeArray()
56+
->toBe($expected);
57+
});
58+
59+
test('to array with no messages', function () {
60+
$payload = createResponseResource();
61+
$payload['output'] = [
62+
outputMessageOnlyRefusal(),
63+
];
64+
65+
$response = CreateResponse::from($payload, meta());
66+
5167
expect($response->toArray())
5268
->toBeArray()
53-
->toBe(createResponseResource());
69+
->outputText->toBeNull();
5470
});
5571

5672
test('fake', function () {

0 commit comments

Comments
 (0)