Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

Gemini 2.0 API vs Claude 3.5 Sonnet: Build AI Chatbot Thông Minh Cho Website Việt

Ếch Trendy
Ếch Trendy

5 tháng trước · 12 phút đọc

Chọn model nào để không hối hận sau 3 tháng?

Mình từng deploy một chatbot cho e-commerce với model "nghe có vẻ tốt" rồi 2 tháng sau phải refactor toàn bộ vì cost vượt budget gấp đôi. Bài học đắt giá đó khiến mình bắt đầu so sánh kỹ hơn trước khi commit vào bất kỳ API nào.

Gemini 2.0 API và Claude 3.5 Sonnet hiện là 2 lựa chọn phổ biến nhất cho việc build chatbot tích hợp vào website Việt. Cả hai đều mạnh, nhưng trade-off rất khác nhau - context window, pricing, vision, function calling đều cần xem xét kỹ trước khi chọn stack.

Bài này mình sẽ đi thẳng vào so sánh kỹ thuật, sau đó hướng dẫn implement RAG pattern với Pinecone, streaming responses trong React, và cuối cùng là cách A/B test UX cho người dùng Việt.

343645 Hai API, hai triết lý thiết kế - chọn sai tốn cả tháng refactor


So sánh kỹ thuật: con số thực tế

Context window

Gemini 2.0 Flash cung cấp context window 1M tokens (Google AI for Developers), đây là lợi thế rõ ràng khi xử lý tài liệu dài hoặc lịch sử hội thoại dày. Claude 3.5 Sonnet cung cấp 200k tokens (Claude API Docs) - đủ dùng cho hầu hết use case chatbot thông thường, nhưng nếu bạn build chatbot hỗ trợ đọc toàn bộ hợp đồng hay báo cáo tài chính dài thì Gemini chiếm ưu thế.

Thực tế khi build chatbot cho website bán hàng Việt: lịch sử chat 50 tin nhắn + product catalog 200 sản phẩm chỉ chiếm khoảng 30k tokens. Cả hai model đều handle tốt. Context window lớn chỉ thực sự cần thiết khi làm document Q&A hoặc legal chatbot.

Pricing per token

Đây là con số bạn cần xem kỹ trước khi deploy production:

Model Input (per 1M tokens) Output (per 1M tokens)
Gemini 2.0 Flash $0.1 (PapersGPT) $0.40 (artificialanalysis.ai)
Claude 3.5 Sonnet $3 (Gradually AI) $15 (getaiperks.com)

Với traffic trung bình 1000 tin nhắn/ngày, mỗi tin ~500 tokens input + 300 tokens output, chi phí hàng tháng chênh lệch đáng kể. Mình sẽ tính cụ thể ở phần sau.

Vision capabilities

Gemini 2.0 xử lý ảnh, video, audio trong cùng một request. Đây là native multimodal thật sự - không phải bolt-on. Bạn có thể gửi ảnh sản phẩm và hỏi chatbot phân tích chất lượng, so sánh với mô tả.

Claude 3.5 Sonnet cũng hỗ trợ vision nhưng chủ yếu tối ưu cho image + text. Không xử lý video hay audio trực tiếp. Bù lại, khả năng phân tích ảnh chi tiết và trả lời bằng tiếng Việt tự nhiên hơn trong nhiều test mình chạy.

343646 Pricing quyết định sống còn của dự án - tính kỹ trước khi commit

Function calling

Cả hai đều hỗ trợ function calling (tool use), nhưng Claude có ưu thế về độ chính xác khi chọn tool đúng. Trong test với 20 function định nghĩa, Claude hiếm khi gọi nhầm tool, trong khi Gemini đôi khi cần prompt engineering thêm để specify rõ hơn.

Nếu chatbot của bạn cần gọi nhiều API (kiểm tra tồn kho, tra cứu đơn hàng, booking lịch), Claude sẽ ít gây lỗi hơn khi production.


Implement RAG pattern với Pinecone

RAG (Retrieval-Augmented Generation) là pattern cần thiết khi chatbot cần trả lời dựa trên data riêng của bạn - catalog sản phẩm, FAQ, chính sách công ty. Thay vì nhồi toàn bộ vào context (tốn token), bạn chỉ kéo đúng phần liên quan vào mỗi query.

Flow cơ bản:

  1. Embed documents → lưu vào Pinecone
  2. User hỏi → embed câu hỏi → tìm chunks gần nhất
  3. Ghép chunks vào prompt → gửi cho Gemini/Claude

343647 RAG giúp chatbot biết về data riêng mà không cần fine-tune

Dưới đây là implementation với Node.js backend, tương thích cả Gemini 2.0 và Claude:

// Cài dependencies trước
// npm install @pinecone-database/pinecone @google/generative-ai @anthropic-ai/sdk

import { Pinecone } from '@pinecone-database/pinecone';
import { GoogleGenerativeAI } from '@google/generative-ai';
import Anthropic from '@anthropic-ai/sdk';

// Khởi tạo clients
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

const index = pinecone.index('chatbot-vietnam');

// Embed câu hỏi của user bằng Gemini embedding model
async function embedQuery(text) {
  const model = gemini.getGenerativeModel({ model: 'text-embedding-004' });
  const result = await model.embedContent(text);
  return result.embedding.values;
}

// Tìm context liên quan từ Pinecone
async function retrieveContext(query, topK = 5) {
  const queryEmbedding = await embedQuery(query);
  
  const results = await index.query({
    vector: queryEmbedding,
    topK,
    includeMetadata: true,
  });
  
  // Ghép các chunks lại thành context string
  return results.matches
    .map(match => match.metadata.text)
    .join('\n\n---\n\n');
}

// Gọi Claude với RAG context
async function chatWithClaude(userMessage, context) {
  const systemPrompt = `Bạn là trợ lý hỗ trợ khách hàng của website Việt Nam.
  Trả lời dựa trên thông tin sau:
  
  ${context}
  
  Nếu không tìm thấy thông tin trong tài liệu, hãy nói thật thay vì đoán mò.`;

  const message = await claude.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 1024,
    system: systemPrompt,
    messages: [{ role: 'user', content: userMessage }],
  });

  return message.content[0].text;
}

// Gọi Gemini với RAG context
async function chatWithGemini(userMessage, context) {
  const model = gemini.getGenerativeModel({
    model: 'gemini-2.0-flash-exp',
    systemInstruction: `Bạn là trợ lý hỗ trợ khách hàng. Trả lời dựa trên:
    ${context}`,
  });

  const result = await model.generateContent(userMessage);
  return result.response.text();
}

// Handler chính - có thể switch model dễ dàng
export async function handleChat(userMessage, modelChoice = 'claude') {
  const context = await retrieveContext(userMessage);
  
  if (modelChoice === 'gemini') {
    return chatWithGemini(userMessage, context);
  }
  return chatWithClaude(userMessage, context);
}

Một điểm cần lưu ý: Pinecone miễn phí chỉ cho 1 index. Nếu bạn có nhiều client, cần dùng namespaces hoặc upgrade plan. Tải template đầy đủ với Docker setup ở đây: RAG Chatbot Node.js Template


Streaming responses trong React

Không có gì khiến người dùng Việt bỏ chatbot nhanh hơn là nhìn loading spinner 5-10 giây rồi đùng cái text hiện ra. Streaming - hiển thị text từng chữ như đang gõ - cải thiện perceived performance rõ rệt, dù thời gian xử lý thực tế không đổi.

Cả Gemini 2.0 và Claude đều hỗ trợ server-sent events (SSE) cho streaming. Backend Node.js:

// Backend Express - stream từ Claude
app.post('/api/chat/stream', async (req, res) => {
  const { message, model } = req.body;
  
  // Set headers cho SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const context = await retrieveContext(message);

  if (model === 'claude') {
    // Stream từ Claude
    const stream = await claude.messages.stream({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 1024,
      system: `Trợ lý hỗ trợ khách hàng. Context: ${context}`,
      messages: [{ role: 'user', content: message }],
    });

    for await (const chunk of stream) {
      if (chunk.type === 'content_block_delta') {
        res.write(`data: ${JSON.stringify({ text: chunk.delta.text })}\n\n`);
      }
    }
  } else {
    // Stream từ Gemini
    const geminiModel = gemini.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
    const result = await geminiModel.generateContentStream(
      `Context: ${context}\n\nUser: ${message}`
    );

    for await (const chunk of result.stream) {
      const text = chunk.text();
      if (text) {
        res.write(`data: ${JSON.stringify({ text })}\n\n`);
      }
    }
  }

  res.write('data: [DONE]\n\n');
  res.end();
});

Phía React frontend, mình dùng custom hook để handle streaming:

// useChatStream.js - Custom hook xử lý streaming
import { useState, useCallback } from 'react';

export function useChatStream() {
  const [messages, setMessages] = useState([]);
  const [isStreaming, setIsStreaming] = useState(false);

  const sendMessage = useCallback(async (userMessage, model = 'claude') => {
    // Thêm tin nhắn user vào danh sách
    setMessages(prev => [
      ...prev,
      { role: 'user', content: userMessage },
      { role: 'assistant', content: '', isStreaming: true },
    ]);
    setIsStreaming(true);

    const response = await fetch('/api/chat/stream', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message: userMessage, model }),
    });

    const reader = response.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const lines = decoder.decode(value).split('\n');
      for (const line of lines) {
        if (!line.startsWith('data: ')) continue;
        const data = line.slice(6);
        if (data === '[DONE]') break;

        const { text } = JSON.parse(data);
        // Append text vào tin nhắn cuối
        setMessages(prev => {
          const updated = [...prev];
          updated[updated.length - 1].content += text;
          return updated;
        });
      }
    }

    setIsStreaming(false);
  }, []);

  return { messages, isStreaming, sendMessage };
}

343648 Streaming cải thiện UX rõ rệt - người dùng thấy phản hồi ngay lập tức

Khác biệt nhỏ giữa hai model: Gemini thường stream ra chunks lớn hơn (nhiều chữ mỗi lần), Claude stream ra granular hơn (từng từ). Với người dùng Việt quen đọc nhanh, cả hai đều ổn. Nhưng nếu bạn muốn hiệu ứng "đánh máy" mượt hơn, Claude cho cảm giác tự nhiên hơn một chút.

Nếu bạn chưa quen với React hooks và async patterns, khóa Xây Dựng Website với ReactJS của F8 có phần custom hooks khá chi tiết để xem thêm.


A/B testing UX cho người dùng Việt

Người dùng Việt có một số đặc điểm riêng khi tương tác với chatbot mà nhiều team hay bỏ qua khi copy nguyên UX pattern từ nước ngoài.

Ngắn gọn hơn, thực tế hơn. Câu trả lời dài 5 đoạn văn với chatbot Việt thường bị bỏ qua. Người dùng Việt có xu hướng quét nội dung, ưu tiên bullet points và số liệu cụ thể hơn là diễn giải dài dòng.

Emoji và tone thân thiện. Không phải kiểu formal "Kính gửi quý khách". Một dấu 🙂 hay lời hỏi thăm ngắn đầu tin nhắn giúp conversion rate tăng đáng kể theo nhiều test mình thấy từ các team e-commerce.

Setup A/B test đơn giản

// abTest.js - Phân chia user vào nhóm test
const AB_VARIANTS = {
  A: {
    model: 'claude',
    systemPrompt: 'Trả lời ngắn gọn, dùng bullet points, thêm emoji phù hợp.',
    label: 'Claude - Friendly',
  },
  B: {
    model: 'gemini',
    systemPrompt: 'Trả lời chi tiết, đầy đủ thông tin, tone chuyên nghiệp.',
    label: 'Gemini - Professional',
  },
};

// Gán variant dựa trên userId (stable, không đổi theo session)
export function getVariant(userId) {
  const hash = userId.split('').reduce((acc, char) => {
    return acc + char.charCodeAt(0);
  }, 0);
  return hash % 2 === 0 ? 'A' : 'B';
}

// Track event để so sánh
export function trackChatEvent(userId, eventType, metadata = {}) {
  const variant = getVariant(userId);
  // Gửi lên analytics (GA4, Mixpanel, v.v.)
  analytics.track(eventType, {
    variant,
    model: AB_VARIANTS[variant].model,
    userId,
    ...metadata,
  });
}

// Dùng trong chat handler
export async function handleChatAB(userId, message) {
  const variant = getVariant(userId);
  const config = AB_VARIANTS[variant];
  
  trackChatEvent(userId, 'message_sent', { messageLength: message.length });
  
  const response = await handleChat(message, config.model, config.systemPrompt);
  
  trackChatEvent(userId, 'response_received', {
    responseLength: response.length,
  });
  
  return response;
}

343649 A/B test giúp bạn quyết định dựa trên data, không phải cảm tính

Metrics cần theo dõi

Đừng chỉ nhìn vào "user thích model nào". Các metrics thực tế hơn:

  • Conversation depth: Số tin nhắn trung bình mỗi session. Model nào giữ người dùng chat lâu hơn?
  • Abandonment rate: Bao nhiêu % user drop sau tin đầu tiên?
  • Task completion: User tìm được thông tin họ cần không? (Có thể track qua nút "Hữu ích" dưới mỗi tin)
  • Escalation rate: Bao nhiêu % cần chuyển sang human support?

Từ kinh nghiệm các team e-commerce Việt: Claude thường thắng về conversation depth với sản phẩm phức tạp (điện tử, tài chính), Gemini thắng về speed và cost trên volume lớn.

Tải template A/B testing đầy đủ với dashboard analytics: Tải A/B Testing Dashboard Template


Kết: Nên chọn model nào cho website Việt?

Sau tất cả các so sánh, câu trả lời thực tế là:

Chọn Gemini 2.0 Flash nếu:

  • Budget quan trọng (traffic cao, cần giữ cost thấp)
  • Cần xử lý ảnh/video từ người dùng gửi lên
  • Build document Q&A với file dài (hợp đồng, báo cáo)
  • Prototype nhanh, muốn free tier generous để test

Chọn Claude 3.5 Sonnet nếu:

  • Chatbot cần function calling phức tạp (gọi nhiều API nội bộ)
  • Ưu tiên chất lượng tiếng Việt tự nhiên
  • Cần reasoning chính xác cho sản phẩm tài chính, y tế, pháp lý
  • Team chưa quen prompt engineering nhiều

Một chiến lược hay mình thấy nhiều team dùng: dùng Gemini cho intent classification và RAG retrieval (rẻ, nhanh), dùng Claude cho response generation (chất lượng cao hơn). Hybrid approach này giảm cost khoảng 40-60% so với dùng Claude hoàn toàn.

343650 Không có model nào thắng tuyệt đối - context của project mới quyết định

Gợi ý bước tiếp theo:

  1. Tạo Pinecone free account, embed thử 50-100 documents từ FAQ hiện tại
  2. Implement code streaming mình share ở trên, chạy local với cả hai model
  3. Đo thời gian phản hồi và chất lượng tiếng Việt trong 100 câu hỏi thực từ user
  4. Chọn model, deploy, bật A/B test với 20% traffic trước

Gặp vướng mắc chỗ nào, drop comment bên dưới.