Это вторая часть статьи.
Часть 1: Практика без Python и data science
AI в PHP: не теория, а место, с которого можно начать
В своей прошлой статье я описал на довольно общем уровне почему тема AI вроде бы везде, но при этом почти не пересекается с повседневной PHP-разработкой. Не потому что PHP “не подходит”, а потому что сам разговор обычно идёт мимо наших задач и привычного способа мышления. Ну и, конечно, о том, что почти нет материала, который объясняет AI именно для PHP-разработчиков, их задач и их мышления.
После публикации мне несколько раз задали один и тот же вопрос, в разных формулировках:
Окей, допустим. А с чего конкретно начать?
И это, пожалуй, самый интересный вопрос из тех, что я получил. Ниже я попытаюсь дать на него ответ.
Про “точку входа”
Когда вы слышите “использование AI в проекте”, скорее всего в вашей голове сразу возникает слишком много лишнего: инфраструктура, обучение моделей, эксперименты, отдельные сервисы, новые роли в команде и т.д.
Но если честно, в большинстве PHP-проектов нам это просто не нужно (хотя разобраться в этом самому – жутко интересная задача).
Но всё же, нам с вами не нужно:
-
обучать модели с нуля
-
разбираться в градиентном спуске
-
собирать датасеты и т.д.
Нужно понять, что AI ≠ Data Science. Друзья, в большинстве PHP-проектов никто не обучает модели, пожалуйста, запомните это! По крайней мере в прикладных PHP-проектах, где мы говорим об использовании готовых моделей, а не об исследованиях и обучении.
Нам нужно вз��ть готовый инструмент и аккуратно встроить его в уже существующую логику. Так же, как мы это делаем с любой другой библиотекой.
И вот тут начинается интересное.
Что это вообще за зверь такой TransformersPHP и с чем его едят?
Про использование LLM через API знают уже все. Это полезно и удобно, но в таком виде AI остаётся чем-то внешним: сервисом, к которому ты просто отправляешь текст. Внутри – туман.
В какой-то момент мне захотелось посмотреть на более “приземлённый” уровень: не генерация текста, а представление семантики: эмбеддинги, задачи классификации и поиска. Именно на этом уровне решается большинство прикладных задач в бэкенд-системах: поиск по тексту, сравнение и автоматическая классификация, а не диалоговое взаимодействие человека с моделью.
И здесь неожиданно выяснилось, что есть инструменты, которые позволяют делать это напрямую в PHP, без Python-стека. Один из них – TransformersPHP.
Важно сразу понять: это не попытка превратить PHP в Python и не универсальное решение. Это библиотека для inference (инференс) – использования уже обученных моделей.
Как по мне, TransformersPHP – один из самых интересных и показательных проектов в современной PHP ML-экосистеме. Отдельное спасибо его автору – Kyrian Obikwelu за то что создал этот проект и продолжает над ним работать. В общем это библиотека, которая позволяет использовать трансформер-модели (BERT, RoBERTa, DistilBERT и др.) напрямую из PHP, без Python и без внешних API.
По сути, это PHP-ориентированная обертка над идеями Hugging Face Transformers, адаптированная под PHP-экосистему и реальные прикладные сценарии.
Ключевая особенность библиотеки – локальный инференс. Модели загружаются и выполняются на стороне PHP-приложения (через ONNX Runtime), что открывает важные архитектурные возможности:
-
отсутствие сетевых вызовов к LLM API
-
полный контроль над данными (это может быть важно для privacy (конфиденциальности) в вашей работе)
-
предсказуемое и стабильное время обработки запроса
-
возможность оффлайн-работы (после первого запуска и загрузки модели)
TransformersPHP поддерживает типовые задачи NLP, такие как: получение эмбеддингов, классификацию текста, семантическое сравнение и прочее.
Что мне нравится больше всего
Самое ценное ощущение – отсутствие разрыва контекста.
Я остаюсь в PHP, я пишу тот же код, у меня тот же деплой и у меня те же подходы к архитектуре. Ничего не изменилось. Модель для меня в этом случае – не некий “магический объект”, а просто ещё один источник данных. Да, непривычный, но вполне объяснимый – не хуже и не лучше других.
И это сильно меняет отношение к всей теме. Вы согласны?
Запуск модели за 10 минут
Один из самых важных моментов – это первый запуск.
Если он сложный, на этом всё обычно и заканчивается.
В случае с TransformersPHP ощущение как раз обратное: это больше похоже на работу с обычной зависимостью.
Полное руководство по установке можно найти на сайте документации.
Мы же с вами для простоты запустим докер контейнер со всеми необходимыми зависимостями. Да, Docker file выглядит объёмно – но это одноразовая инфраструктура. Сам запуск и первый демо-пример действительно укладываются в несколько минут.
Что нам нужно (требования)
-
PHP 8.1 или выше
-
Composer
-
Расширение PHP FFI
-
JIT-компиляция (опционально, для повышения производительности)
-
Увеличенный лимит памяти (для сложных задач, таких как генерация текста)
Структура проекта
/project/
├── app/
│ ├── demo.php
│ ├── semantic-search.php
├── docker/
│ ├── Dockerfile
├── docker-composer.yaml
├── composer.json
Установка (Docker)
Файл: docker-compose.yml
Файл: docker/Dockerfile
# ------------------------------
# Install system dependencies
# ------------------------------
RUN apt-get update && apt-get install -y
libzip-dev
zip
unzip
git
libxml2-dev
libcurl4-openssl-dev
libpng-dev
libonig-dev
&& rm -rf /var/lib/apt/lists/*
# ------------------------------
# Install PHP extensions
# ------------------------------
RUN docker-php-ext-install zip pdo_mysql bcmath xml mbstring curl gd pcntl
# ------------------------------
# Enable FFI
# ------------------------------
RUN apt-get update && apt-get install -y
libffi-dev
pkg-config
&& rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install ffi
RUN echo "ffi.enable=1" > /usr/local/etc/php/conf.d/ffi.ini
# ------------------------------
# Install ONNX Runtime
# ------------------------------
ENV ONNXRUNTIME_VERSION=1.17.1
RUN curl -L https://github.com/microsoft/onnxruntime/releases/download/v${ONNXRUNTIME_VERSION}/onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}.tgz
| tar -xz
&& cp onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}/lib/libonnxruntime.so* /usr/lib/
&& ldconfig
&& rm -rf onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}
# ------------------------------
# Install Composer
# ------------------------------
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www
# Copy existing application directory contents
COPY . /var/www
# Configure PHP
RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/docker-php-ram-limit.ini
RUN echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/docker-php-max-execution-time.ini
# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]
И для установки самого TransformersPHP
Файл: composer.json
Команда для запуска окружения
docker compose build --pull
docker compose up -d
docker compose exec app /bin/bash -c "composer install"
Идея простая: чтобы пример можно было поднять локально без ручной настройки окружения.
Базовый демо-пример
Начнём с самого простого: анализа настроений.
Если всё, что описано выше сработало хорошо и установка прошла нормально, можно запустить пример, описанный ниже (примите во внимание, что первый запуск может занять несколько секунд):
docker compose exec app php app/demo.php
Пример использования выглядит концептуально просто: вы загружаете предобученную модель и применяете ее к тексту так же, как это делали бы в Python – но уже внутри PHP-кода. TransformersPHP предлагает простой pipeline API для задач вроде анализа настроений, классификации текста, семантического сравнения и т.д. В примере ниже модель определяет тональность двух фраз и показывает метку и score.
Файл: app/demo.php
require_once __DIR__ . '/../vendor/autoload.php';
use function CodewithkyrianTransformersPipelinespipeline;
// Выделить конвейер для анализа настроений
$classifier = pipeline('sentiment-analysis');
$out = $classifier(['I love transformers!']);
echo 'I love transformers!';
echo print_r($out, true);
$out = $classifier(['I hate transformers!']);
echo 'I hate transformers!';
echo print_r($out, true);
Результат, конечно, же вполне ожидаемый:
I love transformers!
Array (
[label] => POSITIVE
[score] => 0.99978870153427
)
I hate transformers!
Array (
[label] => NEGATIVE
[score] => 0.99863630533218
)
Цель этого примера – снять у вас психологический барьер: эта модель в PHP — это просто ещё один объект, с которым можно работать.
Этот же пример можно запустить онлайн.
Важно понимать архитектурную роль TransformersPHP.
Эта библиотека не конкурирует с большими LLM-сервисами вроде GPT или Claude. Она закрывает другой, очень важный слой:
-
быстрые эмбеддинги
-
локальная классификация
-
семантический поиск
-
lightweight NLP без внешних зависимостей
В связке с PHP это выглядит особенно логично. PHP остается центральным слоем бизнес-логики, а трансформеры становятся встроенным инструментом, а не удаленным сервисом. TransformersPHP – это хороший пример того, как современный ML постепенно перестает быть “чужим” для PHP и становится частью его нативной экосистемы, пусть и через аккуратные инженерные мосты вроде ONNX.
Реальный кейс: семантический поиск по событиям
Учебные примеры хороши, но быстро надоедают. Гораздо интереснее посмотреть на задачу, которая реально встречается в бэкенде. Сейчас мы с вами сделаем кое-что поинтересней.
Для запуска этого примера используйте следующую команду
docker compose exec app php app/semantic-search.php
Сценарий
Есть события с коротким текстовым описанием. Пользователь ищет, например:
-
“санкции против IT-компаний”
-
“космическая гонка среди стран региона”
При этом, как вы понимаете, в самих данных таких формулировок может не быть вообще. Классический поиск по словам здесь начинает усложняться: синонимы, морфология, разные языки, костыли поверх костылей и прочее.
Небольшое мысленное упражнение
Допустим, у нас есть лента событий или материалов, где каждое событие описано парой предложений. Что-то вроде:
-
введены новые ограничения в отношении технологических корпораций
-
страны региона наращивают инвестиции в спутниковые программы
-
обострение конфликта на политической почве в нескольких провинциях
Теперь пользователь вводит запрос: “космическая гонка среди стран региона”.
Ни одно из этих слов буквально не обязано встречаться в описании событий. Упс… И это нормально – люди редко формулируют мысли так же, как их описывают системы.
Идея решения
Вместо того чтобы пытаться угадать слова, можно попробовать искать по смыслу. Не в философском смысле, а в инженерном:
-
описание события → вектор,
-
запрос пользователя → вектор,
-
дальше – обычный поиск ближайших значений.
То есть, нам нужно использовать эмбеддинги как универсальный индекс смысла.
Таким образом мы:
-
Берём массив событий
{id, title, description} -
Считаем эмбеддинг только по
description(заголовок часто слишком короткий, неинформативный и может добавлять шум) -
Эмбеддим пользовательский запрос
-
Ищем ближайшие векторы
-
Сортируем и возвращаем результат
Без обучения моделей и без сложной инфраструктуры. Эмбеддинги для событий считаются один раз и могут храниться где угодно. Запрос пользователя обрабатывается в момент поиска. Дальше – сортировка и вывод результатов.
И… вуаля!
Никакого обучения моделей.
Никакой магии.
Просто другой способ представить текст.
Логика работы
Поместим логику работы в отдельный класс SemanticEventSearch. Этот класс не претендует на звание лучшего кода на планете, и написан только в в демонстрационных целях – поэтому опустим замечания по его качеству.
Класс SemanticEventSearch
final class SemanticEventSearch
{
private string $model = 'Xenova/paraphrase-multilingual-MiniLM-L12-v2';
private string $cachePath;
private string $defaultQuery = 'санкции против IT-компаний';
private int $topN;
private ?string $query = null;
/** @var list<array{id:int,title:string,description:string}> */
private array $events;
/** @var array<int, list<float|int>> */
private array $eventEmbeddingsById = [];
private $embedder;
/**
* Create a new semantic search instance.
*
* @param int $topN Number of results to return.
*/
public function __construct(int $topN = 3)
{
$this->cachePath = __DIR__ . '/../embeddings.events.json';
$this->events = [];
$this->embedder = null;
$this->topN = $topN;
}
/**
* Inject events that will be indexed/searched.
*
* @param list<array{id:int,title:string,description:string}> $events
* @return $this
*/
public function setEvents(array $events): self
{
$this->events = $events;
$this->eventEmbeddingsById = [];
return $this;
}
/**
* Set the embeddings model identifier.
*
* Switching model invalidates in-memory embeddings.
*
* @param string $model
* @return $this
*/
public function setModel(string $model): self
{
$this->model = $model;
$this->embedder = null;
$this->eventEmbeddingsById = [];
return $this;
}
/**
* Set the query to be searched.
*
* @param string $query
* @return $this
*/
public function setQuery(string $query): self
{
$q = trim($query);
$this->query = $q === '' ? null : $q;
return $this;
}
/**
* Run the end-to-end semantic search pipeline (cache -> embed query -> score -> top-N).
*
* @return array{query:string,results:list<array{score:float,event:array{id:int,title:string,description:string}}>}
* @throws RuntimeException If events are not set or embeddings output is unexpected.
*/
public function run(): array
{
if (count($this->events) === 0) {
throw new RuntimeException('Events list is empty. Call setEvents() before run().');
}
if ($this->embedder === null) {
$this->embedder = pipeline('embeddings', $this->model);
}
$this->loadEmbeddingsFromCacheIfCompatible();
$this->ensureAllEventEmbeddings();
$query = $this->query ?? $this->defaultQuery;
$queryVec = $this->embedText($query);
$results = $this->search($queryVec);
return [
'query' => $query,
'results' => $results,
];
}
/**
* Compute an embedding vector for a single text.
*
* @param string $text
* @return list<float|int>
* @throws RuntimeException
*/
private function embedText(string $text): array
{
$emb = ($this->embedder)($text, normalize: true, pooling: 'mean');
if (!is_array($emb) || !isset($emb[0]) || !is_array($emb[0])) {
throw new RuntimeException('Unexpected embeddings output format');
}
return $emb[0];
}
/**
* Cosine similarity between two vectors.
*
* @param list<float|int> $a
* @param list<float|int> $b
* @return float
*/
private function cosineSimilarity(array $a, array $b): float
{
$n = min(count($a), count($b));
$dot = 0.0;
$normA = 0.0;
$normB = 0.0;
for ($i = 0; $i < $n; $i++) {
$x = (float) $a[$i];
$y = (float) $b[$i];
$dot += $x * $y;
$normA += $x * $x;
$normB += $y * $y;
}
if ($normA <= 0.0 || $normB <= 0.0) {
return 0.0;
}
return $dot / (sqrt($normA) * sqrt($normB));
}
/**
* Load a JSON file and decode to array.
*
* @param string $path
* @return array|null
*/
private function loadJsonFile(string $path): ?array
{
if (!is_file($path)) {
return null;
}
$raw = file_get_contents($path);
if ($raw === false) {
return null;
}
$data = json_decode($raw, true);
return is_array($data) ? $data : null;
}
/**
* Encode and save data to JSON file.
*
* @param string $path
* @param array $data
* @throws RuntimeException
*/
private function saveJsonFile(string $path, array $data): void
{
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
if ($json === false) {
throw new RuntimeException('Failed to encode JSON');
}
$ok = file_put_contents($path, $json);
if ($ok === false) {
throw new RuntimeException('Failed to write cache file: ' . $path);
}
}
/**
* Load cached event embeddings only if they were produced by the current model.
*
* @return void
*/
private function loadEmbeddingsFromCacheIfCompatible(): void
{
$cached = $this->loadJsonFile($this->cachePath);
if (!is_array($cached) || !isset($cached['model'], $cached['events']) || !is_array($cached['events'])) {
return;
}
if ($cached['model'] !== $this->model) {
return;
}
foreach ($cached['events'] as $row) {
if (isset($row['id'], $row['embedding']) && is_array($row['embedding'])) {
$this->eventEmbeddingsById[(int) $row['id']] = $row['embedding'];
}
}
}
/**
* Ensure embeddings exist for all events and persist them to cache.
*
* @return void
* @throws RuntimeException
*/
private function ensureAllEventEmbeddings(): void
{
$missing = [];
foreach ($this->events as $event) {
$id = (int) $event['id'];
if (!isset($this->eventEmbeddingsById[$id])) {
$missing[] = $event;
}
}
if (count($missing) === 0) {
return;
}
foreach ($missing as $event) {
$id = (int) $event['id'];
$text = (string) $event['description'];
$this->eventEmbeddingsById[$id] = $this->embedText($text);
}
$toCache = [
'model' => $this->model,
'events' => array_values(array_map(
fn(array $event): array => [
'id' => (int) $event['id'],
'embedding' => $this->eventEmbeddingsById[(int) $event['id']],
],
$this->events
)),
];
$this->saveJsonFile($this->cachePath, $toCache);
}
/**
* Score all events against the query embedding and return the top-N results.
*
* @param list<float|int> $queryVec
* @return list<array{score:float,event:array{id:int,title:string,description:string}}>
*/
private function search(array $queryVec): array
{
$scored = [];
foreach ($this->events as $event) {
$id = (int) $event['id'];
$score = $this->cosineSimilarity($queryVec, $this->eventEmbeddingsById[$id]);
$scored[] = [
'score' => $score,
'event' => $event,
];
}
usort($scored, static fn(array $a, array $b): int => $b['score'] <=> $a['score']);
return array_slice($scored, 0, $this->topN);
}
/**
* Render results as plain text.
*
* @param string $query
* @param list<array{score:float,event:array{id:int,title:string,description:string}}> $results
* @return void
*/
public function render(string $query, array $results): void
{
echo "Query: {$query}nn";
foreach ($results as $row) {
$event = $row['event'];
$score = (float) $row['score'];
echo "[" . number_format($score, 4) . "] #{$event['id']} {$event['title']}n";
echo " {$event['description']}nn";
}
}
}
Подготовим данные
Предположим, что это наши данные, собранные из разных источников. Для простоты поместим их в массив.
Массив $events
$events = [
[
'id' => 1,
'title' => 'Ограничения против технологических корпораций',
'description' => 'Введены новые экономические меры в отношении крупных технологических компаний.',
],
[
'id' => 2,
'title' => 'Развитие космических программ',
'description' => 'Несколько стран региона увеличили финансирование национальных спутниковых проектов.',
],
[
'id' => 3,
'title' => 'Эскалация политического конфликта',
'description' => 'Обострение конфликта на политической почве в нескольких провинциях.',
],
[
'id' => 4,
'title' => 'Ограничения против ИТ-сектора',
'description' => 'Правительство объявило о новых ограничениях для компаний, работающих в сфере информационных технологий.',
],
[
'id' => 5,
'title' => 'Рост инфляции и пересмотр ключевой ставки',
'description' => 'Центральный банк повысил ключевую ставку на фоне ускорения инфляции и роста цен на импортные товары.',
],
[
'id' => 6,
'title' => 'Запуск программы поддержки малого бизнеса',
'description' => 'Власти объявили о льготных кредитах и налоговых послаблениях для малого и среднего бизнеса в регионах.',
],
[
'id' => 8,
'title' => 'Утечка данных в сфере онлайн-ритейла',
'description' => 'Интернет-магазин расследует утечку персональных данных клиентов после компрометации учётных записей сотрудников.',
],
[
'id' => 9,
'title' => 'Прорыв в медицине: новый метод диагностики',
'description' => 'Исследователи представили метод ранней диагностики заболеваний по биомаркерам, сокращающий время анализа.',
],
[
'id' => 10,
'title' => 'Сезонный рост заболеваемости',
'description' => 'В нескольких городах отмечен рост заболеваемости респираторными инфекциями, клиники усилили приём пациентов.',
],
[
'id' => 12,
'title' => 'Засуха и риски для сельского хозяйства',
'description' => 'Из-за продолжительной засухи фермеры прогнозируют снижение урожайности, обсуждаются меры поддержки аграриев.',
],
[
'id' => 13,
'title' => 'Финал крупного спортивного турнира',
'description' => 'В решающем матче сезона команда одержала победу в дополнительное время, установив новый рекорд по посещаемости.',
],
[
'id' => 14,
'title' => 'Трансфер игрока и усиление состава',
'description' => 'Клуб подписал контракт с новым нападающим, рассчитывая усилить атакующую линию перед серией дерби.',
],
[
'id' => 15,
'title' => 'Новые правила для маркетплейсов',
'description' => 'Регулятор предложил требования к маркировке товаров и прозрачности комиссий на торговых онлайн-платформах.',
],
[
'id' => 17,
'title' => 'Сбои в поставках полупроводников',
'description' => 'Производители электроники предупредили о задержках поставок чипов из-за ограничений экспорта и перегрузки заводов.',
],
[
'id' => 18,
'title' => 'Открытие фестиваля современного искусства',
'description' => 'В столице стартовал фестиваль современного искусства с выставками, перформансами и лекциями художников.',
],
[
'id' => 19,
'title' => 'Крупная сделка на рынке недвижимости',
'description' => 'Инвестфонд приобрёл портфель коммерческой недвижимости, планируя реконструкцию и повышение энергоэффективности.',
],
[
'id' => 20,
'title' => 'Исследование океана и новые данные',
'description' => 'Научная экспедиция собрала данные о течениях и температуре воды, уточнив прогнозы по изменению климата.',
],
];
Использование примера
Здесь всё просто – запускаем наш код и ждём результата.
require_once __DIR__ . '/../vendor/autoload.php';
use function CodewithkyrianTransformersPipelinespipeline;
final class SemanticEventSearch {...}
$events = [...];
$query = 'санкции против IT-компаний';
$search = new SemanticEventSearch(topN: 3);
$search->setModel('Xenova/paraphrase-multilingual-MiniLM-L12-v2');
$search->setEvents($events);
$search->setQuery($query);
$out = $search->run();
$search->render($out['query'], $out['results']);
Логика работы (по шагам)
Если вам не хочется разбираться в деталях реализации SemanticEventSearch, ниже – упрощённое пошаговое описание. Более подробный разбор кода выходит за рамки этой статьи.
Логика работы кода
-
Снаружи задаём:
-
список событий (setEvents)
-
модель (setModel)
-
запрос (setQuery)
-
topN через конструктор
-
-
Дальше — обычная логика в run():
-
Поднимаем embedder (pipeline(’embeddings’, model)) если ещё не поднят.
-
.transformers-cache:
-
при первом использовании модель и файлы токенизации/весов скачиваются и кладутся в .transformers-cache
-
дальше они берутся оттуда, чтобы не качать заново и работать быстрее
-
-
embeddings.events.json:
-
это наш локальный кэш эмбеддингов событий
-
пытаемся его прочитать и использовать только если model в кэше совпадает с текущей моделью
-
-
Если для каких-то событий эмбеддингов нет:
-
считаем эмбеддинги для description
-
сохраняем обратно в embeddings.events.json
-
-
Эмбеддим запрос (эмбеддинги нормализуются, чтобы косинусная близость была стабильной и сравнимой)
-
Считаем близость запроса к каждому событию (cosine similarity)
-
Сортируем, берём top‑N, возвращаем результаты
-
Рендер (вывод) делается снаружи через render(query, results)
-
Результат
На выходе мы получаем не совпадение слов, а совпадение по смыслу. Первый результат – наиболее подходящий по схожести с нашим запросом про “санкции против IT-компаний”. И всё это – без сложной математики и без танцев с бубнами.
Query: санкции против IT-компаний
[0.4288] #4 Ограничения против ИТ-сектора
Правительство объявило о новых ограничениях для компаний, работающих в сфере информационных технологий.
[0.3356] #15 Новые правила для маркетплейсов
Регулятор предложил требования к маркировке товаров и прозрачности комиссий на торговых онлайн-платформах.
[0.2598] #8 Утечка данных в сфере онлайн-ритейла
Интернет-магазин расследует утечку персональных данных клиентов после компрометации учётных записей сотрудников.
Где это можно применять в проде
В целом это уже похоже на продуктовый подход, а не на эксперимент. К тому же вы можете легко заметить, что такой подход:
-
слабо привязан к конкретной формулировке запроса
-
хорошо работает на коротких описаниях
-
легко комбинируется с обычными фильтрами (дата, регион, тип события)
То есть это не “AI ради AI”, а вполне конкретная прикладная логика. Та самая, которую можно объяснить, отладить и поддерживать.
Ограничения и подводные камни
Важно не обманываться: это не серебряная пуля.
Модели весят немало. Производительность нужно учитывать. Некоторые задачи проще и надёжнее решаются без AI обычным SQL.
Но это уже нормальный инженерный разговор – про trade-off’ы, а не про магию или чёрный ящик.
Куда двигаться дальш��
Как для меня, то TransformersPHP – это хороший пример того, что AI можно использовать напрямую в PHP-проектах без смены стека и без Python.
В своей книге “AI для PHP-разработчиков” (открытой и бесплатной) я как раз и разбираю подобные кейсы: где это имеет смысл, как выбирать модели и как не превратить проект в набор экспериментальных фич.
Ссылка на книгу “AI для PHP-разработчиков”.
Кстати, все примеры можно скачать и запустить через готовую среду Docker.
Или же вы также можете запускать все примеры из книги напрямую.
Если тема откликается – буду рад обсуждению и фидбэку.
Особенно интересно, какие задачи вы уже решаете или хотели бы решать с помощью AI в PHP-проектах.
Автор: samako
- Запись добавлена: 07.02.2026 в 22:43
- Оставлено в


