Skip to content

Commit 750ab79

Browse files
committed
Simplify choice handling
1 parent ab882c8 commit 750ab79

File tree

10 files changed

+31
-184
lines changed

10 files changed

+31
-184
lines changed

src/agent/phpstan.dist.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ parameters:
1515
-
1616
identifier: 'symfonyAi.forbidNativeException'
1717
path: tests/*
18+
-
19+
message: "#^Method .*::test.*\\(\\) has no return type specified\\.$#"
20+
path: tests/*

src/agent/tests/StructuredOutput/AgentProcessorTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
use Symfony\AI\Platform\Capability;
2525
use Symfony\AI\Platform\Message\MessageBag;
2626
use Symfony\AI\Platform\Model;
27-
use Symfony\AI\Platform\Result\Choice;
2827
use Symfony\AI\Platform\Result\Metadata\Metadata;
2928
use Symfony\AI\Platform\Result\ObjectResult;
3029
use Symfony\AI\Platform\Result\TextResult;
@@ -34,7 +33,6 @@
3433
#[UsesClass(Input::class)]
3534
#[UsesClass(Output::class)]
3635
#[UsesClass(MessageBag::class)]
37-
#[UsesClass(Choice::class)]
3836
#[UsesClass(MissingModelSupportException::class)]
3937
#[UsesClass(TextResult::class)]
4038
#[UsesClass(ObjectResult::class)]

src/platform/src/Bridge/Gemini/Gemini/ResultConverter.php

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Symfony\AI\Platform\Bridge\Gemini\Gemini;
1515
use Symfony\AI\Platform\Exception\RuntimeException;
1616
use Symfony\AI\Platform\Model;
17-
use Symfony\AI\Platform\Result\Choice;
1817
use Symfony\AI\Platform\Result\ChoiceResult;
1918
use Symfony\AI\Platform\Result\RawHttpResult;
2019
use Symfony\AI\Platform\Result\RawResultInterface;
@@ -49,18 +48,9 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
4948
throw new RuntimeException('Response does not contain any content.');
5049
}
5150

52-
/** @var Choice[] $choices */
5351
$choices = array_map($this->convertChoice(...), $data['candidates']);
5452

55-
if (1 !== \count($choices)) {
56-
return new ChoiceResult(...$choices);
57-
}
58-
59-
if ($choices[0]->hasToolCall()) {
60-
return new ToolCallResult(...$choices[0]->getToolCalls());
61-
}
62-
63-
return new TextResult($choices[0]->getContent());
53+
return 1 === \count($choices) ? $choices[0] : new ChoiceResult(...$choices);
6454
}
6555

6656
private function convertStream(HttpResponse $result): \Generator
@@ -94,7 +84,6 @@ private function convertStream(HttpResponse $result): \Generator
9484
throw new RuntimeException('Failed to decode JSON response.', 0, $e);
9585
}
9686

97-
/** @var Choice[] $choices */
9887
$choices = array_map($this->convertChoice(...), $data['candidates'] ?? []);
9988

10089
if (!$choices) {
@@ -106,13 +95,7 @@ private function convertStream(HttpResponse $result): \Generator
10695
continue;
10796
}
10897

109-
if ($choices[0]->hasToolCall()) {
110-
yield new ToolCallResult(...$choices[0]->getToolCalls());
111-
}
112-
113-
if ($choices[0]->hasContent()) {
114-
yield $choices[0]->getContent();
115-
}
98+
yield $choices[0]->getContent();
11699
}
117100
}
118101
}
@@ -132,16 +115,16 @@ private function convertStream(HttpResponse $result): \Generator
132115
* }
133116
* } $choice
134117
*/
135-
private function convertChoice(array $choice): Choice
118+
private function convertChoice(array $choice): ToolCallResult|TextResult
136119
{
137120
$contentPart = $choice['content']['parts'][0] ?? [];
138121

139122
if (isset($contentPart['functionCall'])) {
140-
return new Choice(toolCalls: [$this->convertToolCall($contentPart['functionCall'])]);
123+
return new ToolCallResult($this->convertToolCall($contentPart['functionCall']));
141124
}
142125

143126
if (isset($contentPart['text'])) {
144-
return new Choice($contentPart['text']);
127+
return new TextResult($contentPart['text']);
145128
}
146129

147130
throw new RuntimeException(\sprintf('Unsupported finish reason "%s".', $choice['finishReason']));

src/platform/src/Bridge/Mistral/Llm/ResultConverter.php

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Symfony\AI\Platform\Bridge\Mistral\Mistral;
1515
use Symfony\AI\Platform\Exception\RuntimeException;
1616
use Symfony\AI\Platform\Model;
17-
use Symfony\AI\Platform\Result\Choice;
1817
use Symfony\AI\Platform\Result\ChoiceResult;
1918
use Symfony\AI\Platform\Result\RawHttpResult;
2019
use Symfony\AI\Platform\Result\RawResultInterface;
@@ -60,18 +59,9 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
6059
throw new RuntimeException('Response does not contain choices.');
6160
}
6261

63-
/** @var Choice[] $choices */
6462
$choices = array_map($this->convertChoice(...), $data['choices']);
6563

66-
if (1 !== \count($choices)) {
67-
return new ChoiceResult(...$choices);
68-
}
69-
70-
if ($choices[0]->hasToolCall()) {
71-
return new ToolCallResult(...$choices[0]->getToolCalls());
72-
}
73-
74-
return new TextResult($choices[0]->getContent());
64+
return 1 === \count($choices) ? $choices[0] : new ChoiceResult(...$choices);
7565
}
7666

7767
private function convertStream(HttpResponse $result): \Generator
@@ -170,14 +160,14 @@ private function isToolCallsStreamFinished(array $data): bool
170160
* finish_reason: 'stop'|'length'|'tool_calls'|'content_filter',
171161
* } $choice
172162
*/
173-
private function convertChoice(array $choice): Choice
163+
private function convertChoice(array $choice): ToolCallResult|TextResult
174164
{
175165
if ('tool_calls' === $choice['finish_reason']) {
176-
return new Choice(toolCalls: array_map([$this, 'convertToolCall'], $choice['message']['tool_calls']));
166+
return new ToolCallResult(...array_map([$this, 'convertToolCall'], $choice['message']['tool_calls']));
177167
}
178168

179169
if ('stop' === $choice['finish_reason']) {
180-
return new Choice($choice['message']['content']);
170+
return new TextResult($choice['message']['content']);
181171
}
182172

183173
throw new RuntimeException(\sprintf('Unsupported finish reason "%s".', $choice['finish_reason']));

src/platform/src/Bridge/OpenAi/Gpt/ResultConverter.php

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\AI\Platform\Exception\ContentFilterException;
1616
use Symfony\AI\Platform\Exception\RuntimeException;
1717
use Symfony\AI\Platform\Model;
18-
use Symfony\AI\Platform\Result\Choice;
1918
use Symfony\AI\Platform\Result\ChoiceResult;
2019
use Symfony\AI\Platform\Result\RawHttpResult;
2120
use Symfony\AI\Platform\Result\RawResultInterface;
@@ -57,18 +56,9 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
5756
throw new RuntimeException('Response does not contain choices.');
5857
}
5958

60-
/** @var Choice[] $choices */
6159
$choices = array_map($this->convertChoice(...), $data['choices']);
6260

63-
if (1 !== \count($choices)) {
64-
return new ChoiceResult(...$choices);
65-
}
66-
67-
if ($choices[0]->hasToolCall()) {
68-
return new ToolCallResult(...$choices[0]->getToolCalls());
69-
}
70-
71-
return new TextResult($choices[0]->getContent());
61+
return 1 === \count($choices) ? $choices[0] : new ChoiceResult(...$choices);
7262
}
7363

7464
private function convertStream(HttpResponse $result): \Generator
@@ -167,14 +157,14 @@ private function isToolCallsStreamFinished(array $data): bool
167157
* finish_reason: 'stop'|'length'|'tool_calls'|'content_filter',
168158
* } $choice
169159
*/
170-
private function convertChoice(array $choice): Choice
160+
private function convertChoice(array $choice): ToolCallResult|TextResult
171161
{
172162
if ('tool_calls' === $choice['finish_reason']) {
173-
return new Choice(toolCalls: array_map([$this, 'convertToolCall'], $choice['message']['tool_calls']));
163+
return new ToolCallResult(...array_map([$this, 'convertToolCall'], $choice['message']['tool_calls']));
174164
}
175165

176166
if (\in_array($choice['finish_reason'], ['stop', 'length'], true)) {
177-
return new Choice($choice['message']['content']);
167+
return new TextResult($choice['message']['content']);
178168
}
179169

180170
throw new RuntimeException(\sprintf('Unsupported finish reason "%s".', $choice['finish_reason']));

src/platform/src/Result/Choice.php

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/platform/src/Result/ChoiceResult.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@
1919
final class ChoiceResult extends BaseResult
2020
{
2121
/**
22-
* @var Choice[]
22+
* @var ResultInterface[]
2323
*/
24-
private readonly array $choices;
24+
private readonly array $results;
2525

26-
public function __construct(Choice ...$choices)
26+
public function __construct(ResultInterface ...$results)
2727
{
28-
if ([] === $choices) {
29-
throw new InvalidArgumentException('Result must have at least one choice.');
28+
if (1 >= \count($results)) {
29+
throw new InvalidArgumentException('A choice result must contain at least two results.');
3030
}
3131

32-
$this->choices = $choices;
32+
$this->results = $results;
3333
}
3434

3535
/**
36-
* @return Choice[]
36+
* @return ResultInterface[]
3737
*/
3838
public function getContent(): array
3939
{
40-
return $this->choices;
40+
return $this->results;
4141
}
4242
}

src/platform/tests/Bridge/OpenAi/Gpt/ResultConverterTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Symfony\AI\Platform\Bridge\OpenAi\Gpt\ResultConverter;
1919
use Symfony\AI\Platform\Exception\ContentFilterException;
2020
use Symfony\AI\Platform\Exception\RuntimeException;
21-
use Symfony\AI\Platform\Result\Choice;
2221
use Symfony\AI\Platform\Result\ChoiceResult;
2322
use Symfony\AI\Platform\Result\RawHttpResult;
2423
use Symfony\AI\Platform\Result\TextResult;
@@ -29,7 +28,6 @@
2928

3029
#[CoversClass(ResultConverter::class)]
3130
#[Small]
32-
#[UsesClass(Choice::class)]
3331
#[UsesClass(ChoiceResult::class)]
3432
#[UsesClass(TextResult::class)]
3533
#[UsesClass(ToolCall::class)]

src/platform/tests/Result/ChoiceResultTest.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,30 @@
1313

1414
use PHPUnit\Framework\Attributes\CoversClass;
1515
use PHPUnit\Framework\Attributes\Small;
16-
use PHPUnit\Framework\Attributes\UsesClass;
1716
use PHPUnit\Framework\TestCase;
1817
use Symfony\AI\Platform\Exception\InvalidArgumentException;
19-
use Symfony\AI\Platform\Result\Choice;
2018
use Symfony\AI\Platform\Result\ChoiceResult;
19+
use Symfony\AI\Platform\Result\TextResult;
2120

2221
#[CoversClass(ChoiceResult::class)]
23-
#[UsesClass(Choice::class)]
2422
#[Small]
2523
final class ChoiceResultTest extends TestCase
2624
{
2725
public function testChoiceResultCreation()
2826
{
29-
$choice1 = new Choice('choice1');
30-
$choice2 = new Choice(null);
31-
$choice3 = new Choice('choice3');
32-
$result = new ChoiceResult($choice1, $choice2, $choice3);
27+
$choice1 = new TextResult('choice1');
28+
$choice3 = new TextResult('choice2');
29+
$result = new ChoiceResult($choice1, $choice3);
3330

34-
$this->assertCount(3, $result->getContent());
31+
$this->assertCount(2, $result->getContent());
3532
$this->assertSame('choice1', $result->getContent()[0]->getContent());
36-
$this->assertNull($result->getContent()[1]->getContent());
37-
$this->assertSame('choice3', $result->getContent()[2]->getContent());
33+
$this->assertSame('choice2', $result->getContent()[1]->getContent());
3834
}
3935

4036
public function testChoiceResultWithNoChoices()
4137
{
4238
self::expectException(InvalidArgumentException::class);
43-
self::expectExceptionMessage('Result must have at least one choice.');
39+
self::expectExceptionMessage('A choice result must contain at least two results.');
4440

4541
new ChoiceResult();
4642
}

src/platform/tests/Result/ChoiceTest.php

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)