From a9a2b227b7e1ce8cc52cdadf105f615eae75a38b Mon Sep 17 00:00:00 2001 From: Patrick Kuijvenhoven Date: Fri, 5 Sep 2025 11:39:09 +0200 Subject: [PATCH] feat(mistral): DocumentNormalizer --- .../Mistral/Contract/DocumentNormalizer.php | 44 ++++++++++++ .../src/Bridge/Mistral/PlatformFactory.php | 6 +- .../Contract/DocumentNormalizerTest.php | 69 +++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/platform/src/Bridge/Mistral/Contract/DocumentNormalizer.php create mode 100644 src/platform/tests/Bridge/Mistral/Contract/DocumentNormalizerTest.php diff --git a/src/platform/src/Bridge/Mistral/Contract/DocumentNormalizer.php b/src/platform/src/Bridge/Mistral/Contract/DocumentNormalizer.php new file mode 100644 index 000000000..8b0963f85 --- /dev/null +++ b/src/platform/src/Bridge/Mistral/Contract/DocumentNormalizer.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\Mistral\Contract; + +use Symfony\AI\Platform\Bridge\Mistral\Mistral; +use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer; +use Symfony\AI\Platform\Message\Content\Document; +use Symfony\AI\Platform\Model; + +class DocumentNormalizer extends ModelContractNormalizer +{ + /** + * @param Document $data + * + * @return array{type: 'document_url', document_name: string, document_url: string} + */ + public function normalize(mixed $data, ?string $format = null, array $context = []): array + { + return [ + 'type' => 'document_url', + 'document_name' => $data->getFilename(), + 'document_url' => $data->asDataUrl(), + ]; + } + + protected function supportedDataClass(): string + { + return Document::class; + } + + protected function supportsModel(Model $model): bool + { + return $model instanceof Mistral; + } +} diff --git a/src/platform/src/Bridge/Mistral/PlatformFactory.php b/src/platform/src/Bridge/Mistral/PlatformFactory.php index f17e4a6d8..c3825ae9c 100644 --- a/src/platform/src/Bridge/Mistral/PlatformFactory.php +++ b/src/platform/src/Bridge/Mistral/PlatformFactory.php @@ -11,6 +11,7 @@ namespace Symfony\AI\Platform\Bridge\Mistral; +use Symfony\AI\Platform\Bridge\Mistral\Contract\DocumentNormalizer; use Symfony\AI\Platform\Bridge\Mistral\Contract\ToolNormalizer; use Symfony\AI\Platform\Contract; use Symfony\AI\Platform\Platform; @@ -33,7 +34,10 @@ public static function create( return new Platform( [new Embeddings\ModelClient($httpClient, $apiKey), new Llm\ModelClient($httpClient, $apiKey)], [new Embeddings\ResultConverter(), new Llm\ResultConverter()], - $contract ?? Contract::create(new ToolNormalizer()), + $contract ?? Contract::create( + new ToolNormalizer(), + new DocumentNormalizer() + ), ); } } diff --git a/src/platform/tests/Bridge/Mistral/Contract/DocumentNormalizerTest.php b/src/platform/tests/Bridge/Mistral/Contract/DocumentNormalizerTest.php new file mode 100644 index 000000000..8306f526b --- /dev/null +++ b/src/platform/tests/Bridge/Mistral/Contract/DocumentNormalizerTest.php @@ -0,0 +1,69 @@ + + * + * 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\Mistral\Contract; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Mistral\Contract\DocumentNormalizer; +use Symfony\AI\Platform\Bridge\Mistral\Mistral; +use Symfony\AI\Platform\Contract; +use Symfony\AI\Platform\Message\Content\Document; + +#[Medium] +#[CoversClass(DocumentNormalizer::class)] +final class DocumentNormalizerTest extends TestCase +{ + public function testSupportsNormalization() + { + $normalizer = new DocumentNormalizer(); + + $this->assertTrue($normalizer->supportsNormalization(new Document('some content', 'application/pdf'), context: [ + Contract::CONTEXT_MODEL => new Mistral(), + ])); + $this->assertFalse($normalizer->supportsNormalization('not a document')); + } + + public function testGetSupportedTypes() + { + $normalizer = new DocumentNormalizer(); + + $expected = [ + Document::class => true, + ]; + + $this->assertSame($expected, $normalizer->getSupportedTypes(null)); + } + + #[DataProvider('normalizeDataProvider')] + public function testNormalize(Document $file, array $expected) + { + $normalizer = new DocumentNormalizer(); + + $normalized = $normalizer->normalize($file); + + $this->assertEquals($expected, $normalized); + } + + public static function normalizeDataProvider(): iterable + { + yield 'document from file' => [ + Document::fromFile(\dirname(__DIR__, 3).'/fixtures/document.pdf'), + [ + 'type' => 'document_url', + 'document_name' => 'document.pdf', + 'document_url' => 'data:application/pdf;base64,'.base64_encode(file_get_contents(\dirname(__DIR__, 3).'/fixtures/document.pdf')), + ], + ]; + } +}