Skip to content

Commit 9d595e9

Browse files
wishbornclaude
andcommitted
test(openrouter): re-add streaming payload test for parameterless tools (prism-php#1004)
Replaces the unrunnable test dropped in 5c08d92: this one ships a real two-response SSE fixture and consumes the stream before asserting the request payload omits the parameters key for a parameterless tool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4d20868 commit 9d595e9

3 files changed

Lines changed: 109 additions & 0 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
data: {"id":"gen-1760518100-Kp3RtWnYxQBA2SAbiPRM","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518100,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
2+
3+
: OPENROUTER PROCESSING
4+
5+
: OPENROUTER PROCESSING
6+
7+
data: {"id":"gen-1760518100-Kp3RtWnYxQBA2SAbiPRM","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518100,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":"call_ZQpWJRxU5EIprhJ4cpxTMkNr","index":0,"type":"function","function":{"name":"time","arguments":""}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
8+
9+
data: {"id":"gen-1760518100-Kp3RtWnYxQBA2SAbiPRM","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518100,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"type":"function","function":{"arguments":"{}"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
10+
11+
: OPENROUTER PROCESSING
12+
13+
data: {"id":"gen-1760518100-Kp3RtWnYxQBA2SAbiPRM","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518100,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"tool_calls","native_finish_reason":"completed","logprobs":null}]}
14+
15+
data: {"id":"gen-1760518100-Kp3RtWnYxQBA2SAbiPRM","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518100,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":58,"completion_tokens":21,"total_tokens":79,"cost":0.0003825,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000725,"upstream_inference_completions_cost":0.00031},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
16+
17+
data: [DONE]
18+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
: OPENROUTER PROCESSING
2+
3+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
4+
5+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":"The"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
6+
7+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":" current"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
8+
9+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":" time"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
10+
11+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":" is"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
12+
13+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
14+
15+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":"08"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
16+
17+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":":"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
18+
19+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":"00"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
20+
21+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":":"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
22+
23+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":"00"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
24+
25+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
26+
27+
: OPENROUTER PROCESSING
28+
29+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"completed","logprobs":null}]}
30+
31+
data: {"id":"gen-1760518110-Vw82mQtRc4L9nYhuiWx1","provider":"OpenAI","model":"openai/gpt-5","object":"chat.completion.chunk","created":1760518110,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":86,"completion_tokens":13,"total_tokens":99,"cost":0.0002375,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0001075,"upstream_inference_completions_cost":0.00013},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
32+
33+
data: [DONE]
34+

tests/Providers/OpenRouter/StreamTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,63 @@
248248
expect($streamEndEvents)->not->toBeEmpty();
249249
});
250250

251+
it('sends correct tool payload when streaming with a parameterless tool', function (): void {
252+
FixtureResponse::fakeStreamResponses('v1/chat/completions', 'openrouter/stream-text-with-parameterless-tool');
253+
254+
$currentTime = '08:00:00';
255+
256+
$timeTool = Tool::as('time')
257+
->for('Get the current time')
258+
->using(fn (): string => $currentTime);
259+
260+
$searchTool = Tool::as('search')
261+
->for('Search the web')
262+
->withStringParameter('query', 'the search query')
263+
->using(fn (string $query): string => 'results');
264+
265+
$response = Prism::text()
266+
->using(Provider::OpenRouter, 'openai/gpt-5')
267+
->withTools([$timeTool, $searchTool])
268+
->withMaxSteps(3)
269+
->withPrompt('What time is it?')
270+
->asStream();
271+
272+
$text = '';
273+
$toolCallEvents = [];
274+
275+
foreach ($response as $event) {
276+
if ($event instanceof TextDeltaEvent) {
277+
$text .= $event->delta;
278+
}
279+
280+
if ($event instanceof ToolCallEvent) {
281+
$toolCallEvents[] = $event;
282+
}
283+
}
284+
285+
expect($toolCallEvents)->toHaveCount(1);
286+
expect($toolCallEvents[0]->toolCall->name)->toBe('time');
287+
expect($toolCallEvents[0]->toolCall->arguments())->toBe([]);
288+
expect($text)->toContain('The current time is '.$currentTime);
289+
290+
Http::assertSent(function (Request $request): bool {
291+
$tools = $request->data()['tools'];
292+
293+
// Tool without parameters: no 'parameters' key in function
294+
$timeFunction = $tools[0]['function'];
295+
expect($timeFunction['name'])->toBe('time');
296+
expect($timeFunction)->not->toHaveKey('parameters');
297+
298+
// Tool with parameters keeps its 'parameters' key
299+
$searchFunction = $tools[1]['function'];
300+
expect($searchFunction['name'])->toBe('search');
301+
expect($searchFunction)->toHaveKey('parameters');
302+
expect($searchFunction['parameters']['properties'])->toHaveKey('query');
303+
304+
return true;
305+
});
306+
});
307+
251308
it('can handle reasoning/thinking tokens in streaming', function (): void {
252309
FixtureResponse::fakeStreamResponses('v1/chat/completions', 'openrouter/stream-text-with-reasoning');
253310

0 commit comments

Comments
 (0)