From cf43927f3c702691b8b3bdc6bb13485d20a0a635 Mon Sep 17 00:00:00 2001 From: Guillaume Loulier Date: Tue, 18 Nov 2025 21:27:25 +0100 Subject: [PATCH] refactor(chat): Pogocache store serializer usage --- src/ai-bundle/src/AiBundle.php | 1 + .../DependencyInjection/AiBundleTest.php | 3 + .../src/Bridge/Pogocache/MessageStore.php | 115 +++--------------- .../Bridge/Pogocache/MessageStoreTest.php | 27 ++-- 4 files changed, 36 insertions(+), 110 deletions(-) diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 6b3c5da6a..36a8eff14 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -1540,6 +1540,7 @@ private function processMessageStoreConfig(string $type, array $messageStores, C $messageStore['endpoint'], $messageStore['password'], $messageStore['key'], + new Reference('serializer'), ]) ->addTag('proxy', ['interface' => MessageStoreInterface::class]) ->addTag('ai.message_store'); diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index 2975d8d84..7174d9b4d 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -2924,11 +2924,14 @@ public function testPogocacheMessageStoreIsConfigured() $pogocacheMessageStoreDefinition = $container->getDefinition('ai.message_store.pogocache.custom'); $this->assertTrue($pogocacheMessageStoreDefinition->isLazy()); + $this->assertCount(5, $pogocacheMessageStoreDefinition->getArguments()); $this->assertInstanceOf(Reference::class, $pogocacheMessageStoreDefinition->getArgument(0)); $this->assertSame('http_client', (string) $pogocacheMessageStoreDefinition->getArgument(0)); $this->assertSame('http://127.0.0.1:9401', $pogocacheMessageStoreDefinition->getArgument(1)); $this->assertSame('foo', $pogocacheMessageStoreDefinition->getArgument(2)); $this->assertSame('bar', $pogocacheMessageStoreDefinition->getArgument(3)); + $this->assertInstanceOf(Reference::class, $pogocacheMessageStoreDefinition->getArgument(4)); + $this->assertSame('serializer', (string) $pogocacheMessageStoreDefinition->getArgument(4)); $this->assertTrue($pogocacheMessageStoreDefinition->hasTag('proxy')); $this->assertSame([['interface' => MessageStoreInterface::class]], $pogocacheMessageStoreDefinition->getTag('proxy')); diff --git a/src/chat/src/Bridge/Pogocache/MessageStore.php b/src/chat/src/Bridge/Pogocache/MessageStore.php index f6c0b21d3..2a7a28c6d 100644 --- a/src/chat/src/Bridge/Pogocache/MessageStore.php +++ b/src/chat/src/Bridge/Pogocache/MessageStore.php @@ -12,23 +12,17 @@ namespace Symfony\AI\Chat\Bridge\Pogocache; use Symfony\AI\Chat\Exception\InvalidArgumentException; -use Symfony\AI\Chat\Exception\LogicException; use Symfony\AI\Chat\ManagedStoreInterface; +use Symfony\AI\Chat\MessageNormalizer; use Symfony\AI\Chat\MessageStoreInterface; -use Symfony\AI\Platform\Message\AssistantMessage; -use Symfony\AI\Platform\Message\Content\Audio; -use Symfony\AI\Platform\Message\Content\ContentInterface; -use Symfony\AI\Platform\Message\Content\DocumentUrl; -use Symfony\AI\Platform\Message\Content\File; -use Symfony\AI\Platform\Message\Content\Image; -use Symfony\AI\Platform\Message\Content\ImageUrl; -use Symfony\AI\Platform\Message\Content\Text; use Symfony\AI\Platform\Message\MessageBag; use Symfony\AI\Platform\Message\MessageInterface; -use Symfony\AI\Platform\Message\SystemMessage; -use Symfony\AI\Platform\Message\ToolCallMessage; -use Symfony\AI\Platform\Message\UserMessage; -use Symfony\AI\Platform\Result\ToolCall; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; /** @@ -41,6 +35,10 @@ public function __construct( private readonly string $host, #[\SensitiveParameter] private readonly string $password, private readonly string $key = '_message_store_pogocache', + private readonly SerializerInterface&NormalizerInterface&DenormalizerInterface $serializer = new Serializer([ + new ArrayDenormalizer(), + new MessageNormalizer(), + ], [new JsonEncoder()]), ) { } @@ -60,11 +58,9 @@ public function drop(): void public function save(MessageBag $messages): void { - $messages = $messages->getMessages(); - $this->request('PUT', $this->key, array_map( - $this->convertToIndexableArray(...), - $messages, + fn (MessageInterface $message): array => $this->serializer->normalize($message), + $messages->getMessages(), )); } @@ -73,7 +69,7 @@ public function load(): MessageBag $messages = $this->request('GET', $this->key); return new MessageBag(...array_map( - $this->convertToMessage(...), + fn (array $message): MessageInterface => $this->serializer->denormalize($message, MessageInterface::class), $messages, )); } @@ -97,87 +93,4 @@ private function request(string $method, string $endpoint, array $payload = []): return []; } - - /** - * @return array - */ - private function convertToIndexableArray(MessageInterface $message): array - { - $toolsCalls = []; - - if ($message instanceof AssistantMessage && $message->hasToolCalls()) { - $toolsCalls = array_map( - static fn (ToolCall $toolCall): array => $toolCall->jsonSerialize(), - $message->getToolCalls(), - ); - } - - if ($message instanceof ToolCallMessage) { - $toolsCalls = $message->getToolCall()->jsonSerialize(); - } - - return [ - 'id' => $message->getId()->toRfc4122(), - 'type' => $message::class, - 'content' => ($message instanceof SystemMessage || $message instanceof AssistantMessage || $message instanceof ToolCallMessage) ? $message->getContent() : '', - 'contentAsBase64' => ($message instanceof UserMessage && [] !== $message->getContent()) ? array_map( - static fn (ContentInterface $content) => [ - 'type' => $content::class, - 'content' => match ($content::class) { - Text::class => $content->getText(), - File::class, - Image::class, - Audio::class => $content->asBase64(), - ImageUrl::class, - DocumentUrl::class => $content->getUrl(), - default => throw new LogicException(\sprintf('Unknown content type "%s".', $content::class)), - }, - ], - $message->getContent(), - ) : [], - 'toolsCalls' => $toolsCalls, - 'metadata' => $message->getMetadata()->all(), - ]; - } - - /** - * @param array $payload - */ - private function convertToMessage(array $payload): MessageInterface - { - $type = $payload['type']; - $content = $payload['content'] ?? ''; - $contentAsBase64 = $payload['contentAsBase64'] ?? []; - - $message = match ($type) { - SystemMessage::class => new SystemMessage($content), - AssistantMessage::class => new AssistantMessage($content, array_map( - static fn (array $toolsCall): ToolCall => new ToolCall( - $toolsCall['id'], - $toolsCall['function']['name'], - json_decode($toolsCall['function']['arguments'], true) - ), - $payload['toolsCalls'], - )), - UserMessage::class => new UserMessage(...array_map( - static fn (array $contentAsBase64): ContentInterface => \in_array($contentAsBase64['type'], [File::class, Image::class, Audio::class], true) - ? $contentAsBase64['type']::fromDataUrl($contentAsBase64['content']) - : new $contentAsBase64['type']($contentAsBase64['content']), - $contentAsBase64, - )), - ToolCallMessage::class => new ToolCallMessage( - new ToolCall( - $payload['toolsCalls']['id'], - $payload['toolsCalls']['function']['name'], - json_decode($payload['toolsCalls']['function']['arguments'], true) - ), - $content - ), - default => throw new LogicException(\sprintf('Unknown message type "%s".', $type)), - }; - - $message->getMetadata()->set($payload['metadata']); - - return $message; - } } diff --git a/src/chat/tests/Bridge/Pogocache/MessageStoreTest.php b/src/chat/tests/Bridge/Pogocache/MessageStoreTest.php index 35effe8b7..7bceac67a 100644 --- a/src/chat/tests/Bridge/Pogocache/MessageStoreTest.php +++ b/src/chat/tests/Bridge/Pogocache/MessageStoreTest.php @@ -11,16 +11,20 @@ namespace Symfony\AI\Chat\Tests\Bridge\Pogocache; -use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; use Symfony\AI\Chat\Bridge\Pogocache\MessageStore; -use Symfony\AI\Chat\Tests\MessageStoreTestCase; +use Symfony\AI\Chat\MessageNormalizer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Message\UserMessage; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\JsonMockResponse; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Serializer; -final class MessageStoreTest extends MessageStoreTestCase +final class MessageStoreTest extends TestCase { public function testStoreCannotSetupOnInvalidResponse() { @@ -131,23 +135,28 @@ public function testStoreCannotLoadMessagesOnInvalidResponse() $store->load(); } - /** - * @param array $payload - */ - #[DataProvider('provideMessages')] - public function testStoreCanLoadMessages(array $payload) + public function testStoreCanLoadMessages() { + $serializer = new Serializer([ + new ArrayDenormalizer(), + new MessageNormalizer(), + ], [new JsonEncoder()]); + + $payload = $serializer->normalize(Message::ofUser('Hello World')); + $httpClient = new MockHttpClient([ new JsonMockResponse([$payload], [ 'http_code' => 200, ]), ], 'http://127.0.0.1:9401'); - $store = new MessageStore($httpClient, 'http://127.0.0.1:9401', 'test', 'test'); + $store = new MessageStore($httpClient, 'http://127.0.0.1:9401', 'test', 'test', $serializer); $messageBag = $store->load(); $this->assertCount(1, $messageBag); $this->assertSame(1, $httpClient->getRequestsCount()); + $this->assertInstanceOf(UserMessage::class, $messageBag->getUserMessage()); + $this->assertSame('Hello World', $messageBag->getUserMessage()->asText()); } }