Оригинал: AI Without the Hype: Practical Machine Learning in Symfony

Перевод для канала Мы ж программист

Почему разработчикам PHP стоит интересоваться ИИ

PHP лежит в основе 77% веб-сайтов, использующих известный серверный язык. На Symfony работают некоторые из крупнейших платформ в Интернете. Но способен ли PHP справиться с ИИ и машинным обучением? Да. Вы можете создавать интеллектуальные приложения, не переходя на Python.

В этой статье я покажу вам, как интегрировать машинное обучение в приложения на Symfony. Вы научитесь классифицировать текст, прогнозировать результаты и обрабатывать изображения. И всё это с помощью PHP.

Настройка окружения

Начните с Symfony 7. Вам понадобится PHP 8.2 или выше. Установите пакеты:

Bash
composer require symfony/http-client
composer require php-ai/php-ml
composer require rubix/ml

PHP-ML предоставляет вам встроенные алгоритмы машинного обучения. Rubix ML предлагает более полный набор инструментов. Оба хорошо работают с Symfony.

Создайте новый файл конфигурации сервиса:

YAML
# config/services.yaml
services:
    App\Service\MachineLearning\:
        resource: '../src/Service/MachineLearning/'
        tags: ['app.ml_service']

Эта конфигурация обеспечивает автоматическую интеграцию ваших сервисов машинного обучения. Вы можете подключить их в любом месте вашего приложения.

Подключение к внешним API ИИ

OpenAI, Claude и Hugging Face предлагают мощные API. Ваше приложение на Symfony может обращаться к ним. Вот сервис, который оборачивает вызовы API:

PHP
<?php

namespace App\Service\MachineLearning;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class AIApiClient
{
    private const OPENAI_API = 'https://api.openai.com/v1/chat/completions';
    
    public function __construct(
        private HttpClientInterface $httpClient,
        private string $apiKey
    ) {}
    
    public function classifyText(string $text, array $categories): array
    {
        $prompt = $this->buildClassificationPrompt($text, $categories);
        
        $response = $this->httpClient->request('POST', self::OPENAI_API, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'model' => 'gpt-4',
                'messages' => [
                    ['role' => 'system', 'content' => 'You are a text classifier.'],
                    ['role' => 'user', 'content' => $prompt]
                ],
                'temperature' => 0.3,
            ],
        ]);
        
        $data = $response->toArray();
        return $this->parseClassification($data);
    }
    
    private function buildClassificationPrompt(string $text, array $categories): string
    {
        $categoriesList = implode(', ', $categories);
        return "Classify this text into one of these categories: {$categoriesList}.\n\nText: {$text}\n\nRespond with only the category name.";
    }
    
    private function parseClassification(array $response): array
    {
        $category = trim($response['choices'][0]['message']['content']);
        
        return [
            'category' => $category,
            'tokens_used' => $response['usage']['total_tokens'],
        ];
    }
}

Сохраните API-ключ в .env:

Plaintext
OPENAI_API_KEY=your_key_here

Прокиньте его в ваши сервисы:

YAML
# config/services.yaml
services:
    App\Service\MachineLearning\AIApiClient:
        arguments:
            $apiKey: '%env(OPENAI_API_KEY)%'

Нативное машинное обучение с помощью PHP-ML

Не всегда требуются внешние API. PHP-ML позволяет обучать модели локально. Вот пример анализатора тональности:

PHP
<?php

namespace App\Service\MachineLearning;
use Phpml\Classification\NaiveBayes;
use Phpml\FeatureExtraction\TokenCountVectorizer;
use Phpml\Tokenization\WordTokenizer;
class SentimentAnalyzer
{
    private NaiveBayes $classifier;
    private TokenCountVectorizer $vectorizer;
    
    public function __construct()
    {
        $this->vectorizer = new TokenCountVectorizer(new WordTokenizer());
        $this->classifier = new NaiveBayes();
    }
    
    public function train(array $samples, array $labels): void
    {
        $this->vectorizer->fit($samples);
        $this->vectorizer->transform($samples);
        
        $this->classifier->train($samples, $labels);
    }
    
    public function predict(string $text): string
    {
        $sample = [$text];
        $this->vectorizer->transform($sample);
        
        return $this->classifier->predict($sample[0]);
    }
    
    public function saveModel(string $path): void
    {
        file_put_contents(
            $path,
            serialize([
                'classifier' => $this->classifier,
                'vectorizer' => $this->vectorizer,
            ])
        );
    }
    
    public function loadModel(string $path): void
    {
        $data = unserialize(file_get_contents($path));
        $this->classifier = $data['classifier'];
        $this->vectorizer = $data['vectorizer'];
    }
}

Обучите его консольной командой:

PHP
<?php

namespace App\Command;
use App\Service\MachineLearning\SentimentAnalyzer;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:train-sentiment')]
class TrainSentimentCommand extends Command
{
    public function __construct(
        private SentimentAnalyzer $analyzer,
        private string $projectDir
    ) {
        parent::__construct();
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $samples = [
            'I love this product',
            'This is amazing',
            'Best purchase ever',
            'I hate this',
            'Terrible experience',
            'Waste of money',
        ];
        
        $labels = [
            'positive',
            'positive',
            'positive',
            'negative',
            'negative',
            'negative',
        ];
        
        $output->writeln('Training model...');
        $this->analyzer->train($samples, $labels);
        
        $modelPath = $this->projectDir . '/var/models/sentiment.model';
        $this->analyzer->saveModel($modelPath);
        
        $output->writeln('Model saved to: ' . $modelPath);
        
        return Command::SUCCESS;
    }
}

Запустите:

Bash
php bin/console app:train-sentiment

Используйте в вашем контроллере:

PHP
<?php

namespace App\Controller;
use App\Service\MachineLearning\SentimentAnalyzer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class SentimentController extends AbstractController
{
    public function __construct(
        private SentimentAnalyzer $analyzer,
        private string $projectDir
    ) {
        $modelPath = $this->projectDir . '/var/models/sentiment.model';
        $this->analyzer->loadModel($modelPath);
    }
    
    #[Route('/api/sentiment', methods: ['POST'])]
    public function analyze(Request $request): JsonResponse
    {
        $text = $request->request->get('text');
        
        if (!$text) {
            return $this->json(['error' => 'Text is required'], 400);
        }
        
        $sentiment = $this->analyzer->predict($text);
        
        return $this->json([
            'text' => $text,
            'sentiment' => $sentiment,
        ]);
    }
}

Создание системы рекомендаций товаров

Сайтам электронной коммерции необходимы интеллектуальные рекомендации. Представляем систему совместной фильтрации на базе Rubix ML:

PHP
<?php

namespace App\Service\MachineLearning;
use Rubix\ML\Datasets\Labeled;
use Rubix\ML\Regressors\KNearestNeighbors;
use Rubix\ML\Kernels\Distance\Euclidean;
class RecommendationEngine
{
    private KNearestNeighbors $estimator;
    private array $productIds;
    
    public function __construct()
    {
        $this->estimator = new KNearestNeighbors(5, new Euclidean());
        $this->productIds = [];
    }
    
    public function train(array $userPreferences): void
    {
        $samples = [];
        $labels = [];
        
        foreach ($userPreferences as $userId => $prefs) {
            $samples[] = $prefs['features'];
            $labels[] = $prefs['rating'];
            $this->productIds[] = $prefs['product_id'];
        }
        
        $dataset = new Labeled($samples, $labels);
        $this->estimator->train($dataset);
    }
    
    public function recommend(array $userFeatures, int $limit = 5): array
    {
        $predictions = $this->estimator->predict(
            new \Rubix\ML\Datasets\Unlabeled([$userFeatures])
        );
        
        $scored = array_map(function($id, $score) {
            return ['product_id' => $id, 'score' => $score];
        }, $this->productIds, $predictions);
        
        usort($scored, fn($a, $b) => $b['score'] <=> $a['score']);
        
        return array_slice($scored, 0, $limit);
    }
}

Создайте сервис, чтобы собрать пользовательские данные:

PHP
<?php

namespace App\Service;
use App\Entity\User;
use App\Entity\Purchase;
use Doctrine\ORM\EntityManagerInterface;
class UserPreferenceCollector
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {}
    
    public function collectForUser(User $user): array
    {
        $purchases = $this->entityManager
            ->getRepository(Purchase::class)
            ->findBy(['user' => $user], ['createdAt' => 'DESC'], 50);
        
        $categoryFrequency = [];
        $priceRange = [];
        
        foreach ($purchases as $purchase) {
            $product = $purchase->getProduct();
            $category = $product->getCategory();
            
            $categoryFrequency[$category] = 
                ($categoryFrequency[$category] ?? 0) + 1;
            
            $priceRange[] = $product->getPrice();
        }
        
        return [
            'category_preference' => $this->normalizeCategories($categoryFrequency),
            'avg_price' => array_sum($priceRange) / count($priceRange),
            'purchase_frequency' => count($purchases),
        ];
    }
    
    private function normalizeCategories(array $frequencies): array
    {
        $total = array_sum($frequencies);
        
        return array_map(
            fn($count) => $count / $total,
            $frequencies
        );
    }
}

Классификация изображений с помощью Symfony

Распознавание изображений требует большего объема ресурсов. Можно использовать внешние сервисы или TensorFlow через PHP-биндинги. Вот практический подход с использованием Hugging Face:

PHP
<?php

namespace App\Service\MachineLearning;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class ImageClassifier
{
    private const HF_API = 'https://api-inference.huggingface.co/models/';
    
    public function __construct(
        private HttpClientInterface $httpClient,
        private string $hfToken
    ) {}
    
    public function classify(string $imagePath, string $model = 'google/vit-base-patch16-224'): array
    {
        $imageData = file_get_contents($imagePath);
        
        $response = $this->httpClient->request('POST', self::HF_API . $model, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->hfToken,
            ],
            'body' => $imageData,
        ]);
        
        return $response->toArray();
    }
    
    public function classifyUploadedFile(\Symfony\Component\HttpFoundation\File\UploadedFile $file): array
    {
        $tempPath = $file->getRealPath();
        return $this->classify($tempPath);
    }
}

Используйте в контроллере:

PHP
<?php

namespace App\Controller;
use App\Service\MachineLearning\ImageClassifier;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class ImageAnalysisController extends AbstractController
{
    #[Route('/api/classify-image', methods: ['POST'])]
    public function classify(Request $request, ImageClassifier $classifier): JsonResponse
    {
        /** @var UploadedFile $file */
        $file = $request->files->get('image');
        
        if (!$file) {
            return $this->json(['error' => 'No image uploaded'], 400);
        }
        
        $allowedMimes = ['image/jpeg', 'image/png', 'image/webp'];
        if (!in_array($file->getMimeType(), $allowedMimes)) {
            return $this->json(['error' => 'Invalid image format'], 400);
        }
        
        $results = $classifier->classifyUploadedFile($file);
        
        return $this->json([
            'filename' => $file->getClientOriginalName(),
            'classifications' => $results,
        ]);
    }
}

Прогнозирование временных рядов

Прогнозируйте объемы продаж, посещаемость или потребности в запасах. Вот модель линейной регрессии:

PHP
<?php

namespace App\Service\MachineLearning;
use Phpml\Regression\LeastSquares;
class TimeSeriesPredictor
{
    private LeastSquares $regression;
    
    public function __construct()
    {
        $this->regression = new LeastSquares();
    }
    
    public function train(array $historicalData): void
    {
        $samples = [];
        $targets = [];
        
        foreach ($historicalData as $index => $value) {
            $samples[] = [$index];
            $targets[] = $value;
        }
        
        $this->regression->train($samples, $targets);
    }
    
    public function predict(int $daysAhead): float
    {
        $lastIndex = count($this->regression->getSamples()) - 1;
        $futureIndex = $lastIndex + $daysAhead;
        
        return $this->regression->predict([$futureIndex]);
    }
    
    public function predictRange(int $start, int $end): array
    {
        $predictions = [];
        
        for ($i = $start; $i <= $end; $i++) {
            $predictions[$i] = $this->predict($i);
        }
        
        return $predictions;
    }
}

Создайте команду для предсказания будущих продаж:

PHP
<?php

namespace App\Command;
use App\Service\MachineLearning\TimeSeriesPredictor;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:predict-sales')]
class PredictSalesCommand extends Command
{
    public function __construct(
        private TimeSeriesPredictor $predictor,
        private EntityManagerInterface $entityManager
    ) {
        parent::__construct();
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $sql = "SELECT DATE(created_at) as date, SUM(amount) as total 
                FROM orders 
                WHERE created_at >= DATE_SUB(NOW(), INTERVAL 90 DAY)
                GROUP BY DATE(created_at)
                ORDER BY date";
        
        $results = $this->entityManager
            ->getConnection()
            ->executeQuery($sql)
            ->fetchAllAssociative();
        
        $salesData = array_column($results, 'total');
        
        $this->predictor->train($salesData);
        
        $predictions = $this->predictor->predictRange(1, 7);
        
        $output->writeln('Sales predictions for next 7 days:');
        foreach ($predictions as $day => $amount) {
            $output->writeln(sprintf('Day %d: $%.2f', $day, $amount));
        }
        
        return Command::SUCCESS;
    }
}

Обработка естественного языка

Извлечение смысла из текста. Вот пример экстрактора сущностей:

PHP
<?php

namespace App\Service\MachineLearning;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class EntityExtractor
{
    public function __construct(
        private HttpClientInterface $httpClient,
        private string $apiKey
    ) {}
    
    public function extract(string $text): array
    {
        $response = $this->httpClient->request('POST', 
            'https://api.openai.com/v1/chat/completions', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'model' => 'gpt-4',
                'messages' => [
                    [
                        'role' => 'system',
                        'content' => 'Extract people, organizations, and locations. Return JSON only.'
                    ],
                    [
                        'role' => 'user',
                        'content' => $text
                    ]
                ],
                'temperature' => 0.0,
            ],
        ]);
        
        $data = $response->toArray();
        $content = $data['choices'][0]['message']['content'];
        
        return json_decode($content, true);
    }
}

Используйте для обогащения своего контента:

PHP
<?php

namespace App\Service;
use App\Entity\Article;
use App\Service\MachineLearning\EntityExtractor;
use Doctrine\ORM\EntityManagerInterface;
class ArticleEnricher
{
    public function __construct(
        private EntityExtractor $extractor,
        private EntityManagerInterface $entityManager
    ) {}
    
    public function enrichArticle(Article $article): void
    {
        $entities = $this->extractor->extract($article->getContent());
        
        $article->setExtractedPeople($entities['people'] ?? []);
        $article->setExtractedOrganizations($entities['organizations'] ?? []);
        $article->setExtractedLocations($entities['locations'] ?? []);
        
        $this->entityManager->flush();
    }
}

Кэширование и производительность

Операции искусственного интеллекта требуют значительных ресурсов. Активно используйте кэширование:

PHP
<?php

namespace App\Service\MachineLearning;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class CachedAIService
{
    public function __construct(
        private AIApiClient $apiClient,
        private CacheInterface $cache
    ) {}
    
    public function classifyText(string $text, array $categories): array
    {
        $cacheKey = 'classification_' . md5($text . implode('', $categories));
        
        return $this->cache->get($cacheKey, function (ItemInterface $item) use ($text, $categories) {
            $item->expiresAfter(86400); // 24 hours
            
            return $this->apiClient->classifyText($text, $categories);
        });
    }
}

Добавьте ограничение скорости:

PHP
<?php

namespace App\Service\MachineLearning;
use Symfony\Component\RateLimiter\RateLimiterFactory;
class RateLimitedAIService
{
    public function __construct(
        private AIApiClient $apiClient,
        private RateLimiterFactory $aiApiLimiter
    ) {}
    
    public function classifyText(string $text, array $categories, string $userId): array
    {
        $limiter = $this->aiApiLimiter->create($userId);
        
        if (!$limiter->consume(1)->isAccepted()) {
            throw new \RuntimeException('Rate limit exceeded. Try again later.');
        }
        
        return $this->apiClient->classifyText($text, $categories);
    }
}

Настройте ограничитель:

YAML
# config/packages/rate_limiter.yaml
framework:
    rate_limiter:
        ai_api:
            policy: 'sliding_window'
            limit: 100
            interval: '1 hour'

Тестирование ML-сервисов

Тестируйте ваш ML-код как любой другой сервис:

PHP
<?php

namespace App\Tests\Service\MachineLearning;
use App\Service\MachineLearning\SentimentAnalyzer;
use PHPUnit\Framework\TestCase;
class SentimentAnalyzerTest extends TestCase
{
    private SentimentAnalyzer $analyzer;
    
    protected function setUp(): void
    {
        $this->analyzer = new SentimentAnalyzer();
        
        $samples = [
            'I love this',
            'This is great',
            'I hate this',
            'This is terrible',
        ];
        
        $labels = ['positive', 'positive', 'negative', 'negative'];
        
        $this->analyzer->train($samples, $labels);
    }
    
    public function testPositiveSentiment(): void
    {
        $result = $this->analyzer->predict('I really enjoy this');
        $this->assertEquals('positive', $result);
    }
    
    public function testNegativeSentiment(): void
    {
        $result = $this->analyzer->predict('I dislike this product');
        $this->assertEquals('negative', $result);
    }
}

Замените моками внешние вызовы API:

PHP
<?php

namespace App\Tests\Service\MachineLearning;
use App\Service\MachineLearning\AIApiClient;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
class AIApiClientTest extends TestCase
{
    public function testClassifyText(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'choices' => [
                [
                    'message' => [
                        'content' => 'technology'
                    ]
                ]
            ],
            'usage' => [
                'total_tokens' => 50
            ]
        ]));
        
        $httpClient = new MockHttpClient($mockResponse);
        $client = new AIApiClient($httpClient, 'fake-key');
        
        $result = $client->classifyText('AI news', ['technology', 'sports']);
        
        $this->assertEquals('technology', $result['category']);
        $this->assertEquals(50, $result['tokens_used']);
    }
}

Соглашения по безопасности

Защитите ваши эндпоинты:

PHP
<?php

namespace App\Security\Voter;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AIFeatureVoter extends Voter
{
    public const USE_AI = 'ai.use';
    
    protected function supports(string $attribute, mixed $subject): bool
    {
        return $attribute === self::USE_AI;
    }
    
    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token
    ): bool {
        $user = $token->getUser();
        
        if (!$user instanceof User) {
            return false;
        }
        
        return $user->hasSubscription() && $user->getAiQuota() > 0;
    }
}

Очищайте данные, вводимые пользователем:

PHP
<?php

namespace App\Service;
class InputSanitizer
{
    public function sanitizeForAI(string $input): string
    {
        $input = strip_tags($input);
        $input = preg_replace('/[^\w\s\.,!?-]/u', '', $input);
        
        return mb_substr($input, 0, 5000);
    }
}

Мониторинг и логирование

Контролируйте использование ИИ:

PHP
<?php

namespace App\Service\MachineLearning;
use Psr\Log\LoggerInterface;
class MonitoredAIService
{
    public function __construct(
        private AIApiClient $apiClient,
        private LoggerInterface $logger
    ) {}
    
    public function classifyText(string $text, array $categories): array
    {
        $startTime = microtime(true);
        
        try {
            $result = $this->apiClient->classifyText($text, $categories);
            
            $duration = microtime(true) - $startTime;
            
            $this->logger->info('AI classification completed', [
                'duration' => $duration,
                'tokens' => $result['tokens_used'],
                'category' => $result['category'],
            ]);
            
            return $result;
        } catch (\Exception $e) {
            $this->logger->error('AI classification failed', [
                'error' => $e->getMessage(),
                'text_length' => strlen($text),
            ]);
            
            throw $e;
        }
    }
}

Реальный пример: интеллектуальная служба поддержки

Сложим всё воедино. Вот готовый классификатор запросов в службу поддержки:

PHP
<?php

namespace App\Service;
use App\Entity\SupportTicket;
use App\Service\MachineLearning\AIApiClient;
use App\Service\MachineLearning\SentimentAnalyzer;
use Doctrine\ORM\EntityManagerInterface;
class SmartTicketProcessor
{
    public function __construct(
        private AIApiClient $aiClient,
        private SentimentAnalyzer $sentimentAnalyzer,
        private EntityManagerInterface $entityManager
    ) {}
    
    public function process(SupportTicket $ticket): void
    {
        $categories = ['billing', 'technical', 'account', 'general'];
        $classification = $this->aiClient->classifyText(
            $ticket->getDescription(),
            $categories
        );
        
        $sentiment = $this->sentimentAnalyzer->predict($ticket->getDescription());
        
        $ticket->setCategory($classification['category']);
        $ticket->setSentiment($sentiment);
        $ticket->setPriority($this->calculatePriority($sentiment, $ticket));
        
        $this->entityManager->flush();
        
        $this->routeTicket($ticket);
    }
    
    private function calculatePriority(string $sentiment, SupportTicket $ticket): int
    {
        $priority = 3;
        
        if ($sentiment === 'negative') {
            $priority = 2;
        }
        
        if ($ticket->getCustomer()->isVip()) {
            $priority = min(1, $priority - 1);
        }
        
        return $priority;
    }
    
    private function routeTicket(SupportTicket $ticket): void
    {
        $routing = [
            'billing' => 'billing@company.com',
            'technical' => 'tech@company.com',
            'account' => 'accounts@company.com',
            'general' => 'support@company.com',
        ];
        
        $ticket->setAssignedTo($routing[$ticket->getCategory()]);
        $this->entityManager->flush();
    }
}

Создайте подписчика событий для автоматической обработки тикетов:

PHP
<?php

namespace App\EventSubscriber;
use App\Entity\SupportTicket;
use App\Service\SmartTicketProcessor;
use Doctrine\ORM\Events;
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\ORM\Event\PostPersistEventArgs;
class TicketSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private SmartTicketProcessor $processor
    ) {}
    
    public function getSubscribedEvents(): array
    {
        return [
            Events::postPersist,
        ];
    }
    
    public function postPersist(PostPersistEventArgs $args): void
    {
        $entity = $args->getObject();
        
        if (!$entity instanceof SupportTicket) {
            return;
        }
        
        $this->processor->process($entity);
    }
}

Лучшие практики

Обучайте модели в автономном режиме. Не проводите обучение во время обработки веб-запросов. Используйте консольные команды. Сохраняйте обученные модели на диск.

Кэшируйте всё. Вызовы API требуют затрат времени и средств. Кэшируйте данные как минимум на час. Используйте Redis или кэш Symfony.

Проверяйте размер входных данных. Ограничьте текст 5000 символами. Ограничьте размер изображений 5 МБ. Сразу отклоняйте неверные форматы.

Правильно обрабатывайте сбои. API могут давать сбои. Сеть может превысить время ожидания. Перехватывайте исключения. Возвращайте резервные ответы.

Контролируйте расходы. Отслеживайте использование API. Устанавливайте оповещения о превышении бюджета. Регистрируйте использование токенов.

Тестируйте на реальных данных. Ваши обучающие данные имеют значение. Используйте реальные сообщения клиентов. Обновляйте модели ежемесячно.

Следующие шаги

Начните с малого. Выберите одну функцию. Добавьте анализ тональности комментариев. Или классифицируйте заявки в службу поддержки.

Оценивайте результаты. Отслеживайте точность. Контролируйте время отклика. Собирайте отзывы пользователей.

Постепенно расширяйте масштабы. Начните с кэширования. Добавьте очереди для ресурсоемких задач. Перенесите обучение в фоновые процессы.

Продолжайте учиться. Машинное обучение быстро развивается. Следите за библиотеками машинного обучения для PHP. Пробуйте новые модели.