Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 0060605

Browse files
committed
refactor: remove visitor infavor of single converter class
1 parent 9271a5b commit 0060605

21 files changed

+206
-179
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ PINECONE_HOST=
4141
RUN_EXPENSIVE_EXAMPLES=false
4242

4343
# For using Gemini
44-
GOOGLE_API_KEY=
44+
GOOGLE_API_KEY=

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ LLM Chain categorizes two main types of models: **Language Models** and **Embedd
3838
Language Models, like GPT, Claude and Llama, as essential centerpiece of LLM applications
3939
and Embeddings Models as supporting models to provide vector representations of text.
4040

41-
Those models are provided by different **platforms**, like OpenAI, Azure, Replicate, and others.
41+
Those models are provided by different **platforms**, like OpenAI, Azure, Google, Replicate, and others.
4242

4343
#### Example Instantiation
4444

@@ -63,6 +63,7 @@ $embeddings = new Embeddings();
6363
* [OpenAI's GPT](https://platform.openai.com/docs/models/overview) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
6464
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) as Platform
6565
* [Meta's Llama](https://www.llama.com/) with [Ollama](https://ollama.com/) and [Replicate](https://replicate.com/) as Platform
66+
* [Google's Gemini](https://gemini.google.com/) with [Google](https://ai.google.dev/) as Platform
6667
* [Google's Gemini](https://gemini.google.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
6768
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
6869
* Embeddings Models

examples/chat-gemini-google.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
use PhpLlm\LlmChain\Bridge\Google\GoogleModel;
3+
use PhpLlm\LlmChain\Bridge\Google\Gemini;
44
use PhpLlm\LlmChain\Bridge\Google\PlatformFactory;
55
use PhpLlm\LlmChain\Chain;
66
use PhpLlm\LlmChain\Model\Message\Message;
@@ -16,7 +16,7 @@
1616
}
1717

1818
$platform = PlatformFactory::create($_ENV['GOOGLE_API_KEY']);
19-
$llm = new GoogleModel(GoogleModel::GEMINI_2_FLASH);
19+
$llm = new Gemini(Gemini::GEMINI_2_FLASH);
2020

2121
$chain = new Chain($platform, $llm);
2222
$messages = new MessageBag(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Google\Gemini;
4+
use PhpLlm\LlmChain\Bridge\Google\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Model\Message\Content\Image;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
13+
14+
if (empty($_ENV['GOOGLE_API_KEY'])) {
15+
echo 'Please set the GOOGLE_API_KEY environment variable.'.PHP_EOL;
16+
exit(1);
17+
}
18+
19+
$platform = PlatformFactory::create($_ENV['GOOGLE_API_KEY']);
20+
$llm = new Gemini(Gemini::GEMINI_1_5_FLASH);
21+
22+
$chain = new Chain($platform, $llm);
23+
$messages = new MessageBag(
24+
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
25+
Message::ofUser(
26+
'Describe the image as a comedian would do it.',
27+
new Image(dirname(__DIR__).'/tests/Fixture/image.jpg'),
28+
),
29+
);
30+
$response = $chain->call($messages);
31+
32+
echo $response->getContent().PHP_EOL;

src/Bridge/Google/GoogleModel.php renamed to src/Bridge/Google/Gemini.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use PhpLlm\LlmChain\Model\LanguageModel;
88

9-
final readonly class GoogleModel implements LanguageModel
9+
final readonly class Gemini implements LanguageModel
1010
{
1111
public const GEMINI_2_FLASH = 'gemini-2.0-flash';
1212
public const GEMINI_2_PRO = 'gemini-2.0-pro-exp-02-05';
@@ -40,7 +40,7 @@ public function supportsAudioInput(): bool
4040

4141
public function supportsImageInput(): bool
4242
{
43-
return false; // it does, but implementation here is still open;in_array($this->version, [self::GEMINI_2_FLASH, self::GEMINI_2_PRO, self::GEMINI_2_FLASH_LITE, self::GEMINI_2_FLASH_THINKING, self::GEMINI_1_5_FLASH], true);
43+
return true;
4444
}
4545

4646
public function supportsStreaming(): bool
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Google;
6+
7+
use PhpLlm\LlmChain\Model\Message\AssistantMessage;
8+
use PhpLlm\LlmChain\Model\Message\Content\Image;
9+
use PhpLlm\LlmChain\Model\Message\Content\Text;
10+
use PhpLlm\LlmChain\Model\Message\MessageBagInterface;
11+
use PhpLlm\LlmChain\Model\Message\MessageInterface;
12+
use PhpLlm\LlmChain\Model\Message\Role;
13+
use PhpLlm\LlmChain\Model\Message\UserMessage;
14+
15+
use function Symfony\Component\String\u;
16+
17+
final class GooglePromptConverter
18+
{
19+
/**
20+
* @return array{
21+
* contents: list<array{
22+
* role: 'model'|'user',
23+
* parts: list<array{inline_data?: array{mime_type: string, data: string}|array{text: string}}>
24+
* }>,
25+
* system_instruction?: array{parts: array{text: string}}
26+
* }
27+
*/
28+
public function convertToPrompt(MessageBagInterface $bag): array
29+
{
30+
$body = ['contents' => []];
31+
32+
$systemMessage = $bag->getSystemMessage();
33+
if (null !== $systemMessage) {
34+
$body['system_instruction'] = [
35+
'parts' => ['text' => $systemMessage->content],
36+
];
37+
}
38+
39+
foreach ($bag->withoutSystemMessage()->getMessages() as $message) {
40+
$body['contents'][] = [
41+
'role' => $message->getRole()->equals(Role::Assistant) ? 'model' : 'user',
42+
'parts' => $this->convertMessage($message),
43+
];
44+
}
45+
46+
return $body;
47+
}
48+
49+
/**
50+
* @return list<array{inline_data?: array{mime_type: string, data: string}|array{text: string}}>
51+
*/
52+
private function convertMessage(MessageInterface $message): array
53+
{
54+
if ($message instanceof AssistantMessage) {
55+
return [['text' => $message->content]];
56+
}
57+
58+
if ($message instanceof UserMessage) {
59+
$parts = [];
60+
foreach ($message->content as $content) {
61+
if ($content instanceof Text) {
62+
$parts[] = ['text' => $content->text];
63+
}
64+
if ($content instanceof Image) {
65+
$parts[] = ['inline_data' => [
66+
'mime_type' => u($content->url)->after('data:')->before(';')->toString(),
67+
'data' => u($content->url)->after('base64,')->toString(),
68+
]];
69+
}
70+
}
71+
72+
return $parts;
73+
}
74+
75+
return [];
76+
}
77+
}

src/Bridge/Google/GoogleRequestBodyProducer.php

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

src/Bridge/Google/ModelHandler.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@
3131
public function __construct(
3232
HttpClientInterface $httpClient,
3333
#[\SensitiveParameter] private string $apiKey,
34+
private GooglePromptConverter $promptConverter = new GooglePromptConverter(),
3435
) {
3536
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
3637
}
3738

3839
public function supports(Model $model, array|string|object $input): bool
3940
{
40-
return $model instanceof GoogleModel && $input instanceof MessageBagInterface;
41+
return $model instanceof Gemini && $input instanceof MessageBagInterface;
4142
}
4243

4344
/**
@@ -47,13 +48,11 @@ public function request(Model $model, object|array|string $input, array $options
4748
{
4849
Assert::isInstanceOf($input, MessageBagInterface::class);
4950

50-
$body = new GoogleRequestBodyProducer($input);
51-
5251
return $this->httpClient->request('POST', sprintf('https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent', $model->getVersion()), [
5352
'headers' => [
5453
'x-goog-api-key' => $this->apiKey,
5554
],
56-
'json' => $body,
55+
'json' => $this->promptConverter->convertToPrompt($input),
5756
]);
5857
}
5958

src/Model/Message/AssistantMessage.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,4 @@ public function jsonSerialize(): array
5050

5151
return $array;
5252
}
53-
54-
public function accept(MessageVisitor $visitor): array
55-
{
56-
return $visitor->visitAssistantMessage($this);
57-
}
5853
}

src/Model/Message/Content/Audio.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,4 @@ public function jsonSerialize(): array
3232
],
3333
];
3434
}
35-
36-
public function accept(ContentVisitor $visitor): array
37-
{
38-
return $visitor->visitAudio($this);
39-
}
4035
}

0 commit comments

Comments
 (0)