2026年7月6日

2026年7月6日

WordPressでAIを使ったコンテンツ生成を導入する方法|安全な実装と品質管理の考え方

WordPressにAIによるコンテンツ生成を組み込むなら、「AIが下書きを作り、人が確認して公開する」流れを前提に、APIキーをサーバー側だけで扱う形で実装するのが基本です。この記事では、導入方法の選び方、投稿編集画面から要約の下書きを生成する小さなプラグインの実装例、そして公開前の事実確認やGoogleの方針を踏まえた品質管理の考え方を説明します。

AIに任せやすい作業と任せにくい作業

最初に、どの作業をAIに手伝わせるかを決めます。向いているのは、元になる情報がすでに手元にあり、人が結果を短時間で確認できる作業です。

  • 書き上げた記事の要約、抜粋、メタディスクリプションの下書き
  • 見出し構成や論点の洗い出し
  • 表記ゆれや冗長な表現の指摘
  • 画像の代替テキストの候補

逆に任せにくいのは、AIの出力を確かめる手段が無い作業です。最新の料金やバージョン、法律や医療に関する記述、統計の数値などは、もっともらしい誤り(ハルシネーション)が混ざりやすく、確認の手間が記事を一から書くのと変わらなくなることがあります。

導入方法の選び方

方法は大きく3つあります。

  • 外部のAIサービスで作業し、結果を貼り付ける:導入の手間がなく、まず試すには十分です。ただし作業の記録が残りにくく、担当者ごとに使い方がばらつきます。
  • AI機能を持つプラグインを使う:Jetpack の AI Assistant や AI Engine など、編集画面からAIを呼び出せるプラグインがあります。導入は簡単ですが、どのデータがどこに送られるか、APIキーをどう保存するか、費用がどう発生するかをプラグインごとに確認する必要があります。
  • 自前で実装する:用途を絞った機能を、自社のルールに合わせて作れます。送信するデータとプロンプトを自分で管理できるのが利点です。

WordPress本体でも、AI機能を組み込むための基盤(Abilities API など)の整備が進んでいます。ただし仕様は変わりやすいため、利用する場合は公式の開発者向け情報で最新の状態を確認してください(2026年10月現在)。

実装の前提:APIキーはサーバー側だけで扱う

AIのAPIキーは、漏れると第三者に使われて費用が発生します。次の点を守ってください。

  • APIキーをJavaScriptやHTMLに出力しない。ブラウザから直接AIのAPIを呼ぶ構成にしない
  • APIキーはデータベースではなく wp-config.php の定数か、サーバーの環境変数に置く。データベースのバックアップやエクスポートからの漏えいを避けられます
  • AIの提供元の管理画面で、利用額の上限やアラートを設定する
  • 呼び出しは、編集権限のあるユーザーだけに許可する

wp-config.php には、次のように記述します。your-api-key は実際のキーに置き換えます。

define( 'LSC_AI_API_KEY', 'your-api-key' );
define( 'LSC_AI_MODEL', 'claude-opus-5-5' );

ここでは例として Anthropic の Claude API を使います。モデル名は2026年10月現在のものです。モデルは定期的に新しくなるため、提供元のドキュメントで現行のモデル名を確認し、この定数だけを書き換えれば切り替えられるようにしています。他社のAPIを使う場合は、エンドポイント、ヘッダー、リクエストとレスポンスの形式が異なるため、それぞれのドキュメントに合わせて書き換えてください。

実装例:投稿の要約の下書きを生成する

投稿編集画面の右側に「AIで要約の下書き」というボックスを追加し、ボタンを押すと保存済みの本文から要約を生成して表示するプラグインです。生成結果は自動で保存せず、人が読んで抜粋欄やSEOプラグインの説明欄に貼り付ける運用にしています。

wp-content/plugins/lsc-ai-summary/lsc-ai-summary.php として保存し、管理画面で有効化します。

<?php
/**
 * Plugin Name: LSC AI Summary Helper
 * Description: 投稿本文から要約の下書きをAIで生成する(管理画面専用)
 * Version: 1.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// 1. 投稿編集画面にボックスを追加
add_action( 'add_meta_boxes', function () {
	add_meta_box( 'lsc-ai-summary', 'AIで要約の下書き', 'lsc_ai_render_box', 'post', 'side' );
} );

function lsc_ai_render_box( $post ) {
	printf(
		'<p><button type="button" class="button" id="lsc-ai-generate" data-post-id="%d">要約を生成</button></p>',
		(int) $post->ID
	);
	echo '<textarea id="lsc-ai-result" rows="6" style="width:100%" readonly></textarea>';
	echo '<p class="description">結果は下書きです。内容を確認してから使ってください。対象は保存済みの本文です。</p>';
}

// 2. ボタンを押したときの処理(REST APIを呼ぶだけ。APIキーはブラウザに渡さない)
add_action( 'admin_enqueue_scripts', function ( $hook ) {
	if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
		return;
	}
	$js = <<<'JS'
document.addEventListener('click', function (e) {
	if (!e.target.matches('#lsc-ai-generate')) return;
	var btn = e.target, out = document.getElementById('lsc-ai-result');
	btn.disabled = true;
	out.value = '生成中…';
	wp.apiFetch({ path: '/lsc-ai/v1/summary', method: 'POST', data: { post_id: Number(btn.dataset.postId) } })
		.then(function (res) { out.value = res.text; })
		.catch(function (err) { out.value = 'エラー: ' + (err.message || '不明なエラー'); })
		.finally(function () { btn.disabled = false; });
});
JS;
	wp_register_script( 'lsc-ai-summary', false, array( 'wp-api-fetch' ), '1.0', true );
	wp_enqueue_script( 'lsc-ai-summary' );
	wp_add_inline_script( 'lsc-ai-summary', $js );
} );

// 3. サーバー側でAIのAPIを呼ぶRESTエンドポイント
add_action( 'rest_api_init', function () {
	register_rest_route( 'lsc-ai/v1', '/summary', array(
		'methods'             => 'POST',
		'callback'            => 'lsc_ai_summary',
		'permission_callback' => function ( WP_REST_Request $request ) {
			return current_user_can( 'edit_post', (int) $request['post_id'] );
		},
		'args'                => array(
			'post_id' => array( 'required' => true, 'type' => 'integer', 'minimum' => 1 ),
		),
	) );
} );

function lsc_ai_summary( WP_REST_Request $request ) {
	if ( ! defined( 'LSC_AI_API_KEY' ) || '' === LSC_AI_API_KEY ) {
		return new WP_Error( 'lsc_ai_no_key', 'APIキーが設定されていません。', array( 'status' => 500 ) );
	}

	$post = get_post( (int) $request['post_id'] );
	if ( ! $post ) {
		return new WP_Error( 'lsc_ai_no_post', '投稿が見つかりません。', array( 'status' => 404 ) );
	}

	// HTMLとショートコードを除いた本文。費用と応答時間を抑えるため先頭2万字までを送る
	$text = trim( wp_strip_all_tags( strip_shortcodes( $post->post_content ) ) );
	if ( '' === $text ) {
		return new WP_Error( 'lsc_ai_empty', '本文が空です。下書き保存してから実行してください。', array( 'status' => 400 ) );
	}
	$text = mb_substr( $text, 0, 20000 );

	$response = wp_remote_post( 'https://api.anthropic.com/v1/messages', array(
		'timeout' => 60,
		'headers' => array(
			'x-api-key'         => LSC_AI_API_KEY,
			'anthropic-version' => '2023-06-01',
			'content-type'      => 'application/json',
		),
		'body'    => wp_json_encode( array(
			'model'         => defined( 'LSC_AI_MODEL' ) ? LSC_AI_MODEL : 'claude-opus-5-5',
			'max_tokens'    => 4000,
			'output_config' => array( 'effort' => 'low' ),
			'system'        => 'あなたは日本語の編集者です。渡された記事本文だけを根拠に、120字程度の要約を1つ書いてください。本文に無い事実や数値は加えないでください。',
			'messages'      => array(
				array( 'role' => 'user', 'content' => $text ),
			),
		) ),
	) );

	if ( is_wp_error( $response ) ) {
		return new WP_Error( 'lsc_ai_http', '通信に失敗しました: ' . $response->get_error_message(), array( 'status' => 502 ) );
	}

	$code = (int) wp_remote_retrieve_response_code( $response );
	$data = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( 200 !== $code || ! is_array( $data ) ) {
		return new WP_Error( 'lsc_ai_api', 'AIのAPIがエラーを返しました(HTTP ' . $code . ')。', array( 'status' => 502 ) );
	}
	if ( isset( $data['stop_reason'] ) && 'refusal' === $data['stop_reason'] ) {
		return new WP_Error( 'lsc_ai_refusal', 'この内容では生成できませんでした。', array( 'status' => 422 ) );
	}

	$out = '';
	foreach ( (array) ( $data['content'] ?? array() ) as $block ) {
		if ( isset( $block['type'], $block['text'] ) && 'text' === $block['type'] ) {
			$out .= $block['text'];
		}
	}

	return array( 'text' => sanitize_textarea_field( $out ) );
}

この実装のポイント

  • 権限の確認:RESTエンドポイントの permission_callback で、その投稿を編集できるユーザーかを確認しています。管理画面から wp.apiFetch で呼ぶと、ログイン中のユーザーのRESTノンスが自動で付くため、外部サイトからのなりすましリクエストは拒否されます。
  • APIキーの扱い:キーはPHPの中だけで使い、ブラウザには生成結果のテキストだけを返します。
  • 出力の扱い:AIの出力は外部からの入力と同じように扱い、sanitize_textarea_field() で整えてから返しています。結果を投稿やメタデータに保存する機能を足す場合も、保存時のサニタイズと表示時のエスケープを省かないでください。
  • 応答の読み取り:レスポンスの content には文章以外の種類のブロックが含まれることがあるため、種類が text のものだけをつなげています。
  • プロンプト:「本文だけを根拠に」「本文に無い事実は加えない」と指示し、要約に新しい情報が入り込むのを抑えています。それでも誤りは起こりうるため、確認は人が行います。

確認方法は、投稿を下書き保存し、右側のボックスで「要約を生成」を押して結果が表示されるかを見ます。エラーになる場合は、表示されたメッセージのHTTPステータスで切り分けます。401ならAPIキー、404ならモデル名の誤りを疑います。

公開前の品質管理

AIで作った文章でも、公開した時点でその内容の責任はサイト運営者にあります。次の確認を運用ルールにしておきます。

  • 事実確認:固有名詞、数値、日付、手順、コードは一次情報(公式ドキュメント、法令、自社のデータ)と照合する。AIに出典を挙げさせても、その出典が実在しないことがあるため、リンク先を実際に開いて確かめる
  • 独自性:自社の経験、検証結果、事例など、他のサイトに無い情報を人が加える。AIの出力だけで構成された記事は、一般論の寄せ集めになりがちです
  • 権利と機密:他者の文章を大量に貼り付けて書き換えさせない。顧客情報や未公開の社内情報をAPIに送らない。送信したデータが提供元でどう扱われるか(学習への利用の有無、保存期間)を利用規約で確認する
  • 記録:どの記事のどの部分にAIを使ったか、誰が確認したかを残しておくと、誤りが見つかったときの修正範囲を特定しやすくなります

Googleの方針をどう受け止めるか

Googleは、AIを使ったかどうかではなく、コンテンツの品質を評価するという方針を公表しています。一方で、検索順位の操作を主な目的として、AIを含む自動化で価値の低いページを大量に作ることは、スパムに関するポリシー(大量生成されたコンテンツの不正使用)に違反するとしています。

つまり、AIを使うこと自体が不利になるわけではありませんが、AIを使えば順位が上がるわけでもありません。読者の役に立つか、正確か、独自の情報があるかという、人が書いた記事と同じ基準で判断されると考えるのが妥当です。Googleは、読者が「どのように作られたか」を知りたいと考える場面では、自動化の使い方を説明することも検討するよう勧めています。

まとめ

  • AIには要約、構成案、表記の指摘など、人が短時間で確認できる作業から任せる
  • APIキーは wp-config.php の定数か環境変数に置き、サーバー側だけで使う。ブラウザから直接APIを呼ばない
  • 自前実装では、権限の確認、RESTノンス、出力のサニタイズを省かず、生成結果は人が確認してから使う
  • 公開した内容の責任は運営者にある。事実確認、独自情報の追加、機密情報を送らないことを運用ルールにする
  • Googleは作り方ではなく品質で評価する一方、順位操作目的の大量生成はスパムとして扱う

お気軽にご相談ください

お見積りへ お問い合わせへ
▲