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
1 change: 1 addition & 0 deletions examples/gemini/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
result.png
31 changes: 31 additions & 0 deletions examples/gemini/image-editing.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.
*/

use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory;
use Symfony\AI\Platform\Message\Content\Image;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

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

$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client());

$messages = new MessageBag(
Message::ofUser(
'Please colorize the elephant in red.',
Image::fromFile(dirname(__DIR__, 2).'/fixtures/image.jpg'),
),
);
$result = $platform->invoke('gemini-2.5-flash-image', $messages);

file_put_contents(__DIR__.'/result.png', $result->asBinary());

echo 'Result image saved to result.png'.\PHP_EOL;
5 changes: 3 additions & 2 deletions src/platform/src/Bridge/Gemini/Gemini/ResultConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private function convertChoice(array $choice): ToolCallResult|TextResult|BinaryR
}

if (isset($contentPart['inlineData'])) {
return new BinaryResult($contentPart['inlineData']['data'], $contentPart['inlineData']['mimeType'] ?? null);
return BinaryResult::fromBase64($contentPart['inlineData']['data'], $contentPart['inlineData']['mimeType'] ?? null);
}

throw new RuntimeException(\sprintf('Unsupported finish reason "%s".', $choice['finishReason']));
Expand All @@ -150,7 +150,8 @@ private function convertChoice(array $choice): ToolCallResult|TextResult|BinaryR
return new TextResult($content);
}

throw new RuntimeException('Code execution failed.');
// TODO: see https://github.com/symfony/ai/issues/1053
throw new RuntimeException('Choice conversion failed. Potentially due to multiple content parts.');
}

/**
Expand Down
18 changes: 18 additions & 0 deletions src/platform/src/Bridge/Gemini/ModelCatalog.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ public function __construct(array $additionalModels = [])
Capability::TOOL_CALLING,
],
],
'gemini-3-pro-image-preview' => [
'class' => Gemini::class,
'capabilities' => [
Capability::INPUT_MESSAGES,
Capability::INPUT_IMAGE,
Capability::OUTPUT_IMAGE,
Capability::OUTPUT_TEXT,
],
],
'gemini-2.5-flash-image' => [
'class' => Gemini::class,
'capabilities' => [
Capability::INPUT_MESSAGES,
Capability::INPUT_IMAGE,
Capability::OUTPUT_IMAGE,
Capability::OUTPUT_TEXT,
],
],
'gemini-2.5-flash' => [
'class' => Gemini::class,
'capabilities' => [
Expand Down
11 changes: 11 additions & 0 deletions src/platform/src/Result/BinaryResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public function __construct(
) {
}

public static function fromBase64(string $base64Data, ?string $mimeType = null): self
{
$data = base64_decode($base64Data, true);

if (false === $data) {
throw new RuntimeException('The provided data is not valid base64-encoded data.');
}

return new self($data, $mimeType);
}

public function getMimeType(): ?string
{
return $this->mimeType;
Expand Down
16 changes: 10 additions & 6 deletions src/platform/tests/Bridge/Gemini/Gemini/ResultConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\AI\Platform\Bridge\Gemini\Gemini\ResultConverter;
use Symfony\AI\Platform\Exception\RuntimeException;
use Symfony\AI\Platform\Message\Content\Image;
use Symfony\AI\Platform\Result\BinaryResult;
use Symfony\AI\Platform\Result\RawHttpResult;
use Symfony\AI\Platform\Result\ToolCall;
Expand Down Expand Up @@ -83,15 +84,16 @@ public function testConvertsInlineDataToBinaryResult()
$converter = new ResultConverter();
$httpResponse = self::createMock(ResponseInterface::class);
$httpResponse->method('getStatusCode')->willReturn(200);
$image = Image::fromFile(\dirname(__DIR__, 6).'/fixtures/image.jpg');
$httpResponse->method('toArray')->willReturn([
'candidates' => [
[
'content' => [
'parts' => [
[
'inlineData' => [
'mimeType' => 'image/png',
'data' => 'base64EncodedImageData',
'mimeType' => 'image/jpeg',
'data' => $image->asBase64(),
],
],
],
Expand All @@ -102,23 +104,25 @@ public function testConvertsInlineDataToBinaryResult()

$result = $converter->convert(new RawHttpResult($httpResponse));
$this->assertInstanceOf(BinaryResult::class, $result);
$this->assertSame('base64EncodedImageData', $result->getContent());
$this->assertSame('image/png', $result->getMimeType());
$this->assertSame($image->asBinary(), $result->getContent());
$this->assertSame('image/jpeg', $result->getMimeType());
$this->assertSame($image->asDataUrl(), $result->toDataUri());
}

public function testConvertsInlineDataWithoutMimeTypeToBinaryResult()
{
$converter = new ResultConverter();
$httpResponse = self::createMock(ResponseInterface::class);
$httpResponse->method('getStatusCode')->willReturn(200);
$image = Image::fromFile(\dirname(__DIR__, 6).'/fixtures/image.jpg');
$httpResponse->method('toArray')->willReturn([
'candidates' => [
[
'content' => [
'parts' => [
[
'inlineData' => [
'data' => 'base64EncodedData',
'data' => $image->asBase64(),
],
],
],
Expand All @@ -129,7 +133,7 @@ public function testConvertsInlineDataWithoutMimeTypeToBinaryResult()

$result = $converter->convert(new RawHttpResult($httpResponse));
$this->assertInstanceOf(BinaryResult::class, $result);
$this->assertSame('base64EncodedData', $result->getContent());
$this->assertSame($image->asBinary(), $result->getContent());
$this->assertNull($result->getMimeType());
}
}