17 августа 2023

Генератор контента на WordPress с использованием OpenAI

7 минут
Генератор контента на WordPress

На сегодняшний день по всему миру набирает популярность искусственный интеллект разработанный компанией OpenAI который открывает множество возможностей для разработки различных чат ботов и генераторов уникального текста, в данной статье мы решили поделиться личным опытом создания плагина генерации контента с использованием OpenAI.

Структура

Основная идея работы с разрабатываемым сервисом заключалась в создании промежуточного сервиса в котором пользователи смогут зарегистрировать аккаунт и настраивать доступ для своего WordPress сайта.

WordPress плагин обращается к Middleware с запросом на генерацию контента, если у сайта есть доступ — то Middleware регистрирует запрос в кабинете пользователя и формирует запрос к OpenAI API, ответ от API возвращается в том же порядке и отображается для возможности вставить в выбранный редактор.

Middleware

Middleware был реализован на Laravel, при регистрации аккаунта пользователь получает следующие возможности:

  1. Отслеживать статистику запросов на генерацию контента 
  2. Видеть общее количество сгенерированного текста за все время или за выбранный период
  3. Пополнять баланс аккаунта (в том числе оформить подписку)
  4. Отслеживать историю пополнений баланса
  5. Скачать последнюю версию плагина
  6. Выписать доступ для доменов на которых будет работать плагин и получить токен доступа

Скрипт который подключается плагином на WordPress сайт и отвечает за большую часть работы генератора, реализован в виде CDN и подключается с помощью ключа доступа полученного в кабинете пользователя

Плагин

На WordPress сайте была произведена интеграция с большинством популярных редакторов — TinyMCE, Elementor, Gutenberg, YOOtheme Editor. Реализовано в виде иконки в редакторе при нажатии на которую появляется всплывающее окно с функционалом генерации контента

В данном окне есть возможность:

  1. Выбрать количество символов для генерации
  2. Выбрать количество предлагаемых сгенерированных вариантов
  3. Ввести тему на которую необходимо сгенерировать контент
  4. Выбрать шаблон генерации
  5. Редактировать сгенерированный контент
  6. Передать контент в редактор из которого было вызвано всплывающее окно
  7. Выбор языка генерируемого контента

Весь функционал этого всплывающего окна реализован при помощи подключенного скрипта через CDN, это позволяет централизованно выпускать обновления и правки.

OpenAI интеграция

Изначально для интеграции с OpenAI планировалось использовать OpenAI PHP for Laravel, но в процессе разработки нашлось еще одно решение как по мне более удобное и подходящее под наши задачи OpenAI PHP Client. Возможно для более глубокой работы с Laravel первое решение будет более подходящим, но на данном проекте обычный клиент с более внятным описанием работы и более гибкими требованиями мне показался идеальным решением (плюс ко всему большим рейтингом на GitHub).

Для начала работы с OpenAI API необходимо зарегистрироваться на их сайте и получить ключ доступа.
Далее создаем Laravel API маршрут который будет использоваться для генерации контента, для этого в файл routes/api.php мы добавили строку:

Route::middleware('auth:api')
->post('/generateText', [App\Http\Controllers\OpenAIController::class, 'generateText']);

Данный маршрут обращается к OpenAIController и вызывает метод generateText при этом делает проверку токена авторизации который пользователь получил в кабинете и добавил в плагин.
Для генерации контента в основном нужны такие параметры как модель и шаблон, первый отвечает непосредственно за качество результата и влияет на цену использования (подробнее про доступные модели тут), второй отвечает за формулировку того что именно вы хотите чтобы ИИ вам сгенерировал — к примеру вот один из наших шаблонов для генерации текста:

Create a blog article in {lang} on the following topic: {theme}/n/n consider the {local} spelling and grammar

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

В качестве модели мы выбрали text-davinci-003 так как его описание наиболее подходило под наши задачи:
Может решать любые языковые задачи с лучшим качеством, большей продолжительностью и последовательным выполнением инструкций, чем модели Кюри, Бэббиджа или Ада. Также поддерживает некоторые дополнительные возможности, например, вставку текста. 

К примеру модель gpt-4 наиболее подходит для более сложных задач и живого общения в чате, а модель code-davinci-002 подходит для написания кода.

Основные параметры запроса и их описание можно найти тут вместе с их описанием, ниже будет представлена наша реализация функции генерации текста:

public function generateText( Request $input ) {
	if ( $input->title == null ) { //Проверка передана ли в запросе тема для генерации контента
		return response()->json( [ 'status' => 'error', 'message' => 'Not isset text or type' ] );
	}


	if ( empty( $input->header()['request-domain'] ) || empty( $input->header()['request-domain'][0] ) ) { //Проверка передан ли домен в запросе
		return response()->json( [ 'status' => 'error', 'message' => 'Not isset domain' ] );
	}


	$domain = $input->header()['request-domain'][0];


	$domain = $this->get_domain( $domain ); //Ищем домен в списке добавленных доменов данного пользователя


	if ( ! $domain ) { // Если домена нет возвращаем ошибку
		return response()->json( [ 'status' => 'error', 'message' => 'This domain not exist' ] );
	}


	$title = $input->title;


	$suggestions = ! empty( $input->n ) ? intval( $input->n ) : 1; //Количество вариантов сгенерированного текста выбранное во всплывающем окне


	$words = ! empty( $input->words ) ? intval( $input->words ) : 100; //Количество слов сгенерированного текста выбранное во всплывающем окне


	$tokens = round( $words * 1.25 ); //Преобразуем количество слов в примерное количество токенов для генерации (используется формула с сайта OpenAI)


	$text_length = $words * $suggestions; //Получаем полное количество слов использованных в запросе для регистрации в статистике и списания баланса


	if ( Auth::user()->words_balance < $text_length ) { //Если на счету баланса недостаточно
		if ( Auth::user()->auto_renew ) { //Проверяем включена ли опция автоматического продления
			if ( empty( Auth::user()->preAuthorizationId ) ) { //Проверяем привязана ли карта в аккаунте клиента
				return response()->json(
					[
						'status'  => 'error',
						'message' => 'You must add payment method for refill the balance',
					]
				);
			}


			$charge = BillsController::charge( false ); //Делаем попытке пополнения баланса
			if ( ! $charge ) { //Если попытка не удалась делаем запись в базу и возвращаем ошибку
				requests::create(
					[
						'user_id'     => Auth::id(),
						'words_count' => $text_length,
						'type'        => $input->type,
						'status'      => requests::STATUS_FAILED,
						'domain_id'   => $domain->id,
					]
				);


				return response()->json( [ 'status' => 'error', 'message' => 'Not enough balance for this text' ] );
			}
		} else { //Если нет авто-пополнения, то возвращаем ошибку баланса
			requests::create(
				[
					'user_id'     => Auth::id(),
					'words_count' => $text_length,
					'type'        => $input->type,
					'status'      => requests::STATUS_FAILED,
					'domain_id'   => $domain->id,
				]
			);


			return response()->json( [ 'status' => 'error', 'message' => 'Not enough balance for this text' ] );
		}
	}


	try {
		$client = OpenAI::client( env( 'OPENAI_API_KEY' ) ); //Получаем ключ записанный в .env файле и создаем клиент


		$lang   = requests::$languages[ $input->lang ]['name']; //Получаем название языка
		$local  = requests::$languages[ $input->lang ]['local']; //Получаем код языка
		$prompt = requests::$prompts[ $input->type ]['prompt']; //Получаем шаблон для генерации текста


		$prompt = str_replace( '{lang}', $lang, $prompt ); //Подставляем название языка в шаблоне
		$prompt = str_replace( '{theme}', $title, $prompt ); //Подставляем тему в шаблоне
		$prompt = str_replace( '{local}', $local, $prompt ); //Подставляем код языка в шаблоне


		$result = $client->completions()->create(
			[ // Делаем запрос на генерацию текста
				'model'       => 'text-davinci-003', // Модель
				'temperature' => 0.2, //Данный параметр отвечает за случайность текста
				'n'           => $suggestions, //Количество вариантов
				'max_tokens'  => $tokens, //Количество используемых токенов (символов)
				'prompt'      => $prompt, //Шаблон
			]
		);
	} catch ( \Exception $ex ) {
		return response()->json( [ 'status' => 'error', 'message' => $ex->getMessage() ] );
	}


	if ( empty( $result['choices'] ) ) {
		return response()->json( [ 'status' => 'error', 'message' => 'Choices is empty' ] );
	}


	$text_length = $this->getTextLength( $result['choices'] ); // Считаем количество сгенерированных символов


	requests::create(
		[ //Записываем запрос в статистику
			'user_id'     => Auth::id(),
			'words_count' => $text_length,
			'type'        => $input->type,
			'status'      => requests::STATUS_SUCCESS,
			'domain_id'   => $domain->id,
		]
	);


	Auth::user()->set_balance( Auth::user()->words_balance - $text_length ); //Списываем с баланса пользователя символы


	return response()->json(
		[ //Возвращаем сгенерированный текст
			'status' => 'success',
			'body'   => array(
				'texts' => $result['choices'],
			),
		]
	);
}

Заключение

Переда тем как начать работать с OpenAI API мне казалось, что интеграция с таким новым и довольно сложным сервисом будет непростым занятием, но в итоге в процессе разработки пришло понимание что это чуть ли не самая простая часть разработки данного сервиса. Наличие SDK клиента очень сильно упрощает процесс разработки а интуитивно понятная структура клиента и большое количество документации с примерами превращают процесс разработки решения с использованием OpenAI в легкую прогулку для разработчика даже начального уровня.

Комментирование этой и других статей доступно в нашем Телеграм канале