1212namespace Symfony \AI \Chat \Bridge \Meilisearch ;
1313
1414use Symfony \AI \Chat \Exception \InvalidArgumentException ;
15- use Symfony \AI \Chat \Exception \LogicException ;
1615use Symfony \AI \Chat \Exception \RuntimeException ;
1716use Symfony \AI \Chat \ManagedStoreInterface ;
17+ use Symfony \AI \Chat \MessageNormalizer ;
1818use 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 ;
2719use Symfony \AI \Platform \Message \MessageBag ;
2820use 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 ;
3321use 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 ;
3428use Symfony \Contracts \HttpClient \HttpClientInterface ;
3529use 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}
0 commit comments