AIによるアンケート結果からの口コミ自動生成
概要
| 項目 | 内容 |
|---|---|
| ステータス | 🟢 進行中 |
| Issue | #10 |
| 担当 | - |
AI がアンケート回答結果を分析し、口コミ文章を自動生成する機能。高評価のアンケート回答後、システムが口コミの下書きを自動生成し、顧客が確認・編集してGoogleに投稿する。
提案内容
背景・課題
- 口コミの作成が手動で行われており、時間と手間がかかる
- アンケートで収集した顧客の声を口コミとして有効活用できていない
- 口コミの品質やトーンが統一されていない
提案するソリューション
LaravelからClaude APIを直接呼び出し、アンケートデータから自然な口コミ文章を生成する機能を構築する。専用のマイクロサービスは不要、Pythonも不要。
主な機能:
- アンケート結果を口コミ形式の文章に自動変換(顧客が事前に選択する必要なし)
- 店舗ごとのキーワード設定 — 設定済みキーワードを自然に含む口コミを生成
- ハイブリッド品質チェック(ルールベース + LLM)
- アンケートのお礼画面に直接統合
- 生成後の編集・確認UI
- 確認後のGoogle口コミ投稿フロー
機能一覧
| # | 機能名 | 説明 | 優先度 |
|---|---|---|---|
| 1 | AI口コミ生成 | アンケート送信後、最も星が高い項目からフル口コミを自動生成 | 高 |
| 2 | 店舗キーワード設定 | 店舗ごとにキーワードを設定し、口コミに自然に反映 | 高 |
| 3 | ハイブリッド品質チェック | ルールベース + LLMで生成文章の品質をチェック | 高 |
| 4 | 編集・確認UI | 生成された口コミの確認・手動編集画面 | 高 |
| 5 | 口コミ投稿連携 | 確認済み口コミをGoogleに直接投稿するフロー | 中 |
画面遷移フロー & モック
全体フロー(お礼画面に統合)
画面一覧
| # | 画面 | 新規/既存 | 説明 |
|---|---|---|---|
| 1 | アンケート画面 | 既存 | 変更なし |
| 2 | お礼画面(低評価) | 既存 | 変更なし |
| 3 | プレビュー・編集 | 新規 | 高評価 → 最も星が高い項目からフル口コミを自動生成 + 編集 + Copy & Google Review投稿ボタン |
既存コードへの挿入箇所
File: resources/js/mappy/views/guest/Questionnaire.vue
現在のロジック(line 288-292):
// アンケート送信後
if (this.getStarRatingAverage() >= this.GOOGLE_REVIEW_BUTTON_DISPLAY_VALUE) {
this.showGoogleReviewLink = true; // Google Reviewボタンのみ表示
}新しいロジック:
// アンケート送信後
axios.post(url, params).then((response) => {
// race conditionを避けるためbackendからanswer_setを返す
this.answerSet = response.data.answer_set;
if (this.getStarRatingAverage() >= this.GOOGLE_REVIEW_BUTTON_DISPLAY_VALUE) {
this.currentStep = 'review';
this.generateReview(); // answerSet付きでAPI呼び出し、最も星が高い項目から直接フル口コミを生成
} else {
this.currentStep = 'thank-you';
}
this.completed = true;
});Race condition
answer_setはsaveAnswersのレスポンスから返却し、generate-review APIに渡す必要がある。 「最新のanswer_set」をクエリしてはいけない。他の顧客が同時にsubmitする可能性があるため。
送信後の2つの画面状態
| 状態 | 条件 | 表示内容 |
|---|---|---|
thank-you | 低評価 | 従来通りのお礼画面 |
review | 高評価 | 最も星が高い項目からフル口コミ(200-400文字)を自動生成 + 編集 + Copy & Google Review投稿ボタン |
口コミ生成の詳細
Sonnetを1回だけ呼び出し、最も星が高い評価項目に焦点を絞った200-400文字のフル口コミを生成する。顧客が事前に書き出しを選択する必要はない。
焦点となる評価項目の選択ロジック:
- 星評価の質問の中から最も星が高い項目を取得
- 複数の項目が同じ最高星数の場合 → 元の質問順を優先(例:接客 → 清潔感 → 満足度、3項目とも★5なら接客を選択)
- 選択された1項目のみ(項目名 + 星数)をプロンプトに送信。他のアンケート回答(残りの項目、テキスト回答、Y/N回答)は送信しない → プロンプトが簡潔で焦点が絞られる
詳細:
- デフォルトトーン: 丁寧
- 待ち時間: 5-10秒
- 「トーン変更」ボタン →
regenerate_with_toneを呼び出し(別トーンで最初から再生成) - 「Copy & Google Review投稿」ボタンは2つのアクションを統合: 内容をクリップボードにコピーし、そのままGoogle Mapsに遷移して顧客が貼り付けて投稿できる
フロントエンドのエラーハンドリング
| エラー | 表示内容 |
|---|---|
| 生成失敗(3回リトライ超過) | 「生成に失敗しました」メッセージ + 「もう一度試す」ボタン または Google Reviewボタン |
| レート制限(429) | 「本日の生成上限に達しました」メッセージ + Google Reviewボタン |
| タイムアウト / APIエラー | 通常のお礼画面 + Google Reviewボタンにフォールバック(既存フロー) |
原則: AI失敗時は常にGoogle Reviewボタンにフォールバックし、顧客が手動でレビューを書けるようにする。フローをブロックしない。
既存アンケートフロー
QRコード → アンケート回答 → 高評価時に口コミ案内
店舗に設置されたQRコードをスキャン
AI口コミ生成フロー
最も星が高い項目からフル口コミを自動生成 → 編集 → Copy & 投稿
アンケート
ご来店ありがとうございますAI reviewキーワード設定(新規画面)
既存のキーワード画面を使わない理由
画面 /mappy/keywords(KeywordSettings.vue)はmappy_keywordsテーブルに店舗あたり最大8キーワードで保存している。このデータはスクレイピングに使用されている。9番目以降のキーワードを追加するとスクレイピングのロジックに影響するか、スクレイピングのデータ取得ロジックの更新が必要になる。そのためAI reviewキーワード用に別テーブルと別画面を作成する必要がある。
継承ロジック: 店舗がAI review用のキーワードを未設定の場合 → mappy_keywordsテーブル(既存のキーワード画面)のキーワードをデフォルトとして自動使用。
| 項目 | 内容 |
|---|---|
| 画面パス | /mappy/review-keywords (新規) |
| コンポーネント | ReviewKeywordSettings.vue (新規) |
| DBテーブル | mappy_review_keywords (新規) |
| フォールバック | 未設定の場合 → mappy_keywordsから継承 |
| 上限 | キーワード数制限なし(推奨3-10) |
レビュー生成時のキーワード取得フロー:
1. mappy_review_keywords WHERE gbp_location_id = ? をクエリ
2. 結果がある場合 → 専用キーワードを使用
3. 空の場合 → フォールバックで mappy_keywords WHERE gbp_location_id = ? をクエリキーワード管理
変更内容
| 項目 | 現行 | 変更後 |
|---|---|---|
| キーワード上限 | 8件 | 無制限(推奨10件以上) |
| スクレイピング | 8件固定 | 登録数に応じて動的 |
| 表示 | 固定レイアウト | スクロール対応 |
アーキテクチャ
技術スタック
| 項目 | 技術 | 備考 |
|---|---|---|
| バックエンド | Laravel(既存) | 新規サービス不要、既存コードベースに追加 |
| LLM API | Anthropic API(HTTP直接呼び出し) | Guzzle HTTPクライアントを使用 |
| LLM(生成) | Claude Sonnet 4 | 日本語品質が良好、口コミ生成に使用 |
| LLM(チェック) | Claude Haiku 4.5 | 低コスト、品質チェックに使用 |
| APIキー | 1つのキーを共有(全モデル共通) | ANTHROPIC_API_KEY |
Python / LangChainが不要な理由
この機能の処理フローは「プロンプト送信 → テキスト受信 → JSON検証」という1回のリトライを含む単純な順次処理のみ。LangChain/LangGraphの機能(RAG、tool calling、parallel branching、multi-agent)は一切使用しない。LaravelからAnthropic APIを直接呼び出すことで:
- 依存関係の削減: Python パッケージ約15個の追加が不要
- デプロイの簡素化: 専用マイクロサービス不要、Python用Dockerも不要
- デバッグの容易さ: API呼び出しを隠す抽象化レイヤーがない
- 既存インフラの活用: Laravel サーバーを共有、Guzzle HTTPクライアントが既に利用可能
システム構成
アーキテクチャの特徴:
- 専用マイクロサービス不要 — 既存Laravelコードベースに追加
- Guzzle HTTPクライアント(既存)でClaude APIを直接呼び出し
- デプロイ・運用がシンプル: 1サーバー、1ログ
口コミ生成フロー
| ステップ | 処理内容 |
|---|---|
| 最も星が高い項目を選択 | 最も星が高い評価項目を特定(同点の場合は質問順でtie-break)+ 店舗キーワードをクエリ |
| 口コミ生成 | Anthropic API(Sonnet 4)を1項目 + キーワード + トーンのみを含むプロンプトで呼び出し(アンケートの残りは送信しない) |
| 品質チェック | ルールベースを先に → LLMを後に(ハイブリッド、詳細は下記) |
| フィードバック付き再生成 | NG時、スコア + 改善提案を付けてプロンプトを再送信(最大3回) |
品質チェック(ハイブリッド)
全5項目をLLMで呼び出す代わりに、2ステップに分割:
ステップ1: ルールベース(即時、無料)
| 基準 | 配点 | チェック方法 |
|---|---|---|
| 文字数(200〜400) | 20点 | mb_strlen() |
| キーワード反映(1〜3語) | 15点 | 部分文字列チェック |
- ルールベースで明確にfailした場合(例:50文字のみ)→ LLMステップをスキップし、即座にリトライ
- 明らかなfailケースでLLM呼び出し1回を節約
ステップ2: LLM(意味理解が必要な基準のみ)
| 基準 | 配点 | チェック方法 |
|---|---|---|
| 自然さ(AI感がないか) | 30点 | Claude Haiku 4.5 |
| 不適切な表現 | 20点 | Claude Haiku 4.5 |
| 焦点の反映(選択された項目) | 15点 | Claude Haiku 4.5 |
合格基準: 合計 >= 70点 → 合格 / 70点未満 → 再生成(最大3回)
プロンプト構成
プロンプトの詳細設計はプロンプト & API設計を参照。
Generate full review(概要):
Sonnetに送る入力は以下のみ: 最も星が高い項目の名前 + 星数 + 店舗キーワードリスト + トーン。 アンケートの他の回答は送信しない。
System: 口コミ生成アシスタント
- 提供された1つの項目に焦点を絞り、200〜400文字のフル口コミを生成
- デフォルトトーン: 丁寧
- 店舗キーワードを自然に組み込む(詰め込みNG)
User (例):
項目: 接客 (★5)
キーワード: カット, カラー, スタイリング
トーン: 丁寧LaravelからのAPI呼び出し(例)
// app/Services/ReviewGeneratorService.php
use Illuminate\Support\Facades\Http;
class ReviewGeneratorService
{
public function generate(array $topItem, array $keywords, string $tone = '丁寧'): array
{
// $topItem = ['name' => '接客', 'rating' => 5] ← 最も星が高い項目のみ
// surveyデータ全体を受け取らない — プロンプトを簡潔にし、AIが不要なデータに引きずられるのを避ける
// 1. 最も星が高い項目からフル口コミを生成(Sonnet 4)
$review = $this->callClaude('claude-sonnet-4-20250514', 0.8, [
['role' => 'user', 'content' => $this->buildGeneratePrompt($topItem, $keywords, $tone)]
]);
// 2. ハイブリッド品質チェック
for ($attempt = 0; $attempt < 3; $attempt++) {
$check = $this->qualityCheck($review, $topItem, $keywords);
if ($check['passed']) {
return ['review_text' => $review, 'quality_score' => $check['score']];
}
// フィードバック付きリトライ
$review = $this->callClaude('claude-sonnet-4-20250514', 0.9, [
['role' => 'user', 'content' => $this->buildRetryPrompt($topItem, $keywords, $tone, $check['feedback'])]
]);
}
throw new GenerationFailedException();
}
private function qualityCheck(string $review, array $topItem, array $keywords): array
{
$score = 0;
// ステップ1: ルールベース(即時)
$charCount = mb_strlen($review);
$score += ($charCount >= 200 && $charCount <= 400) ? 20 : 0;
$found = collect($keywords)->filter(fn($kw) => str_contains($review, $kw))->count();
$score += min($found, 3) * 5;
// ルールベースで明確にNGなら早期fail
if ($score < 15) {
return ['passed' => false, 'score' => $score, 'feedback' => '文字数またはキーワードが基準未達'];
}
// ステップ2: LLMチェック(Haiku 4.5)— 項目名+星数のみ渡し、焦点が反映されているか確認
$llmResult = $this->callClaude('claude-haiku-4-5-20251001', 0.1, [
['role' => 'user', 'content' => $this->buildQualityCheckPrompt($review, $topItem)]
]);
$result = json_decode($llmResult, true);
$score += ($result['naturalness'] ?? 0) + ($result['inappropriate'] ?? 0) + ($result['topic_reflection'] ?? 0);
return ['passed' => $score >= 70, 'score' => $score, 'feedback' => $result['feedback'] ?? ''];
}
private function callClaude(string $model, float $temperature, array $messages, string $system = ''): string
{
// 全モデル共通の1つのAPIキー
$payload = [
'model' => $model,
'max_tokens' => 1024,
'temperature' => $temperature,
'messages' => $messages,
];
// Anthropic API: system promptはmessagesとは別に送信する必要がある
if ($system) {
$payload['system'] = $system;
}
$response = Http::withHeaders([
'x-api-key' => config('services.anthropic.api_key'),
'anthropic-version' => '2023-06-01',
])->timeout(30)->post('https://api.anthropic.com/v1/messages', $payload);
if ($response->failed()) {
throw new \RuntimeException("Anthropic API error: {$response->status()}");
}
return $response->json('content.0.text');
}
}LLM比較 & コスト
| Model | Input | Output | コスト/件 | 日本語品質 | 推奨 |
|---|---|---|---|---|---|
| Claude Sonnet 4 | $3.00/1M tokens | $15.00/1M tokens | ~¥0.5 | ◎ | 推奨 |
| Claude Haiku 4.5 | $0.80/1M tokens | $4.00/1M tokens | ~¥0.1 | ○ | コスト優先 |
| GPT-4o | $2.50/1M tokens | $10.00/1M tokens | ~¥0.4 | ◎ | 代替 |
| GPT-4o mini | $0.15/1M tokens | $0.60/1M tokens | ~¥0.03 | △ | 非推奨 |
| Gemini 2.5 Flash | $0.15/1M tokens | $0.60/1M tokens | ~¥0.03 | ○ | コスト優先 |
コスト算出条件
- 1件あたり: 入力 ~150 tokens(項目名 + 星数 + キーワード + トーン)+ 出力 ~400 tokens(口コミ文章)
- 品質チェック再生成: 平均1.2回/件
- 100件/月生成の場合: Claude Sonnet 4 ~¥50/月(プロンプトが簡潔になったため旧設計より削減)
推奨構成
| 用途 | Model | 理由 |
|---|---|---|
| 口コミ生成(本番) | Claude Sonnet 4 | 日本語品質◎、コスト妥当 |
| 品質チェック | Claude Haiku 4.5 | 判定処理にはコスト優先 |
| 開発・テスト | Claude Haiku 4.5 | 高速、繰り返しテストに低コスト |
概算工数(AI前提)
体制
| 役割 | 人数 | 担当内容 |
|---|---|---|
| 設計者 | 1名 | 要件確認 → AIに設計書作成指示 → レビュー → 製造へ指示 |
| 製造者 | 1名 | ISSUEを元にAIに作成指示 → コードレビュー → テスト実施 → デプロイ |
工数内訳(Python廃止により削減)
| # | 作業項目 | AIリテイク | レビュー | 工数(人日) | 担当 |
|---|---|---|---|---|---|
| 1 | 要件確認 & 設計書作成 | 2回 | 0.5日/回 | 1.0 | 設計 |
| 2 | プロンプト設計 & API設計 | 2回 | 0.5日/回 | 1.0 | 設計 |
| 3 | AI生成Service実装(Laravel) | 2回 | 0.5日/回 | 1.0 | 製造 |
| 4 | レビュー画面UI実装(Vue.js) | 2回 | 0.5日/回 | 1.0 | 製造 |
| 5 | Copy & Google Review投稿ボタン実装 | 1回 | 0.5日/回 | 0.5 | 製造 |
| 6 | 結合テスト & 品質調整 | 2回 | 0.5日/回 | 1.0 | 製造 |
| 7 | デプロイ & 動作確認 | 1回 | 0.5日/回 | 0.5 | 製造 |
| 合計 | 6.0 |
前提条件・制約
- Anthropic APIアカウントと
ANTHROPIC_API_KEYが設定済みであること - アンケートデータがDBからクエリ可能であること(
mappy_questionnaire_answers) - LaravelサーバーがHTTPS外部通信可能であること(
api.anthropic.com宛) - 設計・製造ともにAI(Claude Code/Cursor等)を活用することが前提
スケジュール
| タスク | 担当 | 日数 | 4/3 | 4/4 | 4/5 | 4/6 | 4/7 | 4/8 | 4/9 | 4/10 | 4/11 | 4/12 | 4/13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 金 | 土 | 日 | 月 | 火 | 水 | 木 | 金 | 土 | 日 | 月 | |||
| 要件確認 & 設計書作成 | 設計 | 1d | |||||||||||
| プロンプト & API設計 | 設計 | 1d | |||||||||||
| 設計レビュー & 修正 | 設計 | 1d | |||||||||||
| 製造指示 & ISSUE作成 | 設計 | 1d | |||||||||||
| AI生成Service実装(Laravel) | 製造 | 1d | |||||||||||
| レビュー画面UI実装 | 製造 | 1d | |||||||||||
| Copy & Google投稿ボタン実装 | 製造 | 1d | |||||||||||
| 結合テスト & 品質調整 | 製造 | 1d | |||||||||||
| デプロイ & 動作確認 | 製造 | 1d |