From 19f09522da8ff537986e22381fbff03b687bc753 Mon Sep 17 00:00:00 2001 From: Muneer shafi Date: Mon, 11 Aug 2025 17:58:12 +0530 Subject: [PATCH] [Platform][Anthropic] Fix TypeError in AssistantMessageNormalizer when toolCalls is null --- .../Contract/AssistantMessageNormalizer.php | 26 ++-- .../AssistantMessageNormalizerTest.php | 113 ++++++++++++++++++ 2 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 src/platform/tests/Bridge/Anthropic/Contract/AssistantMessageNormalizerTest.php diff --git a/src/platform/src/Bridge/Anthropic/Contract/AssistantMessageNormalizer.php b/src/platform/src/Bridge/Anthropic/Contract/AssistantMessageNormalizer.php index c03e034e7..09c256a72 100644 --- a/src/platform/src/Bridge/Anthropic/Contract/AssistantMessageNormalizer.php +++ b/src/platform/src/Bridge/Anthropic/Contract/AssistantMessageNormalizer.php @@ -26,22 +26,12 @@ final class AssistantMessageNormalizer extends ModelContractNormalizer implement { use NormalizerAwareTrait; - protected function supportedDataClass(): string - { - return AssistantMessage::class; - } - - protected function supportsModel(Model $model): bool - { - return $model instanceof Claude; - } - /** * @param AssistantMessage $data * * @return array{ * role: 'assistant', - * content: list 'assistant', - 'content' => array_map(static function (ToolCall $toolCall) { + 'content' => $data->hasToolCalls() ? array_map(static function (ToolCall $toolCall) { return [ 'type' => 'tool_use', 'id' => $toolCall->id, 'name' => $toolCall->name, 'input' => [] !== $toolCall->arguments ? $toolCall->arguments : new \stdClass(), ]; - }, $data->toolCalls), + }, $data->toolCalls) : $data->content, ]; } + + protected function supportedDataClass(): string + { + return AssistantMessage::class; + } + + protected function supportsModel(Model $model): bool + { + return $model instanceof Claude; + } } diff --git a/src/platform/tests/Bridge/Anthropic/Contract/AssistantMessageNormalizerTest.php b/src/platform/tests/Bridge/Anthropic/Contract/AssistantMessageNormalizerTest.php new file mode 100644 index 000000000..72ba32b29 --- /dev/null +++ b/src/platform/tests/Bridge/Anthropic/Contract/AssistantMessageNormalizerTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\Anthropic\Contract; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Anthropic\Claude; +use Symfony\AI\Platform\Bridge\Anthropic\Contract\AssistantMessageNormalizer; +use Symfony\AI\Platform\Contract; +use Symfony\AI\Platform\Message\AssistantMessage; +use Symfony\AI\Platform\Model; +use Symfony\AI\Platform\Result\ToolCall; + +#[Small] +#[CoversClass(AssistantMessageNormalizer::class)] +#[UsesClass(Claude::class)] +#[UsesClass(AssistantMessage::class)] +#[UsesClass(Model::class)] +#[UsesClass(ToolCall::class)] +final class AssistantMessageNormalizerTest extends TestCase +{ + public function testSupportsNormalization() + { + $normalizer = new AssistantMessageNormalizer(); + + $this->assertTrue($normalizer->supportsNormalization(new AssistantMessage('Hello'), context: [ + Contract::CONTEXT_MODEL => new Claude(), + ])); + $this->assertFalse($normalizer->supportsNormalization('not an assistant message')); + } + + public function testGetSupportedTypes() + { + $normalizer = new AssistantMessageNormalizer(); + + $this->assertSame([AssistantMessage::class => true], $normalizer->getSupportedTypes(null)); + } + + #[DataProvider('normalizeDataProvider')] + public function testNormalize(AssistantMessage $message, array $expectedOutput) + { + $normalizer = new AssistantMessageNormalizer(); + + $normalized = $normalizer->normalize($message); + + $this->assertEquals($expectedOutput, $normalized); + } + + /** + * @return iterable|\stdClass + * }> + * } + * }> + */ + public static function normalizeDataProvider(): iterable + { + yield 'assistant message' => [ + new AssistantMessage('Great to meet you. What would you like to know?'), + [ + 'role' => 'assistant', + 'content' => 'Great to meet you. What would you like to know?', + ], + ]; + yield 'function call' => [ + new AssistantMessage(toolCalls: [new ToolCall('id1', 'name1', ['arg1' => '123'])]), + [ + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'tool_use', + 'id' => 'id1', + 'name' => 'name1', + 'input' => ['arg1' => '123'], + ], + ], + ], + ]; + yield 'function call without parameters' => [ + new AssistantMessage(toolCalls: [new ToolCall('id1', 'name1')]), + [ + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'tool_use', + 'id' => 'id1', + 'name' => 'name1', + 'input' => new \stdClass(), + ], + ], + ], + ]; + } +}