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

Commit 1799ed4

Browse files
authored
refactor: introduce decoupled platform layer and convert models to state objects (#140)
1 parent 0fc493b commit 1799ed4

File tree

146 files changed

+1637
-1117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+1637
-1117
lines changed

README.md

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,26 @@ Those models are provided by different **platforms**, like OpenAI, Azure, Replic
4141
#### Example Instantiation
4242

4343
```php
44-
use PhpLlm\LlmChain\OpenAI\Model\Embeddings;
45-
use PhpLlm\LlmChain\OpenAI\Model\Gpt;
46-
use PhpLlm\LlmChain\OpenAI\Model\Gpt\Version;
47-
use PhpLlm\LlmChain\OpenAI\Platform\OpenAI;
48-
use Symfony\Component\HttpClient\HttpClient;
44+
use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings;
45+
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
46+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
4947

5048
// Platform: OpenAI
51-
$platform = new OpenAI(HttpClient::create(), $_ENV['OPENAI_API_KEY']);
49+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
5250

5351
// Language Model: GPT (OpenAI)
54-
$llm = new Gpt($platform, Version::gpt4oMini());
52+
$llm = new GPT(GPT::GPT_4O_MINI);
5553

5654
// Embeddings Model: Embeddings (OpenAI)
57-
$embeddings = new Embeddings($platform);
55+
$embeddings = new Embeddings();
5856
```
5957

6058
#### Supported Models & Platforms
6159

6260
* Language Models
6361
* [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
6462
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) as Platform
63+
* [Meta's Llama](https://www.llama.com/) with [Ollama](https://ollama.com/) and [Replicate](https://replicate.com/) as Platform
6564
* Embeddings Models
6665
* [OpenAI's Text Embeddings](https://platform.openai.com/docs/guides/embeddings/embedding-models) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
6766
* [Voyage's Embeddings](https://docs.voyageai.com/docs/embeddings) with [Voyage](https://www.voyageai.com/) as Platform
@@ -71,7 +70,7 @@ See [issue #28](https://github.com/php-llm/llm-chain/issues/28) for planned supp
7170
### Chain & Messages
7271

7372
The core feature of LLM Chain is to interact with language models via messages. This interaction is done by sending
74-
a **MessageBag** to a **Chain**, which takes care of LLM invokation and response handling.
73+
a **MessageBag** to a **Chain**, which takes care of LLM invocation and response handling.
7574

7675
Messages can be of different types, most importantly `UserMessage`, `SystemMessage`, or `AssistantMessage`, and can also
7776
have different content types, like `Text` or `Image`.
@@ -80,13 +79,13 @@ have different content types, like `Text` or `Image`.
8079

8180
```php
8281
use PhpLlm\LlmChain\Chain;
83-
use PhpLlm\LlmChain\Message\MessageBag;
84-
use PhpLlm\LlmChain\Message\SystemMessage;
85-
use PhpLlm\LlmChain\Message\UserMessage;
82+
use PhpLlm\LlmChain\Model\Message\MessageBag;
83+
use PhpLlm\LlmChain\Model\Message\SystemMessage;
84+
use PhpLlm\LlmChain\Model\Message\UserMessage;
8685

87-
// LLM instantiation
86+
// Platform & LLM instantiation
8887

89-
$chain = new Chain($llm);
88+
$chain = new Chain($platform, $llm);
9089
$messages = new MessageBag(
9190
new SystemMessage('You are a helpful chatbot answering questions about LLM Chain.'),
9291
new UserMessage('Hello, how are you?'),
@@ -104,6 +103,8 @@ The `MessageInterface` and `Content` interface help to customize this process if
104103
1. **OpenAI's GPT with Azure**: [chat-gpt-azure.php](examples/chat-gpt-azure.php)
105104
1. **OpenAI's GPT**: [chat-gpt-openai.php](examples/chat-gpt-openai.php)
106105
1. **OpenAI's o1**: [chat-o1-openai.php](examples/chat-o1-openai.php)
106+
1. **Meta's Llama with Ollama**: [chat-llama-ollama.php](examples/chat-llama-ollama.php)
107+
1. **Meta's Llama with Replicate**: [chat-llama-replicate.php](examples/chat-llama-replicate.php)
107108

108109
### Tools
109110

@@ -112,19 +113,21 @@ Tools are services that can be called by the LLM to provide additional features
112113

113114
Tool calling can be enabled by registering the processors in the chain:
114115
```php
115-
use PhpLlm\LlmChain\ToolBox\ChainProcessor;
116-
use PhpLlm\LlmChain\ToolBox\ToolAnalyzer;
117-
use PhpLlm\LlmChain\ToolBox\ToolBox;
116+
use PhpLlm\LlmChain\Chain\ToolBox\ChainProcessor;
117+
use PhpLlm\LlmChain\Chain\ToolBox\ToolAnalyzer;
118+
use PhpLlm\LlmChain\Chain\ToolBox\ToolBox;
118119
use Symfony\Component\Serializer\Encoder\JsonEncoder;
119120
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
120121
use Symfony\Component\Serializer\Serializer;
121122

123+
// Platform & LLM instantiation
124+
122125
$yourTool = new YourTool();
123126

124127
$toolBox = new ToolBox(new ToolAnalyzer(), [$yourTool]);
125128
$toolProcessor = new ChainProcessor($toolBox);
126129

127-
$chain = new Chain($llm, inputProcessor: [$toolProcessor], outputProcessor: [$toolProcessor]);
130+
$chain = new Chain($platform, $llm, inputProcessor: [$toolProcessor], outputProcessor: [$toolProcessor]);
128131
```
129132

130133
Custom tools can basically be any class, but must configure by the `#[AsTool]` attribute.
@@ -159,15 +162,16 @@ For populating a vector store, LLM Chain provides the service `DocumentEmbedder`
159162
`EmbeddingsModel` and one of `StoreInterface`, and works with a collection of `Document` objects as input:
160163

161164
```php
162-
use PhpLlm\LlmChain\DocumentEmbedder;
163-
use PhpLlm\LlmChain\OpenAI\Model\Embeddings;
164-
use PhpLlm\LlmChain\OpenAI\Platform\OpenAI;
165-
use PhpLlm\LlmChain\Store\Pinecone\Store;
165+
use PhpLlm\LlmChain\Embedder;
166+
use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings;
167+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
168+
use PhpLlm\LlmChain\Bridge\Pinecone\Store;
166169
use Probots\Pinecone\Pinecone;
167170
use Symfony\Component\HttpClient\HttpClient;
168171

169-
$embedder = new DocumentEmbedder(
170-
new Embeddings(new OpenAI(HttpClient::create(), $_ENV['OPENAI_API_KEY']);),
172+
$embedder = new Embedder(
173+
PlatformFactory::create($_ENV['OPENAI_API_KEY']),
174+
new Embeddings(),
171175
new Store(Pinecone::client($_ENV['PINECONE_API_KEY'], $_ENV['PINECONE_HOST']),
172176
);
173177
$embedder->embed($documents);
@@ -196,20 +200,19 @@ In the end the chain is used in combination with a retrieval tool on top of the
196200

197201
```php
198202
use PhpLlm\LlmChain\Chain;
199-
use PhpLlm\LlmChain\DocumentEmbedder;
200-
use PhpLlm\LlmChain\Message\Message;
201-
use PhpLlm\LlmChain\Message\MessageBag;
202-
use PhpLlm\LlmChain\ToolBox\ChainProcessor;
203-
use PhpLlm\LlmChain\ToolBox\Tool\SimilaritySearch;
204-
use PhpLlm\LlmChain\ToolBox\ToolAnalyzer;
205-
use PhpLlm\LlmChain\ToolBox\ToolBox;
203+
use PhpLlm\LlmChain\Model\Message\Message;
204+
use PhpLlm\LlmChain\Model\Message\MessageBag;
205+
use PhpLlm\LlmChain\Chain\ToolBox\ChainProcessor;
206+
use PhpLlm\LlmChain\Chain\ToolBox\Tool\SimilaritySearch;
207+
use PhpLlm\LlmChain\Chain\ToolBox\ToolAnalyzer;
208+
use PhpLlm\LlmChain\Chain\ToolBox\ToolBox;
206209

207-
// Initialize Platform and LLM
210+
// Initialize Platform & Models
208211

209212
$similaritySearch = new SimilaritySearch($embeddings, $store);
210213
$toolBox = new ToolBox(new ToolAnalyzer(), [$similaritySearch]);
211214
$processor = new ChainProcessor($toolBox);
212-
$chain = new Chain(new Gpt($platform), [$processor], [$processor]);
215+
$chain = new Chain($platform, $llm, [$processor], [$processor]);
213216

214217
$messages = new MessageBag(
215218
Message::forSystem(<<<PROMPT
@@ -250,11 +253,11 @@ the response back to PHP objects.
250253
To achieve this, a specific chain processor needs to be registered:
251254
```php
252255
use PhpLlm\LlmChain\Chain;
253-
use PhpLlm\LlmChain\Message\Message;
254-
use PhpLlm\LlmChain\Message\MessageBag;
255-
use PhpLlm\LlmChain\StructuredOutput\ChainProcessor;
256-
use PhpLlm\LlmChain\StructuredOutput\ResponseFormatFactory;
257-
use PhpLlm\LlmChain\Tests\StructuredOutput\Data\MathReasoning;
256+
use PhpLlm\LlmChain\Model\Message\Message;
257+
use PhpLlm\LlmChain\Model\Message\MessageBag;
258+
use PhpLlm\LlmChain\Chain\StructuredOutput\ChainProcessor;
259+
use PhpLlm\LlmChain\Chain\StructuredOutput\ResponseFormatFactory;
260+
use PhpLlm\LlmChain\Tests\Chain\StructuredOutput\Data\MathReasoning;
258261
use Symfony\Component\Serializer\Encoder\JsonEncoder;
259262
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
260263
use Symfony\Component\Serializer\Serializer;
@@ -263,7 +266,7 @@ use Symfony\Component\Serializer\Serializer;
263266

264267
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
265268
$processor = new ChainProcessor(new ResponseFormatFactory(), $serializer);
266-
$chain = new Chain($llm, [$processor], [$processor]);
269+
$chain = new Chain($platform, $llm, [$processor], [$processor]);
267270

268271
$messages = new MessageBag(
269272
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
@@ -279,8 +282,8 @@ dump($response->getContent()); // returns an instance of `MathReasoning` class
279282
Also PHP array structures as `response_format` are supported, which also requires the chain processor mentioned above:
280283

281284
```php
282-
use PhpLlm\LlmChain\Message\Message;
283-
use PhpLlm\LlmChain\Message\MessageBag;
285+
use PhpLlm\LlmChain\Model\Message\Message;
286+
use PhpLlm\LlmChain\Model\Message\MessageBag;
284287

285288
// Initialize Platform, LLM and Chain with processors and Clock tool
286289

@@ -380,9 +383,9 @@ needs to be used.
380383
Some LLMs also support images as input, which LLM Chain supports as `Content` type within the `UserMessage`:
381384

382385
```php
383-
use PhpLlm\LlmChain\Message\Content\Image;
384-
use PhpLlm\LlmChain\Message\Message;
385-
use PhpLlm\LlmChain\Message\MessageBag;
386+
use PhpLlm\LlmChain\Model\Message\Content\Image;
387+
use PhpLlm\LlmChain\Model\Message\Message;
388+
use PhpLlm\LlmChain\Model\Message\MessageBag;
386389

387390
// Initialize Platoform, LLM & Chain
388391

@@ -411,16 +414,15 @@ therefore LLM Chain implements a `EmbeddingsModel` interface with various models
411414
The standalone usage results in an `Vector` instance:
412415

413416
```php
414-
use PhpLlm\LlmChain\OpenAI\Model\Embeddings;
415-
use PhpLlm\LlmChain\OpenAI\Model\Embeddings\Version;
417+
use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings;
416418

417419
// Initialize Platform
418420

419-
$embeddings = new Embeddings($platform, Version::textEmbedding3Small());
421+
$embeddings = new Embeddings($platform, Embeddings::TEXT_3_SMALL);
420422

421-
$vector = $embeddings->create($textInput);
423+
$vectors = $platform->request($embeddings, $textInput)->getContent();
422424

423-
dump($vector->getData()); // Array of float values
425+
dump($vectors[0]->getData()); // Array of float values
424426
```
425427

426428
#### Code Examples
@@ -436,9 +438,9 @@ interface. They are provided while instantiating the Chain instance:
436438
```php
437439
use PhpLlm\LlmChain\Chain;
438440

439-
// Initialize LLM and processors
441+
// Initialize Platform, LLM and processors
440442

441-
$chain = new Chain($llm, $inputProcessors, $outputProcessors);
443+
$chain = new Chain($platform, $llm, $inputProcessors, $outputProcessors);
442444
```
443445

444446
#### InputProcessor
@@ -449,7 +451,7 @@ able to mutate both on top of the `Input` instance provided.
449451
```php
450452
use PhpLlm\LlmChain\Chain\Input;
451453
use PhpLlm\LlmChain\Chain\InputProcessor;
452-
use PhpLlm\LlmChain\Message\AssistantMessage
454+
use PhpLlm\LlmChain\Model\Message\AssistantMessage
453455

454456
final class MyProcessor implements InputProcessor
455457
{
@@ -474,7 +476,7 @@ mutate or replace the given response:
474476
```php
475477
use PhpLlm\LlmChain\Chain\Output;
476478
use PhpLlm\LlmChain\Chain\OutputProcessor;
477-
use PhpLlm\LlmChain\Message\AssistantMessage
479+
use PhpLlm\LlmChain\Model\Message\AssistantMessage
478480

479481
final class MyProcessor implements OutputProcessor
480482
{
@@ -499,7 +501,7 @@ use PhpLlm\LlmChain\Chain\ChainAwareProcessor;
499501
use PhpLlm\LlmChain\Chain\ChainAwareTrait;
500502
use PhpLlm\LlmChain\Chain\Output;
501503
use PhpLlm\LlmChain\Chain\OutputProcessor;
502-
use PhpLlm\LlmChain\Message\AssistantMessage
504+
use PhpLlm\LlmChain\Model\Message\AssistantMessage
503505

504506
final class MyProcessor implements OutputProcessor, ChainAwareProcessor
505507
{

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"mongodb/mongodb": "^1.20",
2929
"php-cs-fixer/shim": "^3.64",
3030
"phpstan/phpstan": "^1.12",
31+
"phpstan/phpstan-webmozart-assert": "^1.2",
3132
"phpunit/phpunit": "^11.3",
3233
"probots-io/pinecone-php": "^1.0",
3334
"rector/rector": "^1.2",

examples/chat-claude-anthropic.php

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

3+
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
4+
use PhpLlm\LlmChain\Bridge\Anthropic\PlatformFactory;
35
use PhpLlm\LlmChain\Chain;
4-
use PhpLlm\LlmChain\Message\Message;
5-
use PhpLlm\LlmChain\Message\MessageBag;
6-
use PhpLlm\LlmChain\Model\Language\Claude;
7-
use PhpLlm\LlmChain\Platform\Anthropic;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
88
use Symfony\Component\Dotenv\Dotenv;
9-
use Symfony\Component\HttpClient\HttpClient;
109

1110
require_once dirname(__DIR__).'/vendor/autoload.php';
1211
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
@@ -16,10 +15,10 @@
1615
exit(1);
1716
}
1817

19-
$platform = new Anthropic(HttpClient::create(), $_ENV['ANTHROPIC_API_KEY']);
20-
$llm = new Claude($platform);
18+
$platform = PlatformFactory::create($_ENV['ANTHROPIC_API_KEY']);
19+
$llm = new Claude();
2120

22-
$chain = new Chain($llm);
21+
$chain = new Chain($platform, $llm);
2322
$messages = new MessageBag(
2423
Message::forSystem('You are a pirate and you write funny.'),
2524
Message::ofUser('What is the Symfony framework?'),

examples/chat-gpt-azure.php

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

3+
use PhpLlm\LlmChain\Bridge\Azure\OpenAI\PlatformFactory;
4+
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
35
use PhpLlm\LlmChain\Chain;
4-
use PhpLlm\LlmChain\Message\Message;
5-
use PhpLlm\LlmChain\Message\MessageBag;
6-
use PhpLlm\LlmChain\Model\Language\Gpt;
7-
use PhpLlm\LlmChain\Platform\OpenAI\Azure;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
88
use Symfony\Component\Dotenv\Dotenv;
9-
use Symfony\Component\HttpClient\HttpClient;
109

1110
require_once dirname(__DIR__).'/vendor/autoload.php';
1211
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
@@ -17,15 +16,15 @@
1716
exit(1);
1817
}
1918

20-
$platform = new Azure(HttpClient::create(),
19+
$platform = PlatformFactory::create(
2120
$_ENV['AZURE_OPENAI_BASEURL'],
2221
$_ENV['AZURE_OPENAI_DEPLOYMENT'],
2322
$_ENV['AZURE_OPENAI_VERSION'],
2423
$_ENV['AZURE_OPENAI_KEY'],
2524
);
26-
$llm = new Gpt($platform, Gpt::GPT_4O_MINI);
25+
$llm = new GPT(GPT::GPT_4O_MINI);
2726

28-
$chain = new Chain($llm);
27+
$chain = new Chain($platform, $llm);
2928
$messages = new MessageBag(
3029
Message::forSystem('You are a pirate and you write funny.'),
3130
Message::ofUser('What is the Symfony framework?'),

examples/chat-gpt-openai.php

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

3+
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
4+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
35
use PhpLlm\LlmChain\Chain;
4-
use PhpLlm\LlmChain\Message\Message;
5-
use PhpLlm\LlmChain\Message\MessageBag;
6-
use PhpLlm\LlmChain\Model\Language\Gpt;
7-
use PhpLlm\LlmChain\Platform\OpenAI\OpenAI;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
88
use Symfony\Component\Dotenv\Dotenv;
9-
use Symfony\Component\HttpClient\HttpClient;
109

1110
require_once dirname(__DIR__).'/vendor/autoload.php';
1211
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
@@ -16,12 +15,12 @@
1615
exit(1);
1716
}
1817

19-
$platform = new OpenAI(HttpClient::create(), $_ENV['OPENAI_API_KEY']);
20-
$llm = new Gpt($platform, Gpt::GPT_4O_MINI, [
18+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
19+
$llm = new GPT(GPT::GPT_4O_MINI, [
2120
'temperature' => 0.5, // default options for the model
2221
]);
2322

24-
$chain = new Chain($llm);
23+
$chain = new Chain($platform, $llm);
2524
$messages = new MessageBag(
2625
Message::forSystem('You are a pirate and you write funny.'),
2726
Message::ofUser('What is the Symfony framework?'),

examples/chat-llama-ollama.php

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

3+
use PhpLlm\LlmChain\Bridge\Meta\Llama;
4+
use PhpLlm\LlmChain\Bridge\Ollama\PlatformFactory;
35
use PhpLlm\LlmChain\Chain;
4-
use PhpLlm\LlmChain\Message\Message;
5-
use PhpLlm\LlmChain\Message\MessageBag;
6-
use PhpLlm\LlmChain\Model\Language\Llama;
7-
use PhpLlm\LlmChain\Platform\Ollama;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
88
use Symfony\Component\Dotenv\Dotenv;
9-
use Symfony\Component\HttpClient\HttpClient;
109

1110
require_once dirname(__DIR__).'/vendor/autoload.php';
1211
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
@@ -16,10 +15,10 @@
1615
exit(1);
1716
}
1817

19-
$platform = new Ollama(HttpClient::create(), $_ENV['OLLAMA_HOST_URL']);
20-
$llm = new Llama($platform);
18+
$platform = PlatformFactory::create($_ENV['OLLAMA_HOST_URL']);
19+
$llm = new Llama('llama3.2');
2120

22-
$chain = new Chain($llm);
21+
$chain = new Chain($platform, $llm);
2322
$messages = new MessageBag(
2423
Message::forSystem('You are a helpful assistant.'),
2524
Message::ofUser('Tina has one brother and one sister. How many sisters do Tina\'s siblings have?'),

0 commit comments

Comments
 (0)