Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions examples/openai/pdf-input-binary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Content\File;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$model = new Gpt(Gpt::GPT_4O_MINI);

$agent = new Agent($platform, $model, logger: logger());
$messages = new MessageBag(
Message::ofUser(
'What is this document about?',
// Note: You can use either `File::fromFile` or `Document::fromFile` here.
File::fromFile(dirname(__DIR__, 2).'/fixtures/document.pdf'),
),
);
$result = $agent->call($messages);

echo $result->getContent().\PHP_EOL;
49 changes: 49 additions & 0 deletions src/platform/src/Bridge/OpenAi/Contract/FileNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenAi\Contract;

use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Contract\Normalizer\ModelContractNormalizer;
use Symfony\AI\Platform\Message\Content\File;
use Symfony\AI\Platform\Model;

/**
* @author Guillermo Lengemann <guillermo.lengemann@gmail.com>
*/
class FileNormalizer extends ModelContractNormalizer
{
/**
* @param File $data
*
* @return array{type: 'file', file: array{filename: string, file_data: 'base64'}}
*/
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
return [
'type' => 'file',
'file' => [
'filename' => $data->getFilename(),
'file_data' => $data->asDataUrl(),
],
];
}

protected function supportedDataClass(): string
{
return File::class;
}

protected function supportsModel(Model $model): bool
{
return $model instanceof Gpt;
}
}
31 changes: 31 additions & 0 deletions src/platform/src/Bridge/OpenAi/Contract/OpenAiContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenAi\Contract;

use Symfony\AI\Platform\Bridge\OpenAi\Whisper\AudioNormalizer;
use Symfony\AI\Platform\Contract;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
* @author Guillermo Lengemann <guillermo.lengemann@gmail.com>
*/
final readonly class OpenAiContract extends Contract
{
public static function create(NormalizerInterface ...$normalizer): Contract
{
return parent::create(
new AudioNormalizer(),
new FileNormalizer(),
...$normalizer
);
}
}
4 changes: 2 additions & 2 deletions src/platform/src/Bridge/OpenAi/PlatformFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\AI\Platform\Bridge\OpenAi;

use Symfony\AI\Platform\Bridge\OpenAi\Whisper\AudioNormalizer;
use Symfony\AI\Platform\Bridge\OpenAi\Contract\OpenAiContract;
use Symfony\AI\Platform\Bridge\OpenAi\Whisper\ModelClient as WhisperModelClient;
use Symfony\AI\Platform\Bridge\OpenAi\Whisper\ResultConverter as WhisperResponseConverter;
use Symfony\AI\Platform\Contract;
Expand Down Expand Up @@ -45,7 +45,7 @@ public static function create(
new DallE\ResultConverter(),
new WhisperResponseConverter(),
],
$contract ?? Contract::create(new AudioNormalizer()),
$contract ?? OpenAiContract::create(),
);
}
}
5 changes: 5 additions & 0 deletions src/platform/src/Message/Content/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,9 @@ public function asResource()

return fopen($this->path, 'r');
}

public function getFilename(): ?string
{
return null === $this->path ? null : basename($this->path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\OpenAi\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\Gemini\Contract\MessageBagNormalizer;
use Symfony\AI\Platform\Bridge\OpenAi\Contract\FileNormalizer;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Contract;
use Symfony\AI\Platform\Message\Content\Document;
use Symfony\AI\Platform\Message\Content\File;

#[Medium]
#[CoversClass(FileNormalizer::class)]
#[CoversClass(MessageBagNormalizer::class)]
final class DocumentNormalizerTest extends TestCase
{
public function testSupportsNormalization()
{
$normalizer = new FileNormalizer();

$this->assertTrue($normalizer->supportsNormalization(new Document('some content', 'application/pdf'), context: [
Contract::CONTEXT_MODEL => new Gpt(),
]));
$this->assertTrue($normalizer->supportsNormalization(new File('some content', 'application/pdf'), context: [
Contract::CONTEXT_MODEL => new Gpt(),
]));
$this->assertFalse($normalizer->supportsNormalization('not a document'));
}

public function testGetSupportedTypes()
{
$normalizer = new FileNormalizer();

$expected = [
File::class => true,
];

$this->assertSame($expected, $normalizer->getSupportedTypes(null));
}

#[DataProvider('normalizeDataProvider')]
public function testNormalize(File $file, array $expected)
{
$normalizer = new FileNormalizer();

$normalized = $normalizer->normalize($file);

$this->assertEquals($expected, $normalized);
}

public static function normalizeDataProvider(): iterable
{
yield 'document from file' => [
File::fromFile(\dirname(__DIR__, 6).'/fixtures/document.pdf'),
[
'type' => 'file',
'file' => [
'filename' => 'document.pdf',
'file_data' => 'data:application/pdf;base64,'.base64_encode(file_get_contents(\dirname(__DIR__, 6).'/fixtures/document.pdf')),
],
],
];
}
}