Skip to content

Commit eab26e6

Browse files
authored
fix(OpenAI): Add support for empty 'keepalive' stream event. (#734)
* fix(OpenAI): support 'keepalive' stream event * fix(OpenAI): adjust tests for 'keepalive' * fix(OpenAI): adjust tests for 'keepalive' on responses
1 parent 18423c9 commit eab26e6

5 files changed

Lines changed: 38 additions & 1 deletion

File tree

src/Responses/StreamResponse.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ public function getIterator(): Generator
6060
throw new ErrorException($response['error'], $this->response);
6161
}
6262

63-
if (isset($response['type']) && $response['type'] === 'ping') {
63+
$skippableTypes = ['ping', 'keepalive'];
64+
if (isset($response['type']) && in_array($response['type'], $skippableTypes, true)) {
6465
continue;
6566
}
6667

tests/Fixtures/Chat.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,14 @@ function chatCompletionStreamPing()
738738
return fopen(__DIR__.'/Streams/ChatCompletionPing.txt', 'r');
739739
}
740740

741+
/**
742+
* @return resource
743+
*/
744+
function chatCompletionStreamKeepAlive()
745+
{
746+
return fopen(__DIR__.'/Streams/ChatCompletionKeepAlive.txt', 'r');
747+
}
748+
741749
/**
742750
* @return resource
743751
*/
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
data: {"id":"msg_0111RgCFCqN68mJbev6Rq1cz","choices":[{"index":0,"delta":{"role":"assistant"}}],"created":1744469024,"model":"claude-3-7-sonnet-20250219","object":"chat.completion.chunk"}
2+
data: {"type": "keepalive"}
3+
data: {"id":"msg_0111RgCFCqN68mJbev6Rq1cz","choices":[{"index":0,"delta":{"content":"Hello!"}}],"created":1744469024,"model":"claude-3-7-sonnet-20250219","object":"chat.completion.chunk"}
4+
data: [DONE]

tests/Fixtures/Streams/ResponseCompletionCreate.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ data: {"type":"response.output_text.done","item_id":"msg_67ccf190ca3881909d433c5
99
data: {"type":"response.content_part.done","item_id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","output_index":1,"content_index":0,"part":{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}, "sequence_number": 9}
1010
data: {"type":"response.output_item.done","output_index":1,"item":{"id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}]}, "sequence_number": 10}
1111
data: {"type":"response.completed","response":{"id":"resp_67ccf18ef5fc8190b16dbee19bc54e5f087bb177ab789d5c","object":"response","created_at":1741484430,"status":"completed","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-4o-2024-08-06","output":[{"type":"web_search_call","id":"ws_67ccf18f64008190a39b619f4c8455ef087bb177ab789d5c","status":"completed","action":{"type":"search","query":"what was a positive news story from today?","sources":[{"type":"url","url":"https://example.com/news/positive-story"},{"type":"url","url":"https://another.example.com/related-article"}]}},{"type":"message","id":"msg_67ccf190ca3881909d433c50b1f6357e087bb177ab789d5c","status":"completed","role":"assistant","content":[{"type":"output_text","text":"As of today, March 9, 2025, one notable positive news story...","annotations":[{"type":"url_citation","start_index":442,"end_index":557,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":962,"end_index":1077,"url":"https://.../?utm_source=chatgpt.com","title":"..."},{"type":"url_citation","start_index":1336,"end_index":1451,"url":"https://.../?utm_source=chatgpt.com","title":"..."}]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"store":true,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"web_search_preview","domains":[],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":328,"input_tokens_details":{"cached_tokens":0},"output_tokens":356,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":684},"user":null,"metadata":{}}, "sequence_number": 11}
12+
data: {"type":"keepalive", "sequence_number": 12}

tests/Resources/Chat.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,29 @@
128128
}
129129
});
130130

131+
test('handles keepalive messages in stream', function () {
132+
$response = new Response(
133+
headers: metaHeaders(),
134+
body: new Stream(chatCompletionStreamKeepAlive()),
135+
);
136+
137+
$client = mockStreamClient('POST', 'chat/completions', [
138+
'model' => 'gpt-3.5-turbo',
139+
'messages' => ['role' => 'user', 'content' => 'Hello!'],
140+
'stream' => true,
141+
], $response);
142+
143+
$stream = $client->chat()->createStreamed([
144+
'model' => 'gpt-3.5-turbo',
145+
'messages' => ['role' => 'user', 'content' => 'Hello!'],
146+
]);
147+
148+
foreach ($stream as $response) {
149+
expect($response)
150+
->toBeInstanceOf(CreateStreamedResponse::class);
151+
}
152+
});
153+
131154
test('handles error messages in stream', function () {
132155
$response = new Response(
133156
body: new Stream(chatCompletionStreamError())

0 commit comments

Comments
 (0)