おはようございます!マネジメントオフィスいまむらの今村敦剛です。
WordPressでフォーム経由の営業メールにめちゃめちゃうんざりしてました。そこでContact Form 7(以下CF7)の問い合わせ文をOpenAI APIでチェックして、営業・勧誘と判断したら送信を止める仕組みを作りました。
どんな仕組みなのかをざっくり解説
流れはシンプルです。
- WordPressで作成したCF7のフォームで、ユーザーが記入して送信
- 送信内容をOpenAIに渡して、「営業」「スパム」「普通の問い合わせ」を判定してもらう
- 営業やスパムなら、送信を止めてエラーメッセージを出す
- 普通の問い合わせなら、そのまま送信する
これをfunction.phpに追記するだけで実現しようという試みです。
止めたくない「普通の問い合わせ」をどうやって通すか
止めるのは簡単なのですが、もっとも問題なのは、止めたくない「普通の問い合わせ」をどうやって通すかということですね。自社のサービスに興味がある人の問い合わせを止めてしまうと、機会損失になりますからね。
そこで今回の仕組みでは、次のようなルールをAIに教えます。
- ユーザーが、「自社のサービス」について質問しているなら normal(普通の問い合わせ)に分類
- ユーザーが「ユーザーのサービス」を売り込んでいるなら sales(営業)に分類
- 詐欺っぽい・意味不明なら spam(スパム)に分類
そのうえで、AIに渡すプロンプト内で自社のサービスを明示します。そうするとAIは判断に迷いにくくなりますよね。
今回の試した内容では、自社(マネジメントオフィスいまむら)のサービスを次の3つに決めて、プロンプト内に収めるようにしました。
- ISOマネジメントシステムのコンサル・研修
- カーボンニュートラルのコンサル・研修
- AIを使った業務改善(効率化・最適化・整理)のコンサル
こう書いておくと、AIが「それは自社サービスへの問い合わせだから normal」「自社のサービスでない話をしているからsalesだ」と判断しやすくなります。
こういうコードを子テーマのfunction.phpに貼るだけ
これを実現するために作成したコードを以下に紹介します。こういうコードを子テーマのfunction.phpに貼るだけで実現できるのですが、試すときは必ずバックアップをとってからにしてくださいね。(function.phpの修正はかなりのリスクを伴いますので注意が必要です)
<?php
/**
* Contact Form 7 × OpenAI 判定で営業/スパムをブロック(最小サンプル)
* 貼り付け先:子テーマ functions.php
*/
define('OPENAI_API_KEY', 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); // 実キーを設定(絶対に公開しない)
define('OPENAI_MODEL', 'gpt-4o-mini');
define('OPENAI_TIMEOUT_SECONDS', 15);
// CF7フィールド名(あなたのフォームに合わせてください)
define('CF7_FIELD_COMPANY', 'your-subject'); // 会社名・屋号
define('CF7_FIELD_MESSAGE', 'your-message');
// API失敗時の動き:まずは fail_open 推奨(誤ブロックを避ける)
define('OPENAI_FAIL_MODE', 'fail_open'); // fail_open / fail_closed
function cf7_ai_classify($content) {
$endpoint = 'https://api.openai.com/v1/chat/completions';
$system_prompt = "You are a strict anti-spam filter for a Japanese company's contact form. Output must be JSON only.";
// ★ここが肝:自社サービス(OUR service/product)を定義して、そこへの問い合わせは normal を強制する
$user_prompt = <<<EOT
Classify the message into exactly one of: "sales", "spam", "normal".
OUR service/product (this company offers ONLY these):
A) Consulting and training on ISO management systems
B) Consulting and training on carbon neutral / decarbonization
C) Consulting on making business effective/optimized/organized by using AI (AI-based business improvement / DX)
CRITICAL DECISION RULES (must follow):
1) If the sender is asking about OUR service/product (A/B/C) such as pricing, schedule, details, quotation, contract, how to join, training contents, consulting scope, delivery method, support after training, then inquiry_type MUST be "normal" even if words like "seminar" appear.
2) "sales" means the sender is trying to sell/promote THEIR service/product to us (unsolicited marketing, SEO offers, outsourcing proposal, partnership pitch, seminar invitation to THEIR event, etc).
3) "spam" is scams/phishing/malware, nonsense, or auto-generated junk.
4) If uncertain between "normal" and "sales", choose "normal" unless it is clearly promoting something to us.
score definition:
- score is confidence that the message is "sales" OR "spam" (0=safe normal, 100=definitely sales/spam).
Return JSON only:
{
"score": 0-100,
"inquiry_type": "sales" or "spam" or "normal",
"reason": "Explanation in Japanese"
}
Content:
{$content}
EOT;
$payload = [
'model' => OPENAI_MODEL,
'temperature' => 0,
'messages' => [
['role' => 'system', 'content' => $system_prompt],
['role' => 'user', 'content' => $user_prompt],
],
'response_format' => ['type' => 'json_object'],
];
$resp = wp_remote_post($endpoint, [
'headers' => [
'Authorization' => 'Bearer ' . OPENAI_API_KEY,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode($payload),
'timeout' => OPENAI_TIMEOUT_SECONDS,
]);
if (is_wp_error($resp)) return ['ok'=>false, 'error'=>$resp->get_error_message()];
$code = (int) wp_remote_retrieve_response_code($resp);
$raw = (string) wp_remote_retrieve_body($resp);
if ($code !== 200) return ['ok'=>false, 'error'=>"HTTP {$code}", 'raw'=>$raw];
$decoded = json_decode($raw, true);
$content_json = $decoded['choices'][0]['message']['content'] ?? '';
$json = json_decode($content_json, true);
if (!is_array($json)) return ['ok'=>false, 'error'=>'Invalid JSON', 'raw'=>$content_json];
return [
'ok' => true,
'inquiry_type' => $json['inquiry_type'] ?? 'normal',
'score' => (int)($json['score'] ?? 0),
'reason' => $json['reason'] ?? '',
];
}
function cf7_ai_should_block($judge) {
// OpenAIが成功したら、typeを最優先で判断する
if (!empty($judge['ok'])) {
$type = (string)($judge['inquiry_type'] ?? 'normal');
if ($type === 'sales' || $type === 'spam') return true;
return false; // normal は通す(scoreで基本ブロックしない)
}
// API失敗時:まずは通す(fail_open)
if (OPENAI_FAIL_MODE === 'fail_open') return false;
// fail_closed にしたい場合は、ここでフォールバック判定を入れる
return false;
}
// CF7のvalidateで送信を止める
add_filter('wpcf7_validate', function($result, $tags){
if (!class_exists('WPCF7_Submission')) return $result;
$submission = WPCF7_Submission::get_instance();
if (!$submission) return $result;
$data = $submission->get_posted_data();
$company = trim((string)($data[CF7_FIELD_COMPANY] ?? ''));
$message = trim((string)($data[CF7_FIELD_MESSAGE] ?? ''));
$content = "Company: {$company}\nMessage:\n{$message}\n";
$judge = cf7_ai_classify($content);
if (cf7_ai_should_block($judge)) {
$result->invalidate(CF7_FIELD_MESSAGE, '営業・勧誘またはスパムの可能性が高いため送信できませんでした。');
}
return $result;
}, 20, 2);
使ってみて分かった注意点
この仕組みを入れて、過去に実際にきた普通の問い合あわせ(normal)と営業メール(sales)をフォームに入力し、正しく仕訳ができるかを確認しました。体感としてはかなりの精度で仕訳してくれますが、「完璧にゼロ」にはなりませんね。まあ、完璧を求めると機会損失になるリスクも高まるので、「まあまあ減ったな」程度で押さえておくほうがいいのかもしれません。
またコード作成で気をつけた点は、APIが落ちたときにどうするかです。その時はfail_open(APIがダメなら通す)で運用して、誤ブロックの事故が起きないようにしておくのが安全かな?と思います。
というわけで、OpenAI APIを使うと、「この文章は売り込みか?それとも本当の問い合わせか?」を文章の意味で見分けられるようになりました。そして精度を上げるコツは、「自社サービス(OUR service/product)をはっきり定義しておく」ことだ、ということですね。
