Tự động tạo nội dung đánh giá bằng AI từ kết quả khảo sát
Tổng quan
| Mục | Nội dung |
|---|---|
| Trạng thái | 🟢 Đang tiến hành |
| Issue | #10 |
| Phụ trách | - |
Chức năng AI phân tích kết quả trả lời khảo sát và tự động tạo văn bản đánh giá. Sau khi khách trả lời khảo sát với đánh giá cao, hệ thống tự động tạo bản nháp đánh giá để khách xác nhận, chỉnh sửa và đăng lên Google.
Nội dung đề xuất
Bối cảnh & vấn đề
- Việc tạo đánh giá được thực hiện thủ công, tốn nhiều thời gian và công sức
- Chưa tận dụng hiệu quả tiếng nói của khách hàng thu thập qua khảo sát thành đánh giá
- Chất lượng và giọng văn của đánh giá không đồng nhất
Giải pháp đề xuất
Xây dựng chức năng gọi Claude API trực tiếp từ Laravel để tạo văn bản đánh giá tự nhiên từ dữ liệu khảo sát. Không cần microservice riêng, không cần Python.
Tính năng chính:
- Tự động chuyển đổi kết quả khảo sát thành văn bản dạng đánh giá (không cần khách chọn trước)
- Cài đặt từ khóa theo cửa hàng — Tạo đánh giá có chứa tự nhiên các từ khóa đã cài đặt
- Kiểm tra chất lượng hybrid (rule-based + LLM)
- Tích hợp trực tiếp vào màn hình cảm ơn của khảo sát
- UI chỉnh sửa & xác nhận sau khi tạo
- Luồng đăng đánh giá lên Google sau khi xác nhận
Danh sách chức năng
| # | Tên chức năng | Mô tả | Ưu tiên |
|---|---|---|---|
| 1 | Tạo đánh giá AI | Tự động tạo full review từ mục có số sao cao nhất sau khi gửi khảo sát | Cao |
| 2 | Cài đặt từ khóa cửa hàng | Cài đặt từ khóa theo từng cửa hàng, đưa tự nhiên vào đánh giá | Cao |
| 3 | Kiểm tra chất lượng hybrid | Rule-based + LLM kiểm tra chất lượng văn bản tạo ra | Cao |
| 4 | UI chỉnh sửa & xác nhận | Màn hình xác nhận và chỉnh sửa thủ công đánh giá đã tạo | Cao |
| 5 | Liên kết đăng đánh giá | Luồng đăng trực tiếp đánh giá đã xác nhận lên Google | Trung bình |
Luồng màn hình & Mock
Luồng tổng thể (tích hợp vào màn hình cảm ơn)
Danh sách màn hình
| # | Màn hình | Mới/Hiện có | Mô tả |
|---|---|---|---|
| 1 | Màn hình khảo sát | Hiện có | Không thay đổi |
| 2 | Màn hình cảm ơn (đánh giá thấp) | Hiện có | Không thay đổi |
| 3 | Xem trước & chỉnh sửa | Mới | Đánh giá cao → tự động tạo full review từ mục có sao cao nhất + chỉnh sửa + nút Copy & đăng Google Review |
Điểm chèn trong code hiện tại
File: resources/js/mappy/views/guest/Questionnaire.vue
Logic hiện tại (line 288-292):
// Sau khi submit khảo sát
if (this.getStarRatingAverage() >= this.GOOGLE_REVIEW_BUTTON_DISPLAY_VALUE) {
this.showGoogleReviewLink = true; // Chỉ hiện nút Google Review
}Logic mới:
// Sau khi submit khảo sát
axios.post(url, params).then((response) => {
// Backend trả answer_set để tránh race condition
this.answerSet = response.data.answer_set;
if (this.getStarRatingAverage() >= this.GOOGLE_REVIEW_BUTTON_DISPLAY_VALUE) {
this.currentStep = 'review';
this.generateReview(); // Gọi API kèm answerSet, tạo thẳng full review từ mục sao cao nhất
} else {
this.currentStep = 'thank-you';
}
this.completed = true;
});Race condition
answer_set phải được trả từ saveAnswers response và truyền vào API generate-review. Không được query "answer_set mới nhất" vì có thể khách khác submit cùng lúc.
2 trạng thái màn hình sau submit
| Trạng thái | Điều kiện | Hiển thị |
|---|---|---|
thank-you | Đánh giá thấp | Màn cảm ơn giữ nguyên như cũ |
review | Đánh giá cao | Tự động tạo full review (200-400 ký tự) từ mục có sao cao nhất + chỉnh sửa + nút Copy & đăng Google Review |
Chi tiết tạo review
Gọi API 1 lần duy nhất tới Sonnet để sinh full review (200-400 ký tự) tập trung vào mục đánh giá có số sao cao nhất. Không yêu cầu khách chọn đoạn trước.
Logic chọn mục đánh giá để làm trọng tâm:
- 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 1 mục đã chọn (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 còn lại, câu text, câu có/không) để prompt gọn và tập trung
Chi tiết:
- Tone mặc định: 丁寧(lịch sự)
- Thời gian chờ: 5-10 giây
- Có nút "Đổi giọng văn" → gọi
regenerate_with_tone(tạo lại từ đầu với tone khác) - Nút "Copy & đăng Google Review" gộp 2 hành động: copy nội dung vào clipboard rồi chuyển sang Google Maps để khách dán và đăng
Error handling trên frontend
| Lỗi | Hiển thị |
|---|---|
| Generate fail (3 retry hết) | Thông báo "生成に失敗しました" + nút "もう一度試す" hoặc nút Google Review |
| Rate limit (429) | Thông báo "本日の生成上限に達しました" + nút Google Review |
| Timeout / API error | Fallback về màn cảm ơn thường + nút Google Review (flow cũ) |
Nguyên tắc: Khi AI fail, luôn fallback về nút Google Review để khách vẫn có thể viết review thủ công. Không block flow.
Luồng khảo sát hiện có
QR code → Trả lời khảo sát → Hướng dẫn đánh giá khi điểm cao
店舗に設置されたQRコードをスキャン
Luồng tạo đánh giá AI
Tự động tạo full review từ mục có sao cao nhất → Chỉnh sửa → Copy & đăng
アンケート
ご来店ありがとうございますCài đặt từ khóa cho AI review (màn hình mới)
Tại sao không dùng màn keyword hiện có?
Màn hình /mappy/keywords (KeywordSettings.vue) lưu vào bảng mappy_keywords với tối đa 8 keyword/cửa hàng. Dữ liệu này được sử dụng cho scraping. Nếu thêm keyword thứ 9 trở đi sẽ ảnh hưởng tới logic scraping hoặc phải update lại logic lấy data của scraping. Vì vậy cần tạo bảng riêng và màn hình riêng cho AI review keyword.
Logic kế thừa: Nếu cửa hàng chưa cài đặt keyword riêng cho AI review → tự động sử dụng keyword từ bảng mappy_keywords (màn keyword hiện có) làm default.
| Mục | Nội dung |
|---|---|
| Đường dẫn màn hình | /mappy/review-keywords (MỚI) |
| Component | ReviewKeywordSettings.vue (MỚI) |
| Bảng DB | mappy_review_keywords (MỚI) |
| Fallback | Nếu chưa cài đặt → kế thừa từ mappy_keywords |
| Giới hạn | Không giới hạn số keyword (khuyến nghị 3-10) |
Luồng lấy keyword khi generate review:
1. Query mappy_review_keywords WHERE gbp_location_id = ?
2. Nếu có kết quả → dùng keyword riêng
3. Nếu rỗng → fallback query mappy_keywords WHERE gbp_location_id = ?キーワード管理
変更内容
| 項目 | 現行 | 変更後 |
|---|---|---|
| キーワード上限 | 8件 | 無制限(推奨10件以上) |
| スクレイピング | 8件固定 | 登録数に応じて動的 |
| 表示 | 固定レイアウト | スクロール対応 |
Kiến trúc
Tech stack
| Mục | Công nghệ | Ghi chú |
|---|---|---|
| Backend | Laravel (hiện có) | Không cần service mới, thêm vào codebase hiện tại |
| LLM API | Anthropic API (gọi HTTP trực tiếp) | Dùng Guzzle HTTP client |
| LLM (tạo) | Claude Sonnet 4 | Chất lượng tiếng Nhật tốt, dùng cho tạo đánh giá |
| LLM (kiểm tra) | Claude Haiku 4.5 | Chi phí thấp, dùng cho kiểm tra chất lượng |
| API Key | 1 key duy nhất (dùng chung mọi model) | ANTHROPIC_API_KEY |
Tại sao không cần Python / LangChain?
Flow xử lý của chức năng này chỉ là "gửi prompt → nhận text → kiểm tra JSON" — một chuỗi tuần tự đơn giản với 1 vòng retry. Các tính năng của LangChain/LangGraph (RAG, tool calling, parallel branching, multi-agent) hoàn toàn không được sử dụng. Gọi Anthropic API trực tiếp từ Laravel giúp:
- Giảm dependency: không thêm ~15 packages Python
- Đơn giản deploy: không cần microservice riêng, không cần Docker cho Python
- Dễ debug: không có abstraction layer che giấu API calls
- Tận dụng infra hiện có: dùng chung server Laravel, Guzzle HTTP client có sẵn
Cấu trúc hệ thống
Đặc điểm kiến trúc:
- Không cần microservice riêng — thêm code vào Laravel hiện có
- Gọi Claude API trực tiếp bằng Guzzle HTTP client (có sẵn)
- Deploy và vận hành đơn giản: 1 server, 1 log
Luồng tạo đánh giá
| Bước | Nội dung xử lý |
|---|---|
| Chọn mục có sao cao nhất | Xác định mục đánh giá có số sao cao nhất (tie-break theo thứ tự câu hỏi) + query keyword cửa hàng |
| Tạo đánh giá | Gọi Anthropic API (Sonnet 4) với prompt chỉ chứa 1 mục + keyword + tone (không gửi phần còn lại của khảo sát) |
| Kiểm tra chất lượng | Rule-based trước → LLM sau (hybrid, xem chi tiết bên dưới) |
| Tạo lại kèm feedback | Khi NG, gửi lại prompt kèm điểm số + gợi ý cải thiện (tối đa 3 lần) |
Kiểm tra chất lượng (Hybrid)
Thay vì gọi LLM cho toàn bộ 5 tiêu chí, chia thành 2 bước:
Bước 1: Rule-based (tức thì, miễn phí)
| Tiêu chí | Điểm | Cách kiểm tra |
|---|---|---|
| Số ký tự (200~400) | 20 điểm | mb_strlen() |
| Phản ánh từ khóa (1~3 từ) | 15 điểm | Kiểm tra chuỗi con |
- Nếu rule-based đã fail rõ ràng (ví dụ: chỉ 50 ký tự) → bỏ qua bước LLM, retry ngay
- Tiết kiệm 1 lần gọi LLM trong trường hợp fail hiển nhiên
Bước 2: LLM (chỉ cho các tiêu chí cần hiểu ngữ nghĩa)
| Tiêu chí | Điểm | Cách kiểm tra |
|---|---|---|
| Tự nhiên (không có cảm giác AI) | 30 điểm | Claude Haiku 4.5 |
| Biểu đạt không phù hợp | 20 điểm | Claude Haiku 4.5 |
| Phản ánh đúng trọng tâm (mục đã chọn) | 15 điểm | Claude Haiku 4.5 |
Tiêu chuẩn đạt: Tổng >= 70 điểm → Đạt / Dưới 70 → Tạo lại (tối đa 3 lần)
Cấu trúc prompt
Chi tiết thiết kế prompt xem tại Thiết kế Prompt & API.
Generate full review (tóm tắt):
Đầu vào gửi Sonnet chỉ gồm: tên mục có sao cao nhất + số sao + danh sách từ khóa cửa hàng + tone. Không gửi các câu trả lời khác của khảo sát.
System: Trợ lý tạo đánh giá
- Sinh full review 200~400 ký tự tập trung vào mục duy nhất được cung cấp
- Tone mặc định: 丁寧 (lịch sự)
- Đưa từ khóa cửa hàng vào tự nhiên (không nhồi nhét)
User (ví dụ):
項目: 接客 (★5)
キーワード: カット, カラー, スタイリング
トーン: 丁寧Gọi API từ Laravel (ví dụ)
// 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] ← chỉ 1 mục có sao cao nhất
// Không nhận toàn bộ survey — prompt được gọn, tránh AI bám vào dữ liệu không cần thiết
// 1. Sinh full review từ mục có sao cao nhất (Sonnet 4)
$review = $this->callClaude('claude-sonnet-4-20250514', 0.8, [
['role' => 'user', 'content' => $this->buildGeneratePrompt($topItem, $keywords, $tone)]
]);
// 2. Kiểm tra chất lượng hybrid
for ($attempt = 0; $attempt < 3; $attempt++) {
$check = $this->qualityCheck($review, $topItem, $keywords);
if ($check['passed']) {
return ['review_text' => $review, 'quality_score' => $check['score']];
}
// Retry với feedback
$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;
// Bước 1: Rule-based (tức thì)
$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;
// Fail sớm nếu rule-based rõ ràng NG
if ($score < 15) {
return ['passed' => false, 'score' => $score, 'feedback' => 'Số ký tự hoặc từ khóa không đạt'];
}
// Bước 2: LLM check (Haiku 4.5) — chỉ truyền tên mục + số sao để check có phản ánh đúng trọng tâm
$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 key duy nhất cho tất cả model
$payload = [
'model' => $model,
'max_tokens' => 1024,
'temperature' => $temperature,
'messages' => $messages,
];
// Anthropic API: system prompt phải gửi 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 \RuntimeException("Anthropic API error: {$response->status()}");
}
return $response->json('content.0.text');
}
}So sánh LLM dự kiến & chi phí
| Model | Input | Output | Chi phí/件 | Chất lượng tiếng Nhật | Khuyến nghị |
|---|---|---|---|---|---|
| Claude Sonnet 4 | $3.00/1M tokens | $15.00/1M tokens | ~¥0.5 | ◎ | Khuyến nghị |
| Claude Haiku 4.5 | $0.80/1M tokens | $4.00/1M tokens | ~¥0.1 | ○ | Ưu tiên chi phí |
| GPT-4o | $2.50/1M tokens | $10.00/1M tokens | ~¥0.4 | ◎ | Thay thế |
| GPT-4o mini | $0.15/1M tokens | $0.60/1M tokens | ~¥0.03 | △ | Không khuyến nghị |
| Gemini 2.5 Flash | $0.15/1M tokens | $0.60/1M tokens | ~¥0.03 | ○ | Ưu tiên chi phí |
Điều kiện tính chi phí
- Mỗi件: đầu vào ~150 tokens (tên mục + số sao + keyword + tone) + đầu ra ~400 tokens (văn bản đánh giá)
- Tạo lại kiểm tra chất lượng: trung bình 1.2 lần/件
- Trường hợp tạo 100件/tháng: Claude Sonnet 4 ~¥50/tháng (giảm so với trước nhờ prompt gọn)
Cấu hình khuyến nghị
| Mục đích | Model | Lý do |
|---|---|---|
| Tạo đánh giá (production) | Claude Sonnet 4 | Chất lượng tiếng Nhật ◎, chi phí hợp lý |
| Kiểm tra chất lượng | Claude Haiku 4.5 | Ưu tiên chi phí cho xử lý phán định |
| Phát triển & kiểm thử | Claude Haiku 4.5 | Nhanh, chi phí thấp cho kiểm thử lặp lại |
Ước tính công (dựa trên AI)
Cơ cấu nhân sự
| Vai trò | Số người | Nội dung phụ trách |
|---|---|---|
| Thiết kế | 1 người | Xác nhận yêu cầu → Chỉ thị AI tạo tài liệu thiết kế → Review → Chỉ thị sản xuất |
| Sản xuất | 1 người | Chỉ thị AI tạo theo ISSUE → Code review → Kiểm thử → Deploy |
Chi tiết công (giảm nhờ bỏ Python)
| # | Hạng mục công việc | Retry AI | Review | Công (người-ngày) | Phụ trách |
|---|---|---|---|---|---|
| 1 | Xác nhận yêu cầu & tạo tài liệu thiết kế | 2 lần | 0.5 ngày/lần | 1.0 | Thiết kế |
| 2 | Thiết kế prompt & thiết kế API | 2 lần | 0.5 ngày/lần | 1.0 | Thiết kế |
| 3 | Triển khai Service tạo AI (Laravel) | 2 lần | 0.5 ngày/lần | 1.0 | Sản xuất |
| 4 | Triển khai UI màn tạo review (Vue.js) | 2 lần | 0.5 ngày/lần | 1.0 | Sản xuất |
| 5 | Triển khai nút Copy & đăng Google Review | 1 lần | 0.5 ngày/lần | 0.5 | Sản xuất |
| 6 | Kiểm thử tích hợp & điều chỉnh chất lượng | 2 lần | 0.5 ngày/lần | 1.0 | Sản xuất |
| 7 | Deploy & xác nhận hoạt động | 1 lần | 0.5 ngày/lần | 0.5 | Sản xuất |
| Tổng cộng | 6.0 |
Điều kiện tiên quyết & ràng buộc
- Tài khoản Anthropic API và
ANTHROPIC_API_KEYđã được cấu hình - Dữ liệu khảo sát có thể query từ DB (
mappy_questionnaire_answers) - Server Laravel có thể gọi HTTPS ra ngoài (đến
api.anthropic.com) - Tiền đề là cả thiết kế và sản xuất đều tận dụng AI (Claude Code/Cursor, v.v.)
Lịch trình
| タスク | 担当 | 日数 | 4/3 | 4/4 | 4/5 | 4/6 | 4/7 | 4/8 | 4/9 | 4/10 | 4/11 | 4/12 | 4/13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 金 | 土 | 日 | 月 | 火 | 水 | 木 | 金 | 土 | 日 | 月 | |||
| Xác nhận yêu cầu & tài liệu thiết kế | Thiết kế | 1d | |||||||||||
| Thiết kế prompt & API | Thiết kế | 1d | |||||||||||
| Review & chỉnh sửa thiết kế | Thiết kế | 1d | |||||||||||
| Chỉ thị sản xuất & tạo ISSUE | Thiết kế | 1d | |||||||||||
| Triển khai Service tạo AI (Laravel) | Sản xuất | 1d | |||||||||||
| Triển khai UI màn tạo review | Sản xuất | 1d | |||||||||||
| Triển khai nút Copy & đăng Google | Sản xuất | 1d | |||||||||||
| Kiểm thử tích hợp & điều chỉnh | Sản xuất | 1d | |||||||||||
| Deploy & xác nhận hoạt động | Sản xuất | 1d |