Бесплатная проверка комментариев с помощью API LLM


LLM прекрасный помощник для многих дел. Ниже расскажу, как я присобачил его (полностью бесплатно) для предварительного модерирования комментариев. Итак, нам необходимо, чтобы система предварительно проверяла комментарии и сразу давала первую оценку и причину – если содержимое некорректно. Все в формате json.



Регистрируетесь здесь openrouter.ai и создаете ключ. Дальше выбираете бесплатную модель



Проект работает на шустром и простом фреймворке Flight. Контроллер

namespace App\Controllers;
use Flight;
class ModerationController extends Controller
{
    public function moderateText()
    {
        $data = Flight::request()->data;
        $text = trim($data->text ?? '');
        if ($text === '') {
            Flight::json(['error' => 'Поле "text" обязательно'], 400);
            return;
        }
        // Ограничение длины (опционально)
        if (mb_strlen($text) > 1000) {
            Flight::json(['error' => 'Текст слишком длинный (макс. 1000 символов)'], 400);
            return;
        }
        $apiKey = 'ключ';
        if (!$apiKey) {
            error_log('OpenRouter API key not configured');
            Flight::json(['error' => 'Сервис временно недоступен'], 500);
            return;
        }
        $prompt = <<<PROMPT
Проанализируй следующий текст на наличие неуместного содержания: мат, оскорбления, спам, реклама, призывы к насилию, дискриминация или другая токсичность.
Ответь строго в формате JSON без пояснений:
Если текст уместный — верни: {"is_inappropriate": false}
Если неуместный — верни: {"is_inappropriate": true, "reason": "причина"}
Текст для анализа:
"""
{$text}
"""
PROMPT;
        $payload = [
            'model' => 'arcee-ai/trinity-large-preview:free',
            'messages' => [
                ['role' => 'user', 'content' => $prompt]
            ],
            'temperature' => 0.0,
            'max_tokens' => 100
        ];
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => 'https://openrouter.ai/api/v1/chat/completions',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $apiKey,
                'HTTP-Referer: ' . Flight::get('flight.base_url'), // требуется OpenRouter
                'X-Title: ' . Flight::get('app.name') // опционально
            ],
            CURLOPT_TIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        if ($error) {
            error_log("OpenRouter cURL error: $error");
            Flight::json(['error' => 'Ошибка при вызове модерации'], 500);
            return;
        }
        if ($httpCode !== 200) {
            error_log("OpenRouter HTTP $httpCode: $response");
            Flight::json(['error' => 'Сервис модерации недоступен'], 500);
            return;
        }
        $decoded = json_decode($response, true);
        $rawContent = $decoded['choices'][0]['message']['content'] ?? '';
        // Очистка: иногда модель оборачивает JSON в блок кода
        $cleanJson = trim(preg_replace('/^```json\s*|\s*```$/i', '', $rawContent));
        $result = json_decode($cleanJson, true);
        if (json_last_error() !== JSON_ERROR_NONE || !isset($result['is_inappropriate'])) {
            // Если модель не вернула валидный JSON — считаем безопасным или логируем
            error_log("Invalid JSON from OpenRouter: $rawContent");
            Flight::json([
                'is_inappropriate' => false,
                'warning' => 'Не удалось распознать ответ модели'
            ]);
            return;
        }
        Flight::json($result);
    }
        public function showForm()
    {
        // Просто рендерим view
        require_once 'views/moderation/form.php';
    }
}
Шаблон страницы

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Модерация текста</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 700px;
            margin: 40px auto;
            padding: 0 20px;
            background-color: #f9f9f9;
            color: #333;
        }
        h1 {
            text-align: center;
            margin-bottom: 24px;
            color: #2c3e50;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
        }
        textarea {
            width: 100%;
            height: 150px;
            padding: 12px;
            border: 1px solid #ccc;
            border-radius: 6px;
            font-size: 15px;
            resize: vertical;
            box-sizing: border-box;
        }
        button {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 12px 24px;
            font-size: 16px;
            border-radius: 6px;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        button:hover {
            background-color: #2980b9;
        }
        button:disabled {
            background-color: #bdc3c7;
            cursor: not-allowed;
        }
        #result {
            margin-top: 24px;
            padding: 16px;
            border-radius: 6px;
            display: none;
        }
        .result-safe {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .result-risk {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .loading {
            display: inline-block;
            margin-left: 10px;
            font-style: italic;
            color: #666;
        }
    </style>
</head>
<body>
    <h1>Проверка текста на неуместное содержание</h1>
    <div class="form-group">
        <label for="textInput">Введите текст для модерации:</label>
        <textarea id="textInput" placeholder="Например: Привет! Как дела?"></textarea>
    </div>
    <button id="submitBtn">Проверить</button>
    <span id="loading" class="loading" style="display:none;">Отправка...</span>
    <div id="result"></div>
    <script>
        document.getElementById('submitBtn').addEventListener('click', async () => {
            const text = document.getElementById('textInput').value.trim();
            const resultDiv = document.getElementById('result');
            const loading = document.getElementById('loading');
            const btn = document.getElementById('submitBtn');
            if (!text) {
                alert('Пожалуйста, введите текст.');
                return;
            }
            // Сброс результата
            resultDiv.style.display = 'none';
            resultDiv.className = '';
            btn.disabled = true;
            loading.style.display = 'inline';
            try {
                const response = await fetch('/api/moderate', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ text: text })
                });
                const data = await response.json();
                let message = '';
                if (response.ok) {
                    if (data.is_inappropriate) {
                        resultDiv.className = 'result-risk';
                        message = `<strong>⚠️ Неуместный контент</strong><br>Причина: ${data.reason || 'не указана'}`;
                    } else {
                        resultDiv.className = 'result-safe';
                        message = '<strong>✅ Текст безопасен</strong>';
                    }
                } else {
                    resultDiv.className = 'result-risk';
                    message = `<strong>❌ Ошибка</strong><br>${data.error || 'Неизвестная ошибка'}`;
                }
                resultDiv.innerHTML = message;
                resultDiv.style.display = 'block';
            } catch (err) {
                resultDiv.className = 'result-risk';
                resultDiv.innerHTML = '<strong>❌ Ошибка подключения</strong><br>Проверьте интернет или попробуйте позже.';
                resultDiv.style.display = 'block';
            } finally {
                btn.disabled = false;
                loading.style.display = 'none';
            }
        });
    </script>
</body>
</html>
Не забудем роут

// Показ формы модерации
Flight::route('GET /moderate', ['App\Controllers\ModerationController', 'showForm']);
// API: отправка текста на модерацию
Flight::route('POST /api/moderate', ['App\Controllers\ModerationController', 'moderateText']);
Итог

Вот так просто и бесплатно можно с помощью LLM проверять комментарии на корретность.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, php, llm



Мегафон: как выйти из минуса
Отслеживание даты в COREL DRAW, или GlobalMacroStorage и Metadata
Меняем вкладки на карточке товара Opencart