Prompt & API設計 — AI口コミ自動生成
| 項目 | 内容 |
|---|---|
| ステータス | 🟡 議論中 |
| 関連 | #10 AI口コミ自動生成 |
1. Prompt設計
1.1 Prompt一覧
| # | Prompt名 | 目的 | 使用LLM | 呼び出し元 |
|---|---|---|---|---|
| 1 | generate_review | 最も星が高い項目からフル口コミを生成(1回呼び出し) | Claude Sonnet 4 | ReviewGeneratorService |
| 2 | quality_check | 品質チェック(LLM部分) | Claude Haiku 4.5 | ReviewGeneratorService |
| 3 | regenerate_with_feedback | NG時フィードバック付き再生成 | Claude Sonnet 4 | ReviewGeneratorService |
| 4 | regenerate_with_tone | tone変更時の再生成 | Claude Sonnet 4 | AiReviewController@regenerateReview |
1.2 口コミ生成Prompt (generate_review)
最も星が高い項目のみから200-400文字のフル口コミを生成。1回だけ呼び出し、プレビュー段階なし。
評価項目の選択ロジック(LLM呼び出し前にバックエンドで処理):
- 星評価の質問の中から最も星が高い項目を取得
- 複数の項目が同じ最高星数の場合 → 元の質問順を優先(例:接客 → 清潔感 → 満足度、3項目とも★5なら接客を選択)
- 項目名 + 星数のみをプロンプトに送信。他のアンケート回答(残りの星評価項目、テキスト回答、Y/N回答)は送信しない
入力変数:
| 変数 | 型 | 説明 | 例 |
|---|---|---|---|
top_item | object | 最も星が高い項目(tie-break後) | { name: "接客", rating: 5 } |
tone | string | 口コミのtone | デフォルト 丁寧 |
shop_name | string | 店舗名 | 銀座ヘアサロンA |
store_keywords | string | 店舗キーワード(カンマ区切り) | 丁寧な接客, 駅近, 清潔感 |
survey_answersは含まない。他の星評価項目や他の回答は送信しない — プロンプトが簡潔になり、inputが約150 tokens(従来は約800 tokens)に削減。
System Prompt:
あなたは実際の顧客として口コミを書くアシスタントです。
指定された1つの高評価項目を中心に、完全な口コミ文を生成してください。
【トーン】{tone}
【トーン別の文体ガイド】
- 丁寧: 敬語を使用。「〜していただきました」「大変満足しております」。30〜50代の落ち着いた印象。
- カジュアル: 友人に話すような口調。「〜だった!」「めっちゃ良かった」。20〜30代の明るい印象。
- ビジネス: 客観的で簡潔。「サービスの質が高い」「費用対効果が良い」。ビジネスパーソンの印象。
【生成ルール】
1. 文字数: 200〜400文字
2. 焦点: 指定された1つの項目を中心に書く。他の話題に広げすぎない
3. 構成: 来店のきっかけ → 指定項目の体験詳細 → 感想・まとめ
4. キーワード: 指定キーワードを文脈に合わせて自然に1〜3個含める。無理に全て入れる必要はない
5. 自然さ: 以下を避ける
- 箇条書き形式
- 「まず」「次に」「最後に」のような明確な段落構成語
- 過度な褒め言葉の連続
- 全てのキーワードを機械的に詰め込む
- 「〜がおすすめです」のような宣伝口調
6. 人間らしさ: 小さな感想や個人的なエピソードを含めて、実体験感を出すHuman Prompt:
以下の情報を元に、200〜400文字の完全な口コミ文を生成してください。
【店舗名】{shop_name}
【中心にする項目】{top_item.name}(★{top_item.rating})
【店舗キーワード】{store_keywords}
【トーン】{tone}LLMパラメータ:
| パラメータ | 値 | 理由 |
|---|---|---|
| model | claude-sonnet-4-20250514 | 日本語品質が高い |
| temperature | 0.8 | 多様な文体を生成 |
| max_tokens | 800 | 400文字 (~600トークン) + 余裕 |
出力例:
選択された項目: 接客 (★5)
初めて銀座ヘアサロンAを利用いたしました。スタッフの方がとても親切で、カウンセリングも丁寧に対応してくださり、初めてでも安心して任せられる雰囲気でした。髪の悩みをしっかり聞いた上で、似合うスタイルを提案してくださり、仕上がりも想像以上でした。駅から近く通いやすい立地も魅力で、次回もぜひお願いしたいと思います。
※ アンケートに他の項目があっても、口コミ全体が「接客」1項目に焦点を絞っている
レスポンスタイム: 5-10秒 (Sonnet + 出力 ~400文字)
1.4 品質チェック — Hybrid方式
品質チェックは2段階(ハイブリッド)に分割:
段階1: Rule-based (即時、無料)
| # | 基準 | 配点 | チェック方法 | 早期失敗? |
|---|---|---|---|---|
| 1 | 文字数 (200〜400) | 20点 | mb_strlen() | 100未満または600超 → 即リトライ |
| 2 | キーワード反映 (1〜3語) | 15点 | str_contains() | — |
- 段階1の合計 < 15点 → 段階2をスキップ、即リトライ(LLM呼び出し1回分の節約)
- 段階1の合計 >= 15点 → 段階2へ進む
段階2: LLM (意味解析が必要な基準のみ)
| # | 基準 | 配点 | チェック方法 |
|---|---|---|---|
| 3 | 自然さ (AI感がないか) | 30点 | Claude Haiku 4.5 |
| 4 | 不適切な表現 | 20点 | Claude Haiku 4.5 |
| 5 | 焦点の反映(選択された項目) | 15点 | Claude Haiku 4.5 |
使用モデル: Claude Haiku 4.5 (temperature: 0.1 — 評価結果の安定性のため)
合格基準: 合計 (段階1 + 段階2) >= 70点 → 合格 / 70未満 → 再生成(最大3回)
LLM品質チェック用Prompt (段階2):
以下の口コミ文を評価してください。JSON形式で回答してください。
【評価項目】
1. naturalness (0〜30点): AI生成と分かる表現がないか。箇条書き・過度な褒め・均一な構成を減点
2. inappropriate (0〜20点): 不適切表現・誇大広告・事実と異なる表現がないか
3. topic_reflection (0〜15点): 指定された項目({top_item.name})が中心に反映されているか
【口コミ文】
{review_text}
【中心にすべき項目】{top_item.name}(★{top_item.rating})
JSON形式で回答:
{"naturalness": 点数, "inappropriate": 点数, "topic_reflection": 点数, "feedback": "改善点(なければ空文字)"}品質チェックは項目名 + 星数のみを受け取り、アンケート回答全体は受け取らない — 生成プロンプトと同じく1つのトピックのみに集中させる。
出力JSONフォーマット:
{
"naturalness": 25,
"inappropriate": 20,
"topic_reflection": 12,
"feedback": "「まず」「次に」の接続詞が目立つ。もう少し自然な流れに。"
}JSON parseの安全対策
LLMがJSONの前後に余分なテキストを返す場合がある。parseの前にregexでJSONを抽出する必要がある:
// レスポンスからJSONを抽出(LLMが余分なテキストを返す場合がある)
private function parseJsonResponse(string $response): array
{
// レスポンス内の最初のJSONオブジェクトを検索
if (preg_match('/\{[^{}]*\}/', $response, $matches)) {
$decoded = json_decode($matches[0], true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
}
// JSON parse失敗 → 品質チェック失敗として扱い、リトライをトリガー
return [
'naturalness' => 0,
'inappropriate' => 0,
'topic_reflection' => 0,
'feedback' => 'Quality check JSON parse failed, retrying...',
];
}JSON parse失敗時は例外をスローせず、スコア0を返す → フロー内で自然にリトライをトリガーする。
1.5 再生成Prompt
品質NG時 (regenerate_with_feedback)
System Prompt: generate_review (1.2節) と同じ
Human Prompt:
以下の情報から口コミ文を生成してください。
前回の生成は品質チェックで不合格でした。フィードバックを参考に改善してください。
【前回のスコア】{previous_score}/100
【改善フィードバック】{feedback}
【店舗名】{shop_name}
【中心にする項目】{top_item.name}(★{top_item.rating})
【店舗キーワード】{store_keywords}
【トーン】{tone}
※ 前回と異なる書き出し・構成で書いてください。| パラメータ | 値 | 理由 |
|---|---|---|
| model | claude-sonnet-4-20250514 | 品質を維持 |
| temperature | 0.9 | 前回と異なる文章を生成するため高めに設定 |
| max_tokens | 800 | — |
tone変更時 (regenerate_with_tone)
System Prompt: generate_review と同じだが {tone} が変更
Human Prompt:
以下の情報から口コミ文を生成してください。
前回は「{previous_tone}」トーンで生成しましたが、今回は「{tone}」トーンで新しく生成してください。
【店舗名】{shop_name}
【中心にする項目】{top_item.name}(★{top_item.rating})
【店舗キーワード】{store_keywords}
【トーン】{tone}
※ 前回の文章「{previous_review_snippet}」の言い回しを流用しないでください。| パラメータ | 値 | 理由 |
|---|---|---|
| model | claude-sonnet-4-20250514 | 品質を維持 |
| temperature | 0.8 | 多様性を保ちつつ安定 |
| max_tokens | 800 | — |
previous_review_snippet: 前回の先頭50文字。LLMが繰り返しを避けるための参照用。
1.6 チューニング用設定値
| 設定 | デフォルト値 | 調整範囲 |
|---|---|---|
| 合格スコア | 70 | 50〜90 |
| 最大リトライ回数 | 3 | 1〜5 |
| 最小文字数 | 200 | 100〜300 |
| 最大文字数 | 400 | 300〜600 |
config/anthropic.php→review_generatorで定数管理。将来的に管理画面から変更可能にする。
// config/anthropic.php
'review_generator' => [
'pass_score' => env('REVIEW_PASS_SCORE', 70),
'max_retries' => env('REVIEW_MAX_RETRIES', 3),
'min_chars' => env('REVIEW_MIN_CHARS', 200),
'max_chars' => env('REVIEW_MAX_CHARS', 400),
],2. API設計 (Laravel直接呼び出し)
2.1 システム構成
Frontend (Vue.js) → Laravel API (既存) → Anthropic API (HTTPS)- Frontend → Laravel: 認証/セッション管理は既存Laravelで処理
- Laravel → Anthropic API: Guzzle HTTPクライアントでHTTPS直接呼び出し
- 専用マイクロサービス不要、Python FastAPIも不要
2.2 Laravel APIエンドポイント (Guest — ログイン不要)
以下のエンドポイントはアンケート画面(guest)から呼び出され、既存のguestルートグループに配置。
POST /guest/questionnaires/generate-review — 最も星が高い項目からフル口コミを生成(1回呼び出し)
Request:
{
"questionnaireId": 201,
"answerSet": 42,
"hash": "abc123"
}
answerSetはsaveAnswersレスポンスから返される値。race condition(複数の顧客が同時にsubmitする場合)を避けるため、具体的な値を指定する必要がある。
selected_preview、selectedItem、アンケート回答は不要 — バックエンドがanswerSetから最も星が高い項目を自動選択する。
バックエンド処理フロー:
questionnaireId+answerSetからアンケート回答を取得- 星評価(★)の質問をフィルタし、最も星が高い項目を選択
- Tie-break: 元の質問順を優先(例:接客 → 清潔感 → 満足度)
store_keywordsを取得:mappy_review_keywords→ フォールバックmappy_keywords- 店舗から
shop_nameを取得 top_item(name + rating) +keywords+tone(デフォルト丁寧)のみでプロンプトを構築。他のアンケート回答は送信しない。- Anthropic API (Sonnet 4) を呼び出してフル口コミを生成
- Hybrid品質チェック (rule-based → LLM)
- NGの場合リトライ(最大3回)
- 結果をフロントエンドに返却
Response:
{
"status": "success",
"data": {
"review_text": "初めて銀座ヘアサロンAを利用いたしました。スタッフの方がとても親切で、カウンセリングも丁寧に対応してくださり...",
"top_item": { "name": "接客", "rating": 5 },
"char_count": 280,
"quality_score": 85,
"tone_used": "丁寧",
"retry_count": 0
}
}
top_itemは次のregenerateリクエストでフロントエンドから送り返せるように返却(DBへの再クエリを避ける)。
タイムアウト: 30秒
POST /guest/questionnaires/regenerate-review — tone変更 / 再生成
Request:
{
"questionnaireId": 201,
"answerSet": 42,
"hash": "abc123",
"tone": "カジュアル",
"top_item": { "name": "接客", "rating": 5 },
"previous_review": "初めて銀座ヘアサロンAを...",
"previous_tone": "丁寧"
}フロントエンドは
generate-reviewのレスポンスで受け取ったtop_itemを送り返し、口コミの焦点を維持する。
Response: generate-reviewと同じフォーマット
POST /guest/questionnaires/submit-review — 口コミ保存 + Google投稿URL返却
Request:
{
"questionnaireId": 201,
"answerSet": 42,
"hash": "abc123",
"review_text": "初めて銀座ヘアサロンAを...",
"top_item": { "name": "接客", "rating": 5 },
"tone": "丁寧",
"edited": true,
"quality_score": 85
}Response:
{
"success": true,
"data": {
"review_id": 789,
"google_review_url": "https://search.google.com/local/writereview?placeid=XXXXX"
}
}フロントエンドは統合された「Copy & Google Review投稿」ボタンで
google_review_urlを使用: 同じクリックでreview_textをクリップボードにコピーし、window.open(google_review_url)でGoogle Mapsを開く。
実際の投稿はユーザーが自身のGoogleアカウントで行う(Google対策)。
2.3 Laravelコード構成
app/
├── Http/Controllers/Mappy/
│ ├── Guest/
│ │ ├── QuestionnaireController.php ← 修正: saveAnswersがanswer_setを返す
│ │ └── AiReviewController.php ← 新規: AI review 4エンドポイント
│ └── ReviewKeywordsController.php ← 新規: AI reviewキーワードCRUD
├── Services/Mappy/
│ ├── ReviewGeneratorService.php ← 新規: 生成 + 品質チェック + キーワードフォールバックロジック
│ └── AnthropicApiService.php ← 新規: Claude API呼び出しラッパー
├── Models/Mappy/
│ ├── Review.php ← 新規
│ ├── ReviewGenerationLog.php ← 新規
│ └── ReviewKeyword.php ← 新規
resources/js/mappy/views/
├── guest/
│ ├── Questionnaire.vue ← 修正: 送信後にgenerate-reviewを呼び出す(書き出し選択ステップなし)
│ └── ReviewPreview.vue ← 新規: フル口コミ表示 + 編集 + 統合Copy & Google投稿ボタン
└── keywords/
└── ReviewKeywordSettings.vue ← 新規: reviewキーワード設定画面
config/
└── anthropic.php ← 新規: Anthropic API + review_generator設定AnthropicApiService — Claude API呼び出し:
// app/Services/Mappy/AnthropicApiService.php
class AnthropicApiService
{
/**
* @param string $model モデルID (sonnet/haiku)
* @param array $messages [['role' => 'user', 'content' => '...']]
* @param float $temperature
* @param int $maxTokens
* @param string $system System prompt (Anthropic APIはmessagesとは別に送信が必要)
*/
public function call(string $model, array $messages, float $temperature = 0.8, int $maxTokens = 1024, string $system = ''): string
{
$payload = [
'model' => $model,
'max_tokens' => $maxTokens,
'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 AnthropicApiException($response->status(), $response->body());
}
return $response->json('content.0.text');
}
}Anthropic APIフォーマット
System promptは system フィールドで別途送信する必要があり、messages 内の {'role': 'system'} ではない。 これはOpenAI APIとの違いである。
1つのAPIキー (
ANTHROPIC_API_KEY) を全モデル (Sonnet, Haiku) で共用。
2.4 DB設計 (新規テーブル)
mappy_review_keywords テーブル (新規):
| カラム | 型 | 説明 |
|---|---|---|
| id | bigint (PK) | |
| user_id | varchar | ユーザーID |
| gbp_location_id | bigint | Google Business ProfileのロケーションID |
| keyword | varchar(255) | キーワード内容 |
| sort_order | tinyint | 表示順 |
| created_at / updated_at | timestamp |
キーワード数に制限なし(推奨3-10個)。
mappy_keywords(スクレイピング用、最大8個)とは別テーブルにして相互影響を避ける。
キーワード取得時のフォールバックロジック:
// ReviewGeneratorService.php
private function getKeywords(int $gbpLocationId): array
{
// 1. review専用キーワードを優先
$reviewKeywords = ReviewKeyword::where('gbp_location_id', $gbpLocationId)
->orderBy('sort_order')
->pluck('keyword')
->toArray();
if (!empty($reviewKeywords)) {
return $reviewKeywords;
}
// 2. フォールバック: 既存のスクレイピングキーワードを継承
return Keyword::where('gbp_location_id', $gbpLocationId)
->orderBy('keyword_number')
->pluck('keyword')
->toArray();
}mappy_reviews テーブル:
| カラム | 型 | 説明 |
|---|---|---|
| id | bigint (PK) | |
| questionnaire_id | bigint (FK) | アンケートID |
| answer_set | int | 回答グループ(mappy_questionnaire_answers と紐付け) |
| shop_id | bigint (FK) | 店舗ID |
| review_text | text | 最終口コミ内容 |
| tone | varchar(20) | 使用したtone |
| quality_score | int | 品質スコア |
| edited | boolean | ユーザーが手動編集したかどうか |
| sms_log_id | bigint (FK, nullable) | SMSトラッキングとの紐付け |
| submitted_at | timestamp (nullable) | Googleに投稿した時間 |
| created_at / updated_at | timestamp |
mappy_review_generation_logs テーブル:
| カラム | 型 | 説明 |
|---|---|---|
| id | bigint (PK) | |
| questionnaire_id | bigint (FK) | アンケートID |
| answer_set | int | 回答グループ |
| shop_id | bigint (FK) | 店舗ID(レート制限用) |
| review_text | text | 生成された口コミ内容 |
| tone | varchar(20) | tone |
| quality_score | int | 品質スコア |
| quality_breakdown | json | 品質チェック詳細(フォーマットは下記) |
| retry_count | int | リトライ回数 |
| generation_type | varchar(20) | initial / retry / regenerate / tone_change |
| llm_model | varchar(50) | 使用モデル |
| input_tokens | int | 入力トークン数 |
| output_tokens | int | 出力トークン数 |
| cost_yen | decimal(10,4) | コスト(円) |
| created_at | timestamp |
テーブルprefix
プロジェクトの既存規約に従い、全テーブルに mappy_ prefixを使用。テーブル名: mappy_reviews、mappy_review_generation_logs。
quality_breakdown JSONフォーマット:
2段階(rule-based + LLM)を明確に分離してデバッグしやすくする:
{
"rule_based": {
"char_count": { "score": 20, "detail": "280文字。範囲内。" },
"keywords": { "score": 10, "detail": "2/3 keyword found" }
},
"llm": {
"naturalness": { "score": 25, "detail": "概ね自然。" },
"inappropriate": { "score": 20, "detail": "問題なし。" },
"topic_reflection": { "score": 12, "detail": "指定項目(接客)が中心に反映されている。" },
"feedback": "「まず」の接続詞が目立つ"
},
"total": 87,
"passed": true
}rule-basedで早期失敗した場合(LLMスキップ):
{
"rule_based": {
"char_count": { "score": 0, "detail": "52文字。範囲外。" },
"keywords": { "score": 5, "detail": "1/3 keyword found" }
},
"llm": null,
"total": 5,
"passed": false
}2.5 既存APIの変更
POST /guest/questionnaires (saveAnswers) — レスポンスに answer_set を追加
現行APIは 200 OK のみでbodyなし。フロントエンドがgenerate-reviewに渡すための answer_set を追加する必要がある。
Response (追加):
{
"success": true,
"answer_set": 42
}QuestionnaireController@saveAnswers の変更:
// 現在: return response()->json([], 200);
// 変更後:
return response()->json([
'success' => true,
'answer_set' => $answerSet,
], 200);
$answerSetは既存ロジックで計算済み (max(answer_set) + 1)、返すだけでよい。
2.6 新規ルート
// routes/mappy/api.php — Guestルート(ログイン不要)
Route::post('/questionnaires/generate-review', 'Mappy\Guest\AiReviewController@generateReview');
Route::post('/questionnaires/regenerate-review', 'Mappy\Guest\AiReviewController@regenerateReview');
Route::post('/questionnaires/submit-review', 'Mappy\Guest\AiReviewController@submitReview');
// routes/mappy/api.php — 認証済みルート(ログイン必要、店舗管理者向け)
Route::post('/review-keywords', 'Mappy\ReviewKeywordsController@get');
Route::post('/review-keywords/update', 'Mappy\ReviewKeywordsController@update');2.7 エラーハンドリング
| エラーコード | HTTP | 説明 |
|---|---|---|
SURVEY_NOT_FOUND | 404 | アンケートが存在しない |
LOW_RATING | 400 | 評価が低い(閾値 display_google_review_button_at 未満) |
GENERATION_FAILED | 500 | LLM生成失敗(リトライ上限超過) |
LLM_UNAVAILABLE | 503 | Anthropic APIが応答しない |
RATE_LIMIT | 429 | 上限超過(100回/日/店舗) |
INVALID_TONE | 400 | 無効なtone |
2.8 セキュリティ & パフォーマンス
| 項目 | 内容 |
|---|---|
| 認証 | Guestルート(ログイン不要)、ただし有効な questionnaireId が必要 |
| レート制限 | 店舗ごとに100回/日の生成(DBでカウント) |
| ログ | 全リクエストを mappy_review_generation_logs に記録 |
| 初回生成レスポンス | 10秒以内(リトライなし) |
| 最大レスポンス | 30秒以内(リトライあり) |
| APIキー | .env の ANTHROPIC_API_KEY、config/anthropic.php で管理 |
// .env
ANTHROPIC_API_KEY=sk-ant-xxx
// config/anthropic.php
'anthropic' => [
'api_key' => env('ANTHROPIC_API_KEY'),
],レート制限 — DBカウントで制御:
// ReviewGeneratorService.php
private function checkRateLimit(int $shopId): void
{
$todayCount = ReviewGenerationLog::where('shop_id', $shopId)
->whereIn('generation_type', ['initial', 'regenerate', 'tone_change'])
->whereDate('created_at', today())
->count();
if ($todayCount >= config('services.review_generator.daily_limit', 100)) {
throw new RateLimitExceededException();
}
}
generate-review+regenerate-reviewでカウント(Sonnet呼び出しのLLMコスト管理)。
3. Googleレビューポリシー対策
3.1 Googleの検出方法と対策
GoogleはAIレビューを以下の方法で検出する:
| # | 検出方法 | 説明 | システムの対策 |
|---|---|---|---|
| 1 | テキストパターン分析 | 同一店舗で文体/構成の繰り返し | temperature 0.8 + 3 tone + 「毎回異なる」指示 |
| 2 | 投稿時間分析 | 短時間に多数のレビュー | 1フローにつき1件のみ、一括生成しない |
| 3 | IP/デバイス分析 | 同一IP/デバイスから複数レビュー | 顧客が自分のデバイス/Googleアカウントから投稿 |
| 4 | アカウント行動分析 | レビュー投稿のみの新規アカウント | 顧客の既存Googleアカウントを使用 |
| 5 | 自然言語処理 (NLP) | AI特有の文体(構成が完璧すぎる) | Promptで「箇条書き」「過度な称賛」を禁止、「人間らしさ」を要求 |
| 6 | キーワード密度分析 | 不自然なキーワード挿入(SEOスパム) | 「強制しない」「1〜3キーワード」をPromptに指定 |
| 7 | 内容の一貫性 | 実体験に基づかない一般的な内容 | 実際のアンケートデータから生成、具体的な体験を反映 |
3.2 Googleポリシー (2025年時点)
Google Maps コンテンツポリシー の禁止事項:
- 虚偽のコンテンツ: 実体験に基づかないレビュー
- なりすまし: 他人の名義で投稿
- スパム: 同一内容の繰り返し、広告目的の投稿
- 自動生成コンテンツ: ボットやスクリプトによる自動投稿
重要: Googleは「AIによる自動投稿」を禁止しているが、「AIが下書きを作成し、ユーザーが確認・編集してから自分で投稿する」ことは明確に禁止していない。ただしグレーゾーンであるため、以下の対策を必ず実施すること。
3.3 必須対策
| # | 対策 | 実装方法 | 優先度 |
|---|---|---|---|
| 1 | 顧客自身が投稿 | submit後にGoogle Review URLを表示、顧客が自分のアカウントで投稿。APIによる自動投稿はしない | 必須 |
| 2 | 編集を促す | プレビュー画面に「投稿前にご自身の言葉で編集してください」を目立つように表示 | 必須 |
| 3 | 多様な文体を生成 | temperature 0.8、3 tone、Promptに「毎回異なる構成で」 | 必須 |
| 4 | 自然なキーワード挿入 | Promptに「強制しない」「1〜3キーワード」 | 必須 |
| 5 | 1件ずつ生成 | 一括生成機能は作らない。1フローにつき1件 | 必須 |
| 6 | 投稿間隔の推奨 | UIに「1日1件程度の投稿を推奨」と表示 | 推奨 |
| 7 | 品質チェックでAI検出基準 | quality_checkで「AI感のある表現」を減点 | 必須 |
| 8 | ログ記録 | 全ての生成/投稿を mappy_review_generation_logs に記録 | 必須 |
3.4 UI上の警告表示 (必須)
口コミ画面(プレビュー & 編集):
⚠ この文章はお客様のアンケート回答をもとにAIが作成した下書きです。
投稿される口コミはお客様ご自身の感想として公開されます。
実際のご体験に合っているかご確認のうえ、必要に応じて編集してからご投稿ください。Google Review投稿ボタン押下後:
口コミの下書きを保存しました。
下記のリンクからGoogle Mapsを開き、ご自身のアカウントで投稿してください。
[Google Mapsで口コミを書く →]