Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 094d9ee

Browse files
ineersachr-hertel
authored andcommitted
Fix streaming conversion in CompletionsConversionTrait. Update tests to validate the correct handling of streaming events, including reasoning and tool calls.
1 parent 9e206f8 commit 094d9ee

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

Completions/CompletionsConversionTrait.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ protected function convertStream(RawResultInterface $result): \Generator
5858
if (null !== $reasoningContent && '' !== $reasoningContent) {
5959
$reasoning .= $reasoningContent;
6060
yield new ThinkingDelta($reasoningContent);
61-
continue;
6261
}
6362

6463
if ('' !== $reasoning && isset($data['choices'][0]['delta']['content']) && '' !== $data['choices'][0]['delta']['content']) {

Tests/Completions/ResultConverterTest.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
use Symfony\AI\Platform\Exception\ContentFilterException;
1919
use Symfony\AI\Platform\Exception\RuntimeException;
2020
use Symfony\AI\Platform\Result\ChoiceResult;
21+
use Symfony\AI\Platform\Result\InMemoryRawResult;
2122
use Symfony\AI\Platform\Result\RawHttpResult;
23+
use Symfony\AI\Platform\Result\Stream\Delta\TextDelta;
24+
use Symfony\AI\Platform\Result\Stream\Delta\ThinkingComplete;
25+
use Symfony\AI\Platform\Result\Stream\Delta\ThinkingDelta;
26+
use Symfony\AI\Platform\Result\Stream\Delta\ToolCallComplete;
27+
use Symfony\AI\Platform\Result\Stream\Delta\ToolCallStart;
28+
use Symfony\AI\Platform\Result\Stream\Delta\ToolInputDelta;
29+
use Symfony\AI\Platform\Result\StreamResult;
2230
use Symfony\AI\Platform\Result\TextResult;
2331
use Symfony\AI\Platform\Result\ToolCallResult;
2432
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
@@ -316,4 +324,86 @@ public function testThrowsDetailedErrorException()
316324

317325
$converter->convert(new RawHttpResult($httpResponse));
318326
}
327+
328+
public function testStreamingInterleavedReasoningContentAndToolCalls()
329+
{
330+
$converter = new ResultConverter();
331+
332+
$events = [
333+
['choices' => [['index' => 0, 'delta' => ['reasoning_content' => 'I need to check the weather']]]],
334+
['choices' => [['index' => 0, 'delta' => ['reasoning_content' => 'Let me call the tool']]]],
335+
['choices' => [['index' => 0, 'delta' => ['content' => 'Let me check']]]],
336+
['choices' => [['index' => 0, 'delta' => [
337+
'tool_calls' => [
338+
[
339+
'id' => 'call_1',
340+
'type' => 'function',
341+
'function' => [
342+
'name' => 'get_weather',
343+
'arguments' => '',
344+
],
345+
],
346+
],
347+
]]]],
348+
['choices' => [['index' => 0, 'delta' => [
349+
'tool_calls' => [
350+
[
351+
'function' => [
352+
'arguments' => '{"city":"Beijing"}',
353+
],
354+
],
355+
],
356+
]]]],
357+
['choices' => [['index' => 0, 'delta' => [], 'finish_reason' => 'tool_calls']]],
358+
];
359+
360+
$httpLike = new class {
361+
public function getStatusCode(): int
362+
{
363+
return 200;
364+
}
365+
};
366+
367+
$raw = new InMemoryRawResult([], $events, $httpLike);
368+
$streamResult = $converter->convert($raw, ['stream' => true]);
369+
370+
$this->assertInstanceOf(StreamResult::class, $streamResult);
371+
372+
$chunks = [];
373+
foreach ($streamResult->getContent() as $part) {
374+
$chunks[] = $part;
375+
}
376+
377+
$thinkingDeltas = array_values(array_filter($chunks, static fn ($c) => $c instanceof ThinkingDelta));
378+
$this->assertCount(2, $thinkingDeltas);
379+
$this->assertSame('I need to check the weather', $thinkingDeltas[0]->getThinking());
380+
$this->assertSame('Let me call the tool', $thinkingDeltas[1]->getThinking());
381+
382+
$thinkingCompletes = array_values(array_filter($chunks, static fn ($c) => $c instanceof ThinkingComplete));
383+
$this->assertCount(1, $thinkingCompletes);
384+
$this->assertSame('I need to check the weatherLet me call the tool', $thinkingCompletes[0]->getThinking());
385+
386+
$textDeltas = array_values(array_filter($chunks, static fn ($c) => $c instanceof TextDelta));
387+
$this->assertCount(1, $textDeltas);
388+
$this->assertSame('Let me check', $textDeltas[0]->getText());
389+
390+
$toolCallStarts = array_values(array_filter($chunks, static fn ($c) => $c instanceof ToolCallStart));
391+
$this->assertCount(1, $toolCallStarts);
392+
$this->assertSame('call_1', $toolCallStarts[0]->getId());
393+
$this->assertSame('get_weather', $toolCallStarts[0]->getName());
394+
395+
$toolInputDeltas = array_values(array_filter($chunks, static fn ($c) => $c instanceof ToolInputDelta));
396+
$this->assertCount(1, $toolInputDeltas);
397+
$this->assertSame('call_1', $toolInputDeltas[0]->getId());
398+
$this->assertSame('get_weather', $toolInputDeltas[0]->getName());
399+
$this->assertSame('{"city":"Beijing"}', $toolInputDeltas[0]->getPartialJson());
400+
401+
$toolCallCompletes = array_values(array_filter($chunks, static fn ($c) => $c instanceof ToolCallComplete));
402+
$this->assertCount(1, $toolCallCompletes);
403+
$completed = $toolCallCompletes[0]->getToolCalls();
404+
$this->assertCount(1, $completed);
405+
$this->assertSame('call_1', $completed[0]->getId());
406+
$this->assertSame('get_weather', $completed[0]->getName());
407+
$this->assertSame(['city' => 'Beijing'], $completed[0]->getArguments());
408+
}
319409
}

0 commit comments

Comments
 (0)