Skip to content

Tự động tạo nội dung đánh giá bằng AI từ kết quả khảo sát

Tổng quan

MụcNộ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ăngMô tảƯu tiên
1Tạo đánh giá AITự động tạo full review từ mục có số sao cao nhất sau khi gửi khảo sátCao
2Cài đặt từ khóa cửa hàngCài đặt từ khóa theo từng cửa hàng, đưa tự nhiên vào đánh giáCao
3Kiểm tra chất lượng hybridRule-based + LLM kiểm tra chất lượng văn bản tạo raCao
4UI chỉnh sửa & xác nhậnMàn hình xác nhận và chỉnh sửa thủ công đánh giá đã tạoCao
5Liên kết đăng đánh giáLuồng đăng trực tiếp đánh giá đã xác nhận lên GoogleTrung 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ìnhMới/Hiện cóMô tả
1Màn hình khảo sátHiện cóKhông thay đổi
2Màn hình cảm ơn (đánh giá thấp)Hiện cóKhông thay đổi
3Xem trước & chỉnh sửaMớ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):

javascript
// 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:

javascript
// 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ệnHiển thị
thank-youĐánh giá thấpMàn cảm ơn giữ nguyên như cũ
reviewĐánh giá caoTự độ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ỗiHiể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 errorFallback 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

1QR読取
2アンケート
3完了
4クチコミ

店舗に設置されたQRコードをスキャン

https://example.com/questionnaire/abc123

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êngmà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ụcNội dung
Đường dẫn màn hình/mappy/review-keywords (MỚI)
ComponentReviewKeywordSettings.vue (MỚI)
Bảng DBmappy_review_keywords (MỚI)
FallbackNếu chưa cài đặt → kế thừa từ mappy_keywords
Giới hạnKhô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 / 8件
1MEO対策
2美容室 渋谷
3ヘアサロン
4カット 安い
5縮毛矯正
6トリートメント
7ヘッドスパ
8カラー 渋谷
上限に達しています。上限撤廃モードを有効にしてください。

変更内容

項目現行変更後
キーワード上限8件無制限(推奨10件以上)
スクレイピング8件固定登録数に応じて動的
表示固定レイアウトスクロール対応

Kiến trúc

Tech stack

MụcCông nghệGhi chú
BackendLaravel (hiện có)Không cần service mới, thêm vào codebase hiện tại
LLM APIAnthropic API (gọi HTTP trực tiếp)Dùng Guzzle HTTP client
LLM (tạo)Claude Sonnet 4Chất lượng tiếng Nhật tốt, dùng cho tạo đánh giá
LLM (kiểm tra)Claude Haiku 4.5Chi phí thấp, dùng cho kiểm tra chất lượng
API Key1 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ướcNội dung xử lý
Chọn mục có sao cao nhấtXá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ượngRule-based trước → LLM sau (hybrid, xem chi tiết bên dưới)
Tạo lại kèm feedbackKhi 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ểmCách kiểm tra
Số ký tự (200~400)20 điểmmb_strlen()
Phản ánh từ khóa (1~3 từ)15 điểmKiể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ểmCách kiểm tra
Tự nhiên (không có cảm giác AI)30 điểmClaude Haiku 4.5
Biểu đạt không phù hợp20 điểmClaude Haiku 4.5
Phản ánh đúng trọng tâm (mục đã chọn)15 điểmClaude 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ụ)

php
// 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í

ModelInputOutputChi phí/件Chất lượng tiếng NhậtKhuyến nghị
Claude Sonnet 4$3.00/1M tokens$15.00/1M tokens~¥0.5Khuyế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.4Thay thế
GPT-4o mini$0.15/1M tokens$0.60/1M tokens~¥0.03Khô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 đíchModelLý do
Tạo đánh giá (production)Claude Sonnet 4Chất lượng tiếng Nhật ◎, chi phí hợp lý
Kiểm tra chất lượngClaude Haiku 4.5Ưu tiên chi phí cho xử lý phán định
Phát triển & kiểm thửClaude Haiku 4.5Nhanh, 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ườiNội dung phụ trách
Thiết kế1 ngườiXá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ất1 ngườiChỉ 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ệcRetry AIReviewCông (người-ngày)Phụ trách
1Xác nhận yêu cầu & tạo tài liệu thiết kế2 lần0.5 ngày/lần1.0Thiết kế
2Thiết kế prompt & thiết kế API2 lần0.5 ngày/lần1.0Thiết kế
3Triển khai Service tạo AI (Laravel)2 lần0.5 ngày/lần1.0Sản xuất
4Triển khai UI màn tạo review (Vue.js)2 lần0.5 ngày/lần1.0Sản xuất
5Triển khai nút Copy & đăng Google Review1 lần0.5 ngày/lần0.5Sản xuất
6Kiểm thử tích hợp & điều chỉnh chất lượng2 lần0.5 ngày/lần1.0Sản xuất
7Deploy & xác nhận hoạt động1 lần0.5 ngày/lần0.5Sản xuất
Tổng cộng6.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/34/44/54/64/74/84/94/104/114/124/13
Xác nhận yêu cầu & tài liệu thiết kếThiết kế1d
Thiết kế prompt & APIThiết kế1d
Review & chỉnh sửa thiết kếThiết kế1d
Chỉ thị sản xuất & tạo ISSUEThiết kế1d
Triển khai Service tạo AI (Laravel)Sản xuất1d
Triển khai UI màn tạo reviewSản xuất1d
Triển khai nút Copy & đăng GoogleSản xuất1d
Kiểm thử tích hợp & điều chỉnhSản xuất1d
Deploy & xác nhận hoạt độngSản xuất1d