Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ vendor
.phpunit.cache
coverage
.env.local
.transformers-cache
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,33 @@ dump($response->getContent());
1. [Translation](examples/huggingface/translation.php)
1. [Zero-shot Classification](examples/huggingface/zero-shot-classification.php)

## TransformerPHP

With installing the library `codewithkyrian/transformers` it is possible to run [ONNX](https://onnx.ai/) models locally
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)
and comes with an extra setup, see [TransformersPHP's Getting Starter](https://transformers.codewithkyrian.com/getting-started).

The usage with LLM Chain is similar to the HuggingFace integration, and also requires the `task` option to be set:

```php
use Codewithkyrian\Transformers\Pipelines\Task;
use PhpLlm\LlmChain\Bridge\TransformersPHP\Model;
use PhpLlm\LlmChain\Bridge\TransformersPHP\PlatformFactory;

$platform = PlatformFactory::create();
$model = new Model('Xenova/LaMini-Flan-T5-783M');

$response = $platform->request($model, 'How many continents are there in the world?', [
'task' => Task::Text2TextGeneration,
]);

echo $response->getContent().PHP_EOL;
```

#### Code Examples

1. [Text Generation with TransformersPHP](examples/transformers-text-generation.php)

## Contributions

Contributions are always welcome, so feel free to join the development of this library. To get started, please read the
Expand Down
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"require-dev": {
"codewithkyrian/chromadb-php": "^0.2.1 || ^0.3",
"codewithkyrian/transformers": "^0.5.3",
"mongodb/mongodb": "^1.21",
"php-cs-fixer/shim": "^3.70",
"phpstan/phpstan": "^2.0",
Expand All @@ -53,6 +54,7 @@
},
"suggest": {
"codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.",
"codewithkyrian/transformers": "For using the TransformersPHP with FFI to run models in PHP.",
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.",
"symfony/css-selector": "For using the YouTube transcription tool.",
Expand All @@ -69,6 +71,9 @@
}
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"codewithkyrian/transformers-libsloader": true
}
}
}
26 changes: 26 additions & 0 deletions examples/transformers-text-generation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Codewithkyrian\Transformers\Pipelines\Task;
use PhpLlm\LlmChain\Bridge\TransformersPHP\Model;
use PhpLlm\LlmChain\Bridge\TransformersPHP\PlatformFactory;

require_once dirname(__DIR__).'/vendor/autoload.php';

if (!extension_loaded('ffi') || '1' !== ini_get('ffi.enable')) {
echo 'FFI extension is not loaded or enabled. Please enable it in your php.ini file.'.PHP_EOL;
echo 'See https://github.com/CodeWithKyrian/transformers-php for setup instructions.'.PHP_EOL;
exit(1);
}

if (!is_dir(dirname(__DIR__).'/.transformers-cache/Xenova/LaMini-Flan-T5-783M')) {
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;
}

$platform = PlatformFactory::create();
$model = new Model('Xenova/LaMini-Flan-T5-783M');

$response = $platform->request($model, 'How many continents are there in the world?', [
'task' => Task::Text2TextGeneration,
]);

echo $response->getContent().PHP_EOL;
58 changes: 58 additions & 0 deletions src/Bridge/TransformersPHP/Handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\TransformersPHP;

use Codewithkyrian\Transformers\Pipelines\Task;
use PhpLlm\LlmChain\Model\Model as BaseModel;
use PhpLlm\LlmChain\Model\Response\ResponseInterface as LlmResponse;
use PhpLlm\LlmChain\Model\Response\StructuredResponse;
use PhpLlm\LlmChain\Model\Response\TextResponse;
use PhpLlm\LlmChain\Platform\ModelClient;
use PhpLlm\LlmChain\Platform\ResponseConverter;
use Symfony\Contracts\HttpClient\ResponseInterface;

use function Codewithkyrian\Transformers\Pipelines\pipeline;

final readonly class Handler implements ModelClient, ResponseConverter
{
public function supports(BaseModel $model, object|array|string $input): bool
{
return $model instanceof Model;
}

public function request(BaseModel $model, object|array|string $input, array $options = []): ResponseInterface
{
if (!isset($options['task'])) {
throw new \InvalidArgumentException('The task option is required.');
}

$pipeline = pipeline(
$options['task'],
$model->getName(),
$options['quantized'] ?? true,
$options['config'] ?? null,
$options['cacheDir'] ?? null,
$options['revision'] ?? 'main',
$options['modelFilename'] ?? null,
);

return new PipelineResponse($pipeline, $input);
}

public function convert(ResponseInterface $response, array $options = []): LlmResponse
{
if (!$response instanceof PipelineResponse) {
throw new \InvalidArgumentException('The response is not a valid TransformersPHP response.');
}

$task = $options['task'];
$data = $response->toArray();

return match ($task) {
Task::Text2TextGeneration => new TextResponse($data[0]['generated_text']),
default => new StructuredResponse($response->toArray()),
};
}
}
30 changes: 30 additions & 0 deletions src/Bridge/TransformersPHP/Model.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\TransformersPHP;

use PhpLlm\LlmChain\Model\Model as BaseModel;

final readonly class Model implements BaseModel
{
/**
* @param string $name the name of the model is optional with TransformersPHP
* @param array<string, mixed> $options
*/
public function __construct(
private ?string $name = null,
private array $options = [],
) {
}

public function getName(): string
{
return $this->name ?? '';
}

public function getOptions(): array
{
return $this->options;
}
}
58 changes: 58 additions & 0 deletions src/Bridge/TransformersPHP/PipelineResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\TransformersPHP;

use Codewithkyrian\Transformers\Pipelines\Pipeline;
use Symfony\Contracts\HttpClient\ResponseInterface;

final class PipelineResponse implements ResponseInterface
{
/**
* @var array<mixed>
*/
private array $result;

/**
* @param object|array<mixed>|string $input
*/
public function __construct(
private readonly Pipeline $pipeline,
private readonly object|array|string $input,
) {
}

public function getStatusCode(): int
{
return 200;
}

public function getHeaders(bool $throw = true): array
{
return [];
}

public function getContent(bool $throw = true): string
{
throw new \RuntimeException('Not implemented');
}

/**
* @return array<mixed>
*/
public function toArray(bool $throw = true): array
{
return $this->result ?? $this->result = ($this->pipeline)($this->input);
}

public function cancel(): void
{
throw new \RuntimeException('Not implemented');
}

public function getInfo(?string $type = null): mixed
{
throw new \RuntimeException('Not implemented');
}
}
20 changes: 20 additions & 0 deletions src/Bridge/TransformersPHP/PlatformFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\TransformersPHP;

use Codewithkyrian\Transformers\Transformers;
use PhpLlm\LlmChain\Platform;

final readonly class PlatformFactory
{
public static function create(): Platform
{
if (!class_exists(Transformers::class)) {
throw new \RuntimeException('TransformersPHP is not installed. Please install it using "composer require codewithkyrian/transformers".');
}

return new Platform([$handler = new Handler()], [$handler]);
}
}