Thiết kế Prompt & API — Tự động tạo đánh giá bằng AI
| Hạng mục | Nội dung |
|---|---|
| Trạng thái | 🟡 Đang thảo luận |
| Liên quan | #10 Tự động tạo đánh giá AI |
1. Thiết kế Prompt
1.1 Danh sách Prompt
| # | Tên Prompt | Mục đích | LLM sử dụng | Gọi từ |
|---|---|---|---|---|
| 1 | generate_review | Tạo full review từ mục có sao cao nhất (1 lần gọi) | Claude Sonnet 4 | ReviewGeneratorService |
| 2 | quality_check | Kiểm tra chất lượng (phần LLM) | Claude Haiku 4.5 | ReviewGeneratorService |
| 3 | regenerate_with_feedback | Tái tạo khi NG kèm feedback | Claude Sonnet 4 | ReviewGeneratorService |
| 4 | regenerate_with_tone | Tái tạo khi đổi tone | Claude Sonnet 4 | AiReviewController@regenerateReview |
1.2 Prompt tạo đánh giá (generate_review)
Tạo full review 200-400 ký tự từ duy nhất mục có số sao cao nhất. Gọi 1 lần duy nhất, không có bước preview.
Logic chọn mục đánh giá (xử lý ở backend trước khi gọi LLM):
- Lấy mục có số sao cao nhất trong các câu hỏi sao
- Nếu có nhiều mục cùng số sao cao nhất → ưu tiên theo thứ tự câu hỏi gốc (VD: 接客 → 清潔感 → 満足度, nên 3 mục cùng ★5 thì chọn 接客)
- Chỉ gửi duy nhất tên mục + số sao vào prompt. Không gửi các câu trả lời khác của khảo sát (các mục sao còn lại, câu text, câu có/không)
Biến đầu vào:
| Biến | Kiểu | Mô tả | Ví dụ |
|---|---|---|---|
top_item | object | Mục có sao cao nhất (sau tie-break) | { name: "接客", rating: 5 } |
tone | string | Tone đánh giá | Mặc định 丁寧 |
shop_name | string | Tên cửa hàng | 銀座ヘアサロンA |
store_keywords | string | Keyword cửa hàng (phân cách bằng dấu phẩy) | 丁寧な接客, 駅近, 清潔感 |
Không có
survey_answers. Không gửi các mục sao khác hay nội dung câu trả lời khác — giúp prompt gọn (~150 tokens input thay vì ~800).
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}Tham số LLM:
| Tham số | Giá trị | Lý do |
|---|---|---|
| model | claude-sonnet-4-20250514 | Chất lượng tiếng Nhật tốt |
| temperature | 0.8 | Tạo văn phong đa dạng |
| max_tokens | 800 | 400 ký tự (~600 tokens) + dư |
Ví dụ đầu ra:
Mục được chọn: 接客 (★5)
初めて銀座ヘアサロンAを利用いたしました。スタッフの方がとても親切で、カウンセリングも丁寧に対応してくださり、初めてでも安心して任せられる雰囲気でした。髪の悩みをしっかり聞いた上で、似合うスタイルを提案してくださり、仕上がりも想像以上でした。駅から近く通いやすい立地も魅力で、次回もぜひお願いしたいと思います。
※ Toàn bộ review tập trung vào 1 mục "接客" — không đề cập tới các mục sao khác trong khảo sát
Response time: 5-10 giây (Sonnet + output ~400 ký tự)
1.4 Kiểm tra chất lượng — Hybrid
Kiểm tra chất lượng chia thành 2 bước (hybrid):
Bước 1: Rule-based (tức thì, miễn phí)
| # | Tiêu chí | Điểm | Cách kiểm tra | Fail sớm? |
|---|---|---|---|---|
| 1 | Số ký tự (200〜400) | 20 điểm | mb_strlen() | Dưới 100 hoặc trên 600 → retry ngay |
| 2 | Phản ánh keyword (1〜3 từ) | 15 điểm | str_contains() | — |
- Nếu tổng bước 1 < 15 điểm → bỏ qua bước 2, retry ngay (tiết kiệm 1 lần gọi LLM)
- Nếu tổng bước 1 >= 15 điểm → tiếp tục bước 2
Bước 2: LLM (chỉ cho tiêu chí cần ngữ nghĩa)
| # | Tiêu chí | Điểm | Cách kiểm tra |
|---|---|---|---|
| 3 | Tự nhiên (không có cảm giác AI) | 30 điểm | Claude Haiku 4.5 |
| 4 | Biểu đạt không phù hợp | 20 điểm | Claude Haiku 4.5 |
| 5 | Phản ánh đúng trọng tâm (mục đã chọn) | 15 điểm | Claude Haiku 4.5 |
Model sử dụng: Claude Haiku 4.5 (temperature: 0.1 — để kết quả đánh giá ổn định)
Tiêu chuẩn đạt: Tổng (bước 1 + bước 2) >= 70 điểm → Đạt / Dưới 70 → Tái tạo (tối đa 3 lần)
Prompt cho LLM quality check (bước 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": "改善点(なければ空文字)"}Quality check chỉ nhận tên mục + số sao, không nhận toàn bộ survey answers — đồng nhất với cách prompt generate chỉ tập trung 1 chủ đề.
Định dạng JSON đầu ra:
{
"naturalness": 25,
"inappropriate": 20,
"topic_reflection": 12,
"feedback": "「まず」「次に」の接続詞が目立つ。もう少し自然な流れに。"
}JSON parse safety
LLM có thể trả text thừa trước/sau JSON. Cần extract JSON bằng regex trước khi parse:
// Extract JSON từ response (LLM có thể trả text thừa)
private function parseJsonResponse(string $response): array
{
// Tìm JSON object đầu tiên trong response
if (preg_match('/\{[^{}]*\}/', $response, $matches)) {
$decoded = json_decode($matches[0], true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
}
// JSON parse fail → coi như quality check fail, trigger retry
return [
'naturalness' => 0,
'inappropriate' => 0,
'topic_reflection' => 0,
'feedback' => 'Quality check JSON parse failed, retrying...',
];
}Khi JSON parse fail, không throw exception mà trả điểm 0 → trigger retry tự nhiên trong flow.
1.5 Prompt tái tạo
Khi chất lượng NG (regenerate_with_feedback)
System Prompt: Giống generate_review (mục 1.2)
Human Prompt:
以下の情報から口コミ文を生成してください。
前回の生成は品質チェックで不合格でした。フィードバックを参考に改善してください。
【前回のスコア】{previous_score}/100
【改善フィードバック】{feedback}
【店舗名】{shop_name}
【中心にする項目】{top_item.name}(★{top_item.rating})
【店舗キーワード】{store_keywords}
【トーン】{tone}
※ 前回と異なる書き出し・構成で書いてください。| Tham số | Giá trị | Lý do |
|---|---|---|
| model | claude-sonnet-4-20250514 | Giữ chất lượng |
| temperature | 0.9 | Cao hơn để tạo văn khác lần trước |
| max_tokens | 800 | — |
Khi đổi tone (regenerate_with_tone)
System Prompt: Giống generate_review nhưng {tone} thay đổi
Human Prompt:
以下の情報から口コミ文を生成してください。
前回は「{previous_tone}」トーンで生成しましたが、今回は「{tone}」トーンで新しく生成してください。
【店舗名】{shop_name}
【中心にする項目】{top_item.name}(★{top_item.rating})
【店舗キーワード】{store_keywords}
【トーン】{tone}
※ 前回の文章「{previous_review_snippet}」の言い回しを流用しないでください。| Tham số | Giá trị | Lý do |
|---|---|---|
| model | claude-sonnet-4-20250514 | Giữ chất lượng |
| temperature | 0.8 | Đa dạng nhưng ổn định |
| max_tokens | 800 | — |
previous_review_snippet: 50 ký tự đầu của bản trước, để LLM biết cần tránh lặp lại.
1.6 Giá trị cấu hình tuning
| Cài đặt | Giá trị mặc định | Phạm vi điều chỉnh |
|---|---|---|
| Điểm đạt | 70 | 50〜90 |
| Số lần retry tối đa | 3 | 1〜5 |
| Số ký tự tối thiểu | 200 | 100〜300 |
| Số ký tự tối đa | 400 | 300〜600 |
Quản lý hằng số trong
config/anthropic.php→review_generator. Sau này có thể thay đổi từ màn hình quản lý.
// 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. Thiết kế API (Laravel trực tiếp)
2.1 Cấu trúc hệ thống
Frontend (Vue.js) → Laravel API (hiện có) → Anthropic API (HTTPS)- Frontend → Laravel: Xác thực/quản lý session xử lý bên Laravel hiện có
- Laravel → Anthropic API: Gọi HTTPS trực tiếp bằng Guzzle HTTP client
- Không cần Python FastAPI, không cần service riêng
2.2 Endpoint Laravel API (Guest — không cần đăng nhập)
Các endpoint dưới đây được gọi từ màn hình khảo sát (guest), nằm trong group route guest hiện có.
POST /guest/questionnaires/generate-review — Tạo full review từ mục sao cao nhất (1 lần gọi)
Request:
{
"questionnaireId": 201,
"answerSet": 42,
"hash": "abc123"
}
answerSetlà giá trị trả về từsaveAnswersresponse. Phải truyền giá trị cụ thể để tránh race condition (nhiều khách submit cùng lúc).
Không cần
selected_preview,selectedItemhay survey answers — backend tự chọn mục có sao cao nhất từanswerSet.
Luồng xử lý backend:
- Lấy câu trả lời khảo sát từ
questionnaireId+answerSet - Lọc các câu hỏi dạng đánh giá sao (★), chọn mục có số sao cao nhất
- Tie-break: ưu tiên theo thứ tự câu hỏi gốc (VD: 接客 → 清潔感 → 満足度)
- Lấy
store_keywords:mappy_review_keywords→ fallbackmappy_keywords - Lấy
shop_nametừ cửa hàng - Xây prompt chỉ với
top_item(name + rating) +keywords+tone(mặc định丁寧). Không gửi survey answers khác. - Gọi Anthropic API (Sonnet 4) tạo full review
- Kiểm tra chất lượng hybrid (rule-based → LLM)
- Retry nếu NG (tối đa 3 lần)
- Trả kết quả về frontend
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được trả về để frontend gửi lại ở các request regenerate tiếp theo (tránh query DB lại).
Timeout: 30 giây
POST /guest/questionnaires/regenerate-review — Đổi tone / tái tạo
Request:
{
"questionnaireId": 201,
"answerSet": 42,
"hash": "abc123",
"tone": "カジュアル",
"top_item": { "name": "接客", "rating": 5 },
"previous_review": "初めて銀座ヘアサロンAを...",
"previous_tone": "丁寧"
}Frontend gửi lại
top_itemđã nhận từ responsegenerate-reviewtrước đó, giữ nguyên trọng tâm của review.
Response: Cùng format với generate-review
POST /guest/questionnaires/submit-review — Lưu đánh giá + trả URL đăng Google
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"
}
}Frontend sử dụng
google_review_urlcho nút gộp "Copy & đăng Google Review": copyreview_textvào clipboard rồiwindow.open(google_review_url)trong cùng 1 click.
Việc đăng thực tế do người dùng tự thực hiện bằng tài khoản Google của họ (đối sách Google).
2.3 Cấu trúc code Laravel
app/
├── Http/Controllers/Mappy/
│ ├── Guest/
│ │ ├── QuestionnaireController.php ← Sửa: saveAnswers trả answer_set
│ │ └── AiReviewController.php ← MỚI: 4 endpoint AI review
│ └── ReviewKeywordsController.php ← MỚI: CRUD keyword cho AI review
├── Services/Mappy/
│ ├── ReviewGeneratorService.php ← MỚI: Logic tạo + quality check + fallback keyword
│ └── AnthropicApiService.php ← MỚI: Wrapper gọi Claude API
├── Models/Mappy/
│ ├── Review.php ← MỚI
│ ├── ReviewGenerationLog.php ← MỚI
│ └── ReviewKeyword.php ← MỚI
resources/js/mappy/views/
├── guest/
│ ├── Questionnaire.vue ← Sửa: gọi generate-review sau submit (không có Step chọn preview)
│ └── ReviewPreview.vue ← MỚI: Component hiển thị full review + chỉnh sửa + nút gộp Copy & đăng Google
└── keywords/
└── ReviewKeywordSettings.vue ← MỚI: Màn cài đặt keyword cho review
config/
└── anthropic.php ← MỚI: Anthropic API + review_generator configAnthropicApiService — Gọi Claude API:
// app/Services/Mappy/AnthropicApiService.php
class AnthropicApiService
{
/**
* @param string $model Model ID (sonnet/haiku)
* @param array $messages [['role' => 'user', 'content' => '...']]
* @param float $temperature
* @param int $maxTokens
* @param string $system System prompt (Anthropic API yêu cầu gửi riêng, không trong 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 là field riêng, KHÔNG nằm trong 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 format
System prompt phải gửi qua field system riêng, không phải {'role': 'system'} trong messages. Đây là điểm khác biệt so với OpenAI API.
1 API key (
ANTHROPIC_API_KEY) dùng chung cho tất cả model (Sonnet, Haiku).
2.4 Thiết kế DB (Bảng mới)
Bảng mappy_review_keywords (MỚI):
| Cột | Kiểu | Mô tả |
|---|---|---|
| id | bigint (PK) | |
| user_id | varchar | User ID |
| gbp_location_id | bigint | ID location Google Business Profile |
| keyword | varchar(255) | Nội dung keyword |
| sort_order | tinyint | Thứ tự hiển thị |
| created_at / updated_at | timestamp |
Không giới hạn số keyword (khuyến nghị 3-10). Tách riêng khỏi
mappy_keywords(dùng cho scraping, tối đa 8) để tránh ảnh hưởng lẫn nhau.
Logic fallback khi lấy keyword:
// ReviewGeneratorService.php
private function getKeywords(int $gbpLocationId): array
{
// 1. Ưu tiên keyword riêng cho review
$reviewKeywords = ReviewKeyword::where('gbp_location_id', $gbpLocationId)
->orderBy('sort_order')
->pluck('keyword')
->toArray();
if (!empty($reviewKeywords)) {
return $reviewKeywords;
}
// 2. Fallback: kế thừa từ keyword scraping hiện có
return Keyword::where('gbp_location_id', $gbpLocationId)
->orderBy('keyword_number')
->pluck('keyword')
->toArray();
}Bảng mappy_reviews:
| Cột | Kiểu | Mô tả |
|---|---|---|
| id | bigint (PK) | |
| questionnaire_id | bigint (FK) | ID khảo sát |
| answer_set | int | Nhóm câu trả lời (liên kết mappy_questionnaire_answers) |
| shop_id | bigint (FK) | ID cửa hàng |
| review_text | text | Nội dung đánh giá cuối cùng |
| tone | varchar(20) | Tone sử dụng |
| quality_score | int | Điểm chất lượng |
| edited | boolean | Người dùng đã chỉnh sửa thủ công hay chưa |
| sms_log_id | bigint (FK, nullable) | Liên kết SMS tracking |
| submitted_at | timestamp (nullable) | Thời gian đăng lên Google |
| created_at / updated_at | timestamp |
Bảng mappy_review_generation_logs:
| Cột | Kiểu | Mô tả |
|---|---|---|
| id | bigint (PK) | |
| questionnaire_id | bigint (FK) | ID khảo sát |
| answer_set | int | Nhóm câu trả lời |
| shop_id | bigint (FK) | ID cửa hàng (dùng cho rate limit) |
| review_text | text | Nội dung đánh giá đã tạo |
| tone | varchar(20) | Tone |
| quality_score | int | Điểm chất lượng |
| quality_breakdown | json | Chi tiết kiểm tra chất lượng (format bên dưới) |
| retry_count | int | Số lần retry |
| generation_type | varchar(20) | initial / retry / regenerate / tone_change |
| llm_model | varchar(50) | Model sử dụng |
| input_tokens | int | Số token đầu vào |
| output_tokens | int | Số token đầu ra |
| cost_yen | decimal(10,4) | Chi phí (Yên) |
| created_at | timestamp |
Prefix bảng
Theo convention hiện tại của project, tất cả bảng dùng prefix mappy_. Tên bảng: mappy_reviews, mappy_review_generation_logs.
quality_breakdown JSON format:
Tách rõ 2 bước (rule-based + LLM) để dễ debug:
{
"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
}Khi rule-based fail sớm (bỏ qua 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 Thay đổi API hiện có
POST /guest/questionnaires (saveAnswers) — Thêm answer_set vào response
API hiện tại chỉ trả 200 OK không có body. Cần thêm answer_set để frontend truyền vào generate-review.
Response (thêm mới):
{
"success": true,
"answer_set": 42
}Thay đổi trong QuestionnaireController@saveAnswers:
// Hiện tại: return response()->json([], 200);
// Đổi thành:
return response()->json([
'success' => true,
'answer_set' => $answerSet,
], 200);
$answerSetđã được tính trong logic hiện tại (max(answer_set) + 1), chỉ cần trả về.
2.6 Route mới
// routes/mappy/api.php — Guest routes (không cần login)
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 — Authenticated routes (cần login, dành cho admin cửa hàng)
Route::post('/review-keywords', 'Mappy\ReviewKeywordsController@get');
Route::post('/review-keywords/update', 'Mappy\ReviewKeywordsController@update');2.7 Xử lý lỗi
| Mã lỗi | HTTP | Mô tả |
|---|---|---|
SURVEY_NOT_FOUND | 404 | Khảo sát không tồn tại |
LOW_RATING | 400 | Đánh giá thấp (dưới ngưỡng display_google_review_button_at) |
GENERATION_FAILED | 500 | Tạo LLM thất bại (vượt giới hạn retry) |
LLM_UNAVAILABLE | 503 | Anthropic API không phản hồi |
RATE_LIMIT | 429 | Vượt giới hạn (100 lượt/ngày/cửa hàng) |
INVALID_TONE | 400 | Tone không hợp lệ |
2.8 Bảo mật & Hiệu suất
| Hạng mục | Nội dung |
|---|---|
| Xác thực | Guest route (không cần login), nhưng cần questionnaireId hợp lệ |
| Rate limit | 100 lượt generate/ngày mỗi cửa hàng (count bằng DB) |
| Log | Ghi log tất cả request vào mappy_review_generation_logs |
| Response tạo lần đầu | Trong 10 giây (không retry) |
| Response tối đa | Trong 30 giây (có retry) |
| API Key | ANTHROPIC_API_KEY trong .env, quản lý qua config/anthropic.php |
// .env
ANTHROPIC_API_KEY=sk-ant-xxx
// config/anthropic.php
'anthropic' => [
'api_key' => env('ANTHROPIC_API_KEY'),
],Rate limit — enforce bằng DB count:
// 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();
}
}Count theo
generate-review+regenerate-review(kiểm soát chi phí LLM gọi Sonnet).
3. Đối sách chính sách đánh giá Google
3.1 Phương pháp phát hiện của Google và đối sách
Google phát hiện đánh giá AI bằng các phương pháp sau:
| # | Phương pháp phát hiện | Mô tả | Đối sách của hệ thống |
|---|---|---|---|
| 1 | Phân tích mẫu văn bản | Cùng cửa hàng có văn phong/cấu trúc lặp lại | temperature 0.8 + 3 tone + chỉ thị "mỗi lần khác nhau" |
| 2 | Phân tích thời gian đăng | Nhiều đánh giá trong thời gian ngắn | Mỗi flow chỉ 1 bài, không tạo hàng loạt |
| 3 | Phân tích IP/thiết bị | Nhiều đánh giá từ cùng IP/thiết bị | Khách hàng tự đăng từ thiết bị/tài khoản Google của họ |
| 4 | Phân tích hành vi tài khoản | Tài khoản mới chỉ để đăng đánh giá | Sử dụng tài khoản Google hiện có của khách |
| 5 | Xử lý ngôn ngữ tự nhiên (NLP) | Văn phong đặc trưng AI (cấu trúc quá hoàn hảo) | Prompt cấm "liệt kê", "khen quá mức", yêu cầu "giống con người" |
| 6 | Phân tích mật độ keyword | Chèn keyword không tự nhiên (SEO spam) | "Không ép buộc", "1〜3 keyword" trong prompt |
| 7 | Tính nhất quán nội dung | Nội dung chung chung không dựa trên trải nghiệm thực | Tạo từ dữ liệu khảo sát thực tế, phản ánh trải nghiệm cụ thể |
3.2 Chính sách Google (thời điểm 2025)
Google Maps Content Policy cấm:
- Nội dung giả mạo: Đánh giá không dựa trên trải nghiệm thực
- Mạo danh: Đăng với danh nghĩa người khác
- Spam: Lặp lại cùng nội dung, đăng với mục đích quảng cáo
- Nội dung tự động tạo: Đăng tự động bằng bot hoặc script
Quan trọng: Google cấm "AI tự động đăng", không cấm rõ ràng việc "AI tạo bản nháp, người dùng xác nhận/chỉnh sửa rồi tự đăng". Tuy nhiên đây là vùng xám, nên phải thực hiện các đối sách dưới đây.
3.3 Đối sách bắt buộc
| # | Đối sách | Cách triển khai | Ưu tiên |
|---|---|---|---|
| 1 | Khách hàng tự đăng | Sau submit hiển thị Google Review URL, khách tự đăng bằng tài khoản mình. Không tự động đăng qua API | Bắt buộc |
| 2 | Khuyến khích chỉnh sửa | Hiển thị nổi bật trên màn preview "Hãy chỉnh sửa bằng lời của bạn trước khi đăng" | Bắt buộc |
| 3 | Tạo văn phong đa dạng | temperature 0.8, 3 tone, prompt "mỗi lần cấu trúc khác" | Bắt buộc |
| 4 | Chèn keyword tự nhiên | "Không ép buộc", "1〜3 keyword" trong prompt | Bắt buộc |
| 5 | Tạo từng bài một | Không làm tính năng tạo hàng loạt. Mỗi flow chỉ 1 bài | Bắt buộc |
| 6 | Khuyến nghị khoảng cách đăng | Hiển thị trên UI "Khuyến nghị đăng khoảng 1 bài/ngày" | Khuyến nghị |
| 7 | Tiêu chí phát hiện AI trong quality check | Trừ điểm "biểu đạt nhận ra là AI" trong quality_check | Bắt buộc |
| 8 | Ghi log | Ghi log toàn bộ tạo/đăng vào mappy_review_generation_logs | Bắt buộc |
3.4 Hiển thị cảnh báo trên UI (bắt buộc)
Màn hình review (xem trước & chỉnh sửa):
⚠ この文章はお客様のアンケート回答をもとにAIが作成した下書きです。
投稿される口コミはお客様ご自身の感想として公開されます。
実際のご体験に合っているかご確認のうえ、必要に応じて編集してからご投稿ください。Sau khi nhấn nút đăng Google Review:
Bản nháp đánh giá đã được lưu.
Vui lòng mở Google Maps từ link bên dưới và đăng bằng tài khoản của bạn.
[Viết đánh giá trên Google Maps →]