Skip to content

Commit 4a6f72b

Browse files
committed
bug #303 [Platform][Anthropic] Fix TypeError in AssistantMessageNormalizer when toolCalls is null (Muneer-Shafi)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform][Anthropic] Fix TypeError in AssistantMessageNormalizer when toolCalls is null | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #302 | License | MIT ## Summary Fixes a `TypeError` in `AssistantMessageNormalizer::normalize()` that occurs when `$data->toolCalls` is null in the Anthropic bridge. ## Problem The current implementation calls `array_map()` on potentially null `toolCalls`: ```php 'content' => array_map(static function (ToolCall $toolCall) { // ... }, $data->toolCalls ?? []), Commits ------- 19f0952 [Platform][Anthropic] Fix TypeError in AssistantMessageNormalizer when toolCalls is null
2 parents da9a6e3 + 19f0952 commit 4a6f72b

File tree

2 files changed

+126
-13
lines changed

2 files changed

+126
-13
lines changed

src/platform/src/Bridge/Anthropic/Contract/AssistantMessageNormalizer.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,12 @@ final class AssistantMessageNormalizer extends ModelContractNormalizer implement
2626
{
2727
use NormalizerAwareTrait;
2828

29-
protected function supportedDataClass(): string
30-
{
31-
return AssistantMessage::class;
32-
}
33-
34-
protected function supportsModel(Model $model): bool
35-
{
36-
return $model instanceof Claude;
37-
}
38-
3929
/**
4030
* @param AssistantMessage $data
4131
*
4232
* @return array{
4333
* role: 'assistant',
44-
* content: list<array{
34+
* content: string|list<array{
4535
* type: 'tool_use',
4636
* id: string,
4737
* name: string,
@@ -53,14 +43,24 @@ public function normalize(mixed $data, ?string $format = null, array $context =
5343
{
5444
return [
5545
'role' => 'assistant',
56-
'content' => array_map(static function (ToolCall $toolCall) {
46+
'content' => $data->hasToolCalls() ? array_map(static function (ToolCall $toolCall) {
5747
return [
5848
'type' => 'tool_use',
5949
'id' => $toolCall->id,
6050
'name' => $toolCall->name,
6151
'input' => [] !== $toolCall->arguments ? $toolCall->arguments : new \stdClass(),
6252
];
63-
}, $data->toolCalls),
53+
}, $data->toolCalls) : $data->content,
6454
];
6555
}
56+
57+
protected function supportedDataClass(): string
58+
{
59+
return AssistantMessage::class;
60+
}
61+
62+
protected function supportsModel(Model $model): bool
63+
{
64+
return $model instanceof Claude;
65+
}
6666
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Tests\Bridge\Anthropic\Contract;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\DataProvider;
16+
use PHPUnit\Framework\Attributes\Small;
17+
use PHPUnit\Framework\Attributes\UsesClass;
18+
use PHPUnit\Framework\TestCase;
19+
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
20+
use Symfony\AI\Platform\Bridge\Anthropic\Contract\AssistantMessageNormalizer;
21+
use Symfony\AI\Platform\Contract;
22+
use Symfony\AI\Platform\Message\AssistantMessage;
23+
use Symfony\AI\Platform\Model;
24+
use Symfony\AI\Platform\Result\ToolCall;
25+
26+
#[Small]
27+
#[CoversClass(AssistantMessageNormalizer::class)]
28+
#[UsesClass(Claude::class)]
29+
#[UsesClass(AssistantMessage::class)]
30+
#[UsesClass(Model::class)]
31+
#[UsesClass(ToolCall::class)]
32+
final class AssistantMessageNormalizerTest extends TestCase
33+
{
34+
public function testSupportsNormalization()
35+
{
36+
$normalizer = new AssistantMessageNormalizer();
37+
38+
$this->assertTrue($normalizer->supportsNormalization(new AssistantMessage('Hello'), context: [
39+
Contract::CONTEXT_MODEL => new Claude(),
40+
]));
41+
$this->assertFalse($normalizer->supportsNormalization('not an assistant message'));
42+
}
43+
44+
public function testGetSupportedTypes()
45+
{
46+
$normalizer = new AssistantMessageNormalizer();
47+
48+
$this->assertSame([AssistantMessage::class => true], $normalizer->getSupportedTypes(null));
49+
}
50+
51+
#[DataProvider('normalizeDataProvider')]
52+
public function testNormalize(AssistantMessage $message, array $expectedOutput)
53+
{
54+
$normalizer = new AssistantMessageNormalizer();
55+
56+
$normalized = $normalizer->normalize($message);
57+
58+
$this->assertEquals($expectedOutput, $normalized);
59+
}
60+
61+
/**
62+
* @return iterable<string, array{
63+
* 0: AssistantMessage,
64+
* 1: array{
65+
* role: 'assistant',
66+
* content: string|list<array{
67+
* type: 'tool_use',
68+
* id: string,
69+
* name: string,
70+
* input: array<string, mixed>|\stdClass
71+
* }>
72+
* }
73+
* }>
74+
*/
75+
public static function normalizeDataProvider(): iterable
76+
{
77+
yield 'assistant message' => [
78+
new AssistantMessage('Great to meet you. What would you like to know?'),
79+
[
80+
'role' => 'assistant',
81+
'content' => 'Great to meet you. What would you like to know?',
82+
],
83+
];
84+
yield 'function call' => [
85+
new AssistantMessage(toolCalls: [new ToolCall('id1', 'name1', ['arg1' => '123'])]),
86+
[
87+
'role' => 'assistant',
88+
'content' => [
89+
[
90+
'type' => 'tool_use',
91+
'id' => 'id1',
92+
'name' => 'name1',
93+
'input' => ['arg1' => '123'],
94+
],
95+
],
96+
],
97+
];
98+
yield 'function call without parameters' => [
99+
new AssistantMessage(toolCalls: [new ToolCall('id1', 'name1')]),
100+
[
101+
'role' => 'assistant',
102+
'content' => [
103+
[
104+
'type' => 'tool_use',
105+
'id' => 'id1',
106+
'name' => 'name1',
107+
'input' => new \stdClass(),
108+
],
109+
],
110+
],
111+
];
112+
}
113+
}

0 commit comments

Comments
 (0)