Skip to content

Commit a3c7756

Browse files
committed
refactor(chat): Meilisearch store serializer usage
1 parent a9420af commit a3c7756

File tree

5 files changed

+37
-197
lines changed

5 files changed

+37
-197
lines changed

src/ai-bundle/src/AiBundle.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,7 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
15051505
$messageStore['api_key'],
15061506
new Reference(ClockInterface::class),
15071507
$messageStore['index_name'],
1508+
new Reference('serializer'),
15081509
])
15091510
->addTag('proxy', ['interface' => MessageStoreInterface::class])
15101511
->addTag('ai.message_store');

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,11 +2870,14 @@ public function testMeilisearchMessageStoreIsConfigured()
28702870
$meilisearchMessageStoreDefinition = $container->getDefinition('ai.message_store.meilisearch.custom');
28712871

28722872
$this->assertTrue($meilisearchMessageStoreDefinition->isLazy());
2873+
$this->assertCount(5, $meilisearchMessageStoreDefinition->getArguments());
28732874
$this->assertSame('http://127.0.0.1:7700', $meilisearchMessageStoreDefinition->getArgument(0));
28742875
$this->assertSame('foo', $meilisearchMessageStoreDefinition->getArgument(1));
28752876
$this->assertInstanceOf(Reference::class, $meilisearchMessageStoreDefinition->getArgument(2));
28762877
$this->assertSame(ClockInterface::class, (string) $meilisearchMessageStoreDefinition->getArgument(2));
28772878
$this->assertSame('test', $meilisearchMessageStoreDefinition->getArgument(3));
2879+
$this->assertInstanceOf(Reference::class, $meilisearchMessageStoreDefinition->getArgument(4));
2880+
$this->assertSame('serializer', (string) $meilisearchMessageStoreDefinition->getArgument(4));
28782881

28792882
$this->assertTrue($meilisearchMessageStoreDefinition->hasTag('proxy'));
28802883
$this->assertSame([['interface' => MessageStoreInterface::class]], $meilisearchMessageStoreDefinition->getTag('proxy'));

src/chat/src/Bridge/Meilisearch/MessageStore.php

Lines changed: 16 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,19 @@
1212
namespace Symfony\AI\Chat\Bridge\Meilisearch;
1313

1414
use Symfony\AI\Chat\Exception\InvalidArgumentException;
15-
use Symfony\AI\Chat\Exception\LogicException;
1615
use Symfony\AI\Chat\Exception\RuntimeException;
1716
use Symfony\AI\Chat\ManagedStoreInterface;
17+
use Symfony\AI\Chat\MessageNormalizer;
1818
use Symfony\AI\Chat\MessageStoreInterface;
19-
use Symfony\AI\Platform\Message\AssistantMessage;
20-
use Symfony\AI\Platform\Message\Content\Audio;
21-
use Symfony\AI\Platform\Message\Content\ContentInterface;
22-
use Symfony\AI\Platform\Message\Content\DocumentUrl;
23-
use Symfony\AI\Platform\Message\Content\File;
24-
use Symfony\AI\Platform\Message\Content\Image;
25-
use Symfony\AI\Platform\Message\Content\ImageUrl;
26-
use Symfony\AI\Platform\Message\Content\Text;
2719
use Symfony\AI\Platform\Message\MessageBag;
2820
use Symfony\AI\Platform\Message\MessageInterface;
29-
use Symfony\AI\Platform\Message\SystemMessage;
30-
use Symfony\AI\Platform\Message\ToolCallMessage;
31-
use Symfony\AI\Platform\Message\UserMessage;
32-
use Symfony\AI\Platform\Result\ToolCall;
3321
use Symfony\Component\Clock\ClockInterface;
22+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
23+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
24+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
25+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
26+
use Symfony\Component\Serializer\Serializer;
27+
use Symfony\Component\Serializer\SerializerInterface;
3428
use Symfony\Contracts\HttpClient\HttpClientInterface;
3529
use Symfony\Contracts\HttpClient\ResponseInterface;
3630

@@ -45,6 +39,10 @@ public function __construct(
4539
#[\SensitiveParameter] private readonly string $apiKey,
4640
private readonly ClockInterface $clock,
4741
private readonly string $indexName = '_message_store_meilisearch',
42+
private readonly SerializerInterface&NormalizerInterface&DenormalizerInterface $serializer = new Serializer([
43+
new ArrayDenormalizer(),
44+
new MessageNormalizer(),
45+
], [new JsonEncoder()]),
4846
) {
4947
if (!interface_exists(ClockInterface::class)) {
5048
throw new RuntimeException('For using Meilisearch as a message store , symfony/clock is required. Try running "composer require symfony/clock".');
@@ -74,7 +72,7 @@ public function save(MessageBag $messages): void
7472
$messages = $messages->getMessages();
7573

7674
$this->request('PUT', \sprintf('indexes/%s/documents', $this->indexName), array_map(
77-
$this->convertToIndexableArray(...),
75+
fn (MessageInterface $message): array => $this->serializer->normalize($message),
7876
$messages,
7977
));
8078
}
@@ -85,7 +83,10 @@ public function load(): MessageBag
8583
'sort' => ['addedAt:asc'],
8684
]);
8785

88-
return new MessageBag(...array_map($this->convertToMessage(...), $messages['results']));
86+
return new MessageBag(...array_map(
87+
fn (array $message): MessageInterface => $this->serializer->denormalize($message, MessageInterface::class),
88+
$messages['results']
89+
));
8990
}
9091

9192
public function drop(): void
@@ -129,88 +130,4 @@ private function request(string $method, string $endpoint, array $payload = []):
129130

130131
return $payload;
131132
}
132-
133-
/**
134-
* @return array<string, mixed>
135-
*/
136-
private function convertToIndexableArray(MessageInterface $message): array
137-
{
138-
$toolsCalls = [];
139-
140-
if ($message instanceof AssistantMessage && $message->hasToolCalls()) {
141-
$toolsCalls = array_map(
142-
static fn (ToolCall $toolCall): array => $toolCall->jsonSerialize(),
143-
$message->getToolCalls(),
144-
);
145-
}
146-
147-
if ($message instanceof ToolCallMessage) {
148-
$toolsCalls = $message->getToolCall()->jsonSerialize();
149-
}
150-
151-
return [
152-
'id' => $message->getId()->toRfc4122(),
153-
'type' => $message::class,
154-
'content' => ($message instanceof SystemMessage || $message instanceof AssistantMessage || $message instanceof ToolCallMessage) ? $message->getContent() : '',
155-
'contentAsBase64' => ($message instanceof UserMessage && [] !== $message->getContent()) ? array_map(
156-
static fn (ContentInterface $content) => [
157-
'type' => $content::class,
158-
'content' => match ($content::class) {
159-
Text::class => $content->getText(),
160-
File::class,
161-
Image::class,
162-
Audio::class => $content->asBase64(),
163-
ImageUrl::class,
164-
DocumentUrl::class => $content->getUrl(),
165-
default => throw new LogicException(\sprintf('Unknown content type "%s".', $content::class)),
166-
},
167-
],
168-
$message->getContent(),
169-
) : [],
170-
'toolsCalls' => $toolsCalls,
171-
'metadata' => $message->getMetadata()->all(),
172-
'addedAt' => (new \DateTimeImmutable())->getTimestamp(),
173-
];
174-
}
175-
176-
/**
177-
* @param array<string, mixed> $payload
178-
*/
179-
private function convertToMessage(array $payload): MessageInterface
180-
{
181-
$type = $payload['type'];
182-
$content = $payload['content'] ?? '';
183-
$contentAsBase64 = $payload['contentAsBase64'] ?? [];
184-
185-
$message = match ($type) {
186-
SystemMessage::class => new SystemMessage($content),
187-
AssistantMessage::class => new AssistantMessage($content, array_map(
188-
static fn (array $toolsCall): ToolCall => new ToolCall(
189-
$toolsCall['id'],
190-
$toolsCall['function']['name'],
191-
json_decode($toolsCall['function']['arguments'], true)
192-
),
193-
$payload['toolsCalls'],
194-
)),
195-
UserMessage::class => new UserMessage(...array_map(
196-
static fn (array $contentAsBase64) => \in_array($contentAsBase64['type'], [File::class, Image::class, Audio::class], true)
197-
? $contentAsBase64['type']::fromDataUrl($contentAsBase64['content'])
198-
: new $contentAsBase64['type']($contentAsBase64['content']),
199-
$contentAsBase64,
200-
)),
201-
ToolCallMessage::class => new ToolCallMessage(
202-
new ToolCall(
203-
$payload['toolsCalls']['id'],
204-
$payload['toolsCalls']['function']['name'],
205-
json_decode($payload['toolsCalls']['function']['arguments'], true)
206-
),
207-
$content
208-
),
209-
default => throw new LogicException(\sprintf('Unknown message type "%s".', $type)),
210-
};
211-
212-
$message->getMetadata()->set($payload['metadata']);
213-
214-
return $message;
215-
}
216133
}

src/chat/tests/Bridge/Meilisearch/MessageStoreTest.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@
1111

1212
namespace Symfony\AI\Chat\Tests\Bridge\Meilisearch;
1313

14-
use PHPUnit\Framework\Attributes\DataProvider;
14+
use PHPUnit\Framework\TestCase;
1515
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore;
16-
use Symfony\AI\Chat\Tests\MessageStoreTestCase;
16+
use Symfony\AI\Chat\MessageNormalizer;
1717
use Symfony\AI\Platform\Message\Message;
1818
use Symfony\AI\Platform\Message\MessageBag;
1919
use Symfony\Component\Clock\MonotonicClock;
2020
use Symfony\Component\HttpClient\Exception\ClientException;
2121
use Symfony\Component\HttpClient\MockHttpClient;
2222
use Symfony\Component\HttpClient\Response\JsonMockResponse;
23+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
24+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
25+
use Symfony\Component\Serializer\Serializer;
2326

24-
final class MessageStoreTest extends MessageStoreTestCase
27+
final class MessageStoreTest extends TestCase
2528
{
2629
public function testStoreCannotSetupOnInvalidResponse()
2730
{
@@ -215,27 +218,31 @@ public function testStoreCannotLoadMessagesOnInvalidResponse()
215218
$store->load();
216219
}
217220

218-
/**
219-
* @param array<mixed, mixed> $payload
220-
*/
221-
#[DataProvider('provideMessages')]
222-
public function testStoreCanLoadMessages(array $payload)
221+
public function testStoreCanLoadMessages()
223222
{
223+
$serializer = new Serializer([
224+
new ArrayDenormalizer(),
225+
new MessageNormalizer(),
226+
], [new JsonEncoder()]);
227+
224228
$httpClient = new MockHttpClient([
225229
new JsonMockResponse([
226230
'results' => [
227-
$payload,
231+
$serializer->normalize(Message::ofUser('Hello World')),
228232
],
229233
], [
230234
'http_code' => 200,
231235
]),
232236
], 'http://127.0.0.1:7700');
233237

234-
$store = new MessageStore($httpClient, 'http://127.0.0.1:7700', 'test', new MonotonicClock(), 'test');
238+
$store = new MessageStore($httpClient, 'http://127.0.0.1:7700', 'test', new MonotonicClock(), 'test', $serializer);
235239

236240
$messageBag = $store->load();
237241

238242
$this->assertCount(1, $messageBag);
239243
$this->assertSame(1, $httpClient->getRequestsCount());
244+
245+
$storedMessage = $messageBag->getUserMessage();
246+
$this->assertSame('Hello World', $storedMessage->asText());
240247
}
241248
}

src/chat/tests/MessageStoreTestCase.php

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

0 commit comments

Comments
 (0)