Skip to content

Commit 6b6d8f1

Browse files
authored
feat: add support for TransformersPHP for model runtime in PHP (#280)
Sitting on top of https://github.com/CodeWithKyrian/transformers-php
1 parent e38cb0d commit 6b6d8f1

File tree

8 files changed

+226
-1
lines changed

8 files changed

+226
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ vendor
44
.phpunit.cache
55
coverage
66
.env.local
7+
.transformers-cache

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,33 @@ dump($response->getContent());
773773
1. [Translation](examples/huggingface/translation.php)
774774
1. [Zero-shot Classification](examples/huggingface/zero-shot-classification.php)
775775
776+
## TransformerPHP
777+
778+
With installing the library `codewithkyrian/transformers` it is possible to run [ONNX](https://onnx.ai/) models locally
779+
without the need of an extra tool like Ollama or a cloud service. This requires [FFI](https://www.php.net/manual/en/book.ffi.php)
780+
and comes with an extra setup, see [TransformersPHP's Getting Starter](https://transformers.codewithkyrian.com/getting-started).
781+
782+
The usage with LLM Chain is similar to the HuggingFace integration, and also requires the `task` option to be set:
783+
784+
```php
785+
use Codewithkyrian\Transformers\Pipelines\Task;
786+
use PhpLlm\LlmChain\Bridge\TransformersPHP\Model;
787+
use PhpLlm\LlmChain\Bridge\TransformersPHP\PlatformFactory;
788+
789+
$platform = PlatformFactory::create();
790+
$model = new Model('Xenova/LaMini-Flan-T5-783M');
791+
792+
$response = $platform->request($model, 'How many continents are there in the world?', [
793+
'task' => Task::Text2TextGeneration,
794+
]);
795+
796+
echo $response->getContent().PHP_EOL;
797+
```
798+
799+
#### Code Examples
800+
801+
1. [Text Generation with TransformersPHP](examples/transformers-text-generation.php)
802+
776803
## Contributions
777804

778805
Contributions are always welcome, so feel free to join the development of this library. To get started, please read the

composer.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
},
3333
"require-dev": {
3434
"codewithkyrian/chromadb-php": "^0.2.1 || ^0.3",
35+
"codewithkyrian/transformers": "^0.5.3",
3536
"mongodb/mongodb": "^1.21",
3637
"php-cs-fixer/shim": "^3.70",
3738
"phpstan/phpstan": "^2.0",
@@ -53,6 +54,7 @@
5354
},
5455
"suggest": {
5556
"codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.",
57+
"codewithkyrian/transformers": "For using the TransformersPHP with FFI to run models in PHP.",
5658
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
5759
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.",
5860
"symfony/css-selector": "For using the YouTube transcription tool.",
@@ -69,6 +71,9 @@
6971
}
7072
},
7173
"config": {
72-
"sort-packages": true
74+
"sort-packages": true,
75+
"allow-plugins": {
76+
"codewithkyrian/transformers-libsloader": true
77+
}
7378
}
7479
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use Codewithkyrian\Transformers\Pipelines\Task;
4+
use PhpLlm\LlmChain\Bridge\TransformersPHP\Model;
5+
use PhpLlm\LlmChain\Bridge\TransformersPHP\PlatformFactory;
6+
7+
require_once dirname(__DIR__).'/vendor/autoload.php';
8+
9+
if (!extension_loaded('ffi') || '1' !== ini_get('ffi.enable')) {
10+
echo 'FFI extension is not loaded or enabled. Please enable it in your php.ini file.'.PHP_EOL;
11+
echo 'See https://github.com/CodeWithKyrian/transformers-php for setup instructions.'.PHP_EOL;
12+
exit(1);
13+
}
14+
15+
if (!is_dir(dirname(__DIR__).'/.transformers-cache/Xenova/LaMini-Flan-T5-783M')) {
16+
echo 'Model "Xenova/LaMini-Flan-T5-783M" not found. Downloading it will be part of the first run. This may take a while...'.PHP_EOL;
17+
}
18+
19+
$platform = PlatformFactory::create();
20+
$model = new Model('Xenova/LaMini-Flan-T5-783M');
21+
22+
$response = $platform->request($model, 'How many continents are there in the world?', [
23+
'task' => Task::Text2TextGeneration,
24+
]);
25+
26+
echo $response->getContent().PHP_EOL;
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\TransformersPHP;
6+
7+
use Codewithkyrian\Transformers\Pipelines\Task;
8+
use PhpLlm\LlmChain\Model\Model as BaseModel;
9+
use PhpLlm\LlmChain\Model\Response\ResponseInterface as LlmResponse;
10+
use PhpLlm\LlmChain\Model\Response\StructuredResponse;
11+
use PhpLlm\LlmChain\Model\Response\TextResponse;
12+
use PhpLlm\LlmChain\Platform\ModelClient;
13+
use PhpLlm\LlmChain\Platform\ResponseConverter;
14+
use Symfony\Contracts\HttpClient\ResponseInterface;
15+
16+
use function Codewithkyrian\Transformers\Pipelines\pipeline;
17+
18+
final readonly class Handler implements ModelClient, ResponseConverter
19+
{
20+
public function supports(BaseModel $model, object|array|string $input): bool
21+
{
22+
return $model instanceof Model;
23+
}
24+
25+
public function request(BaseModel $model, object|array|string $input, array $options = []): ResponseInterface
26+
{
27+
if (!isset($options['task'])) {
28+
throw new \InvalidArgumentException('The task option is required.');
29+
}
30+
31+
$pipeline = pipeline(
32+
$options['task'],
33+
$model->getName(),
34+
$options['quantized'] ?? true,
35+
$options['config'] ?? null,
36+
$options['cacheDir'] ?? null,
37+
$options['revision'] ?? 'main',
38+
$options['modelFilename'] ?? null,
39+
);
40+
41+
return new PipelineResponse($pipeline, $input);
42+
}
43+
44+
public function convert(ResponseInterface $response, array $options = []): LlmResponse
45+
{
46+
if (!$response instanceof PipelineResponse) {
47+
throw new \InvalidArgumentException('The response is not a valid TransformersPHP response.');
48+
}
49+
50+
$task = $options['task'];
51+
$data = $response->toArray();
52+
53+
return match ($task) {
54+
Task::Text2TextGeneration => new TextResponse($data[0]['generated_text']),
55+
default => new StructuredResponse($response->toArray()),
56+
};
57+
}
58+
}

src/Bridge/TransformersPHP/Model.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\TransformersPHP;
6+
7+
use PhpLlm\LlmChain\Model\Model as BaseModel;
8+
9+
final readonly class Model implements BaseModel
10+
{
11+
/**
12+
* @param string $name the name of the model is optional with TransformersPHP
13+
* @param array<string, mixed> $options
14+
*/
15+
public function __construct(
16+
private ?string $name = null,
17+
private array $options = [],
18+
) {
19+
}
20+
21+
public function getName(): string
22+
{
23+
return $this->name ?? '';
24+
}
25+
26+
public function getOptions(): array
27+
{
28+
return $this->options;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\TransformersPHP;
6+
7+
use Codewithkyrian\Transformers\Pipelines\Pipeline;
8+
use Symfony\Contracts\HttpClient\ResponseInterface;
9+
10+
final class PipelineResponse implements ResponseInterface
11+
{
12+
/**
13+
* @var array<mixed>
14+
*/
15+
private array $result;
16+
17+
/**
18+
* @param object|array<mixed>|string $input
19+
*/
20+
public function __construct(
21+
private readonly Pipeline $pipeline,
22+
private readonly object|array|string $input,
23+
) {
24+
}
25+
26+
public function getStatusCode(): int
27+
{
28+
return 200;
29+
}
30+
31+
public function getHeaders(bool $throw = true): array
32+
{
33+
return [];
34+
}
35+
36+
public function getContent(bool $throw = true): string
37+
{
38+
throw new \RuntimeException('Not implemented');
39+
}
40+
41+
/**
42+
* @return array<mixed>
43+
*/
44+
public function toArray(bool $throw = true): array
45+
{
46+
return $this->result ?? $this->result = ($this->pipeline)($this->input);
47+
}
48+
49+
public function cancel(): void
50+
{
51+
throw new \RuntimeException('Not implemented');
52+
}
53+
54+
public function getInfo(?string $type = null): mixed
55+
{
56+
throw new \RuntimeException('Not implemented');
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\TransformersPHP;
6+
7+
use Codewithkyrian\Transformers\Transformers;
8+
use PhpLlm\LlmChain\Platform;
9+
10+
final readonly class PlatformFactory
11+
{
12+
public static function create(): Platform
13+
{
14+
if (!class_exists(Transformers::class)) {
15+
throw new \RuntimeException('TransformersPHP is not installed. Please install it using "composer require codewithkyrian/transformers".');
16+
}
17+
18+
return new Platform([$handler = new Handler()], [$handler]);
19+
}
20+
}

0 commit comments

Comments
 (0)