Quay lại blog
Hướng dẫn triển khai·25 tháng 5, 2026·Đọc 8 phút

Xây dựng chatbot RAG tiếng Nhật bằng Claude — cơ sở kiến thức 5.000 tài liệu trong 30 dòng

Triển khai RAG đầy đủ để tìm kiếm 5.000 tài liệu nội bộ bằng Claude Sonnet 4.6. Khoảng 0,9 yên mỗi truy vấn (sau khi áp dụng prompt caching). Kunavo không cung cấp embedding; chỉ phần đó được tính phí trực tiếp bởi OpenAI. Bao gồm mức tiêu thụ token đặc thù tiếng Nhật, biện pháp chống hallucination và checklist đưa vào production.

Hướng dẫn đầy đủ cách xây dựng chatbot RAG để tìm kiếm cơ sở tri thức nội bộ bằng Claude, triển khai ở quy mô 5.000 tài liệu tiếng Nhật. Công bố cả mã khoảng 30 dòng và ước tính chi phí vận hành hàng tháng.

Tổng quan

  1. Chuyển tài liệu thành vector embedding (text-embedding-3-large)
  2. Chuyển câu hỏi của người dùng thành vector bằng cùng mô hình, tìm kiếm 5 kết quả hàng đầu
  3. Gửi context đã truy xuất đến Claude Sonnet 4.6 để tạo câu trả lời
  4. Giảm 90% chi phí gọi lại bằng prompt caching

1) Nhúng tài liệu (chỉ một lần)

embed.py
# 1) 5,000 件の社内文書を埋め込みベクトル化
from openai import OpenAI

# 埋め込みは Kunavo では提供していません(/v1/embeddings は実装済みの
# ワイヤフォーマットですが、有効なモデルがないため呼ぶと失敗します)。
# 埋め込みは OpenAI に直接、生成は Kunavo に — クライアントは 2 つ持ちます。
embedder = OpenAI(api_key="sk-...")                     # OpenAI 本家
kunavo = OpenAI(
    api_key="sk-kn-...",
    base_url="https://api.kunavo.com/v1",
)

documents = load_documents()  # [{id, text, metadata}, ...]

# バッチ 100 件ずつで埋め込みを呼ぶ
batches = [documents[i:i+100] for i in range(0, len(documents), 100)]
all_vectors = []
for batch in batches:
    resp = embedder.embeddings.create(
        model="text-embedding-3-large",
        input=[d["text"] for d in batch],
    )
    all_vectors.extend(resp.data)

# Postgres + pgvector に保存(または Pinecone, Qdrant など)
save_to_vector_db(documents, all_vectors)

5.000 tài liệu × trung bình 500 token = 2,5 triệu token. Vì Kunavo không cung cấp embedding nên chỉ bước này do OpenAI trực tiếp tính phí—— hãy kiểm tra đơn giá trên trang giá của OpenAI. Tôi không ghi con số ở đây vì nếu ghi giá của thứ mình không bán, sẽ không ai nhận ra khi giá đó trở nên lỗi thời. Chạy quy trình này một lần rồi lưu kết quả vào pgvector.

2) Tìm kiếm và tạo câu trả lời khi truy vấn

query.py
# 2) ユーザー質問 → 関連文書を検索 → Claude に投げて回答
def answer(question: str) -> str:
    # 質問を埋め込み化
    q_embed = embedder.embeddings.create(   # 埋め込みは OpenAI 直
        model="text-embedding-3-large",
        input=[question],
    ).data[0].embedding

    # 上位 5 件を検索
    relevant = vector_search(q_embed, top_k=5)

    # Claude に context + question を渡す
    context = "\n\n---\n\n".join(d["text"] for d in relevant)
    resp = kunavo.chat.completions.create(  # 生成は Kunavo
        model="claude-sonnet-4-6",
        messages=[
            {
                "role": "system",
                "content": (
                    "あなたは社内ナレッジ ベースをもとに正確に答えるアシスタントです。"
                    "提供された context にない情報については「情報がありません」と答えてください。"
                ),
            },
            {"role": "user", "content": f"# Context\n{context}\n\n# 質問\n{question}"},
        ],
        max_tokens=800,
    )
    return resp.choices[0].message.content

Token sử dụng cho mỗi truy vấn: embedding của câu hỏi (~$0,000005) + khoảng 5K token đầu vào của Claude Sonnet 4.6 (context) × $1,20/1M = $0,006 + khoảng 500 token đầu ra × $6,00/1M = $0,003. Khoảng $0,009 mỗi truy vấn (khoảng 1,4 yên).

3) Tiết kiệm thêm bằng prompt caching

Vì cùng một system prompt được gửi mỗi lần, Anthropic prompt caching sẽ phát huy tác dụng:

cache.py
# 3) Cache_control で再呼び出しコストを 90% 削減
# 同じ context(社内ドキュメント)を何度も使うので、Anthropic prompt caching が効く

resp = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[
        {
            "role": "system",
            "content": [{
                "type": "text",
                "text": SYSTEM_PROMPT_AND_GUIDELINES,  # 安定したシステムプロンプト
                "cache_control": {"type": "ephemeral"},
            }],
        },
        {"role": "user", "content": f"# Context\n{context}\n\n# 質問\n{question}"},
    ],
)
# 2回目以降の同じシステムプロンプトはキャッシュヒット → 入力料金の 10%

Nếu system prompt ổn định ở mức 3.000 token và không thay đổi, từ lần thứ hai trở đi phần đó được tính bằng 10% mức phí. Theo đo lường thực tế, chi phí giảm xuống khoảng $0,004 mỗi truy vấn (khoảng 0,6 yên).

Ước tính chi phí vận hành hàng tháng

Mức sử dụngChi phíGhi chú
Embedding ban đầu$0,25Một lần
1.000 truy vấn/ngàyKhoảng $270/thángKhông có caching
1.000 truy vấn/ngàyKhoảng $110/thángCó caching
10.000 truy vấn/ngàyKhoảng $1.100/thángCó caching, quy mô bot Slack

Các điểm cần lưu ý riêng với tiếng Nhật

  • Mức tiêu thụ token: tiếng Nhật đôi khi tiêu thụ lượng token gấp 2-3 lần tiếng Anh (do cách mã hóa kanji và kana). Hãy thu hẹp context từ 5K → 3K và cạnh tranh bằng độ chính xác tìm kiếm
  • Mô hình embedding: text-embedding-3-large hỗ trợ đa ngôn ngữ và độ chính xác tiếng Nhật đủ tốt. Nếu có nhiều thuật ngữ nội bộ, nên duy trì thêm một từ điển từ đồng nghĩa
  • Claude so với Gemini 2.5 Pro: Claude nghiêm ngặt hơn với việc tóm tắt dài và trích dẫn, còn Gemini 2.5 Pro có phạm vi tìm kiếm rộng hơn. Cách làm tiêu chuẩn là bắt đầu với Sonnet 4.6 và nâng cấp lên Opus 4.7 khi cần
  • Chống ảo giác: ghi rõ trong system prompt rằng “nếu không có trong context thì trả lời ‘không có thông tin’”. Nếu vẫn để lọt, hãy giới hạn max_tokens và yêu cầu trả về ID tài liệu nguồn để hiển thị liên kết trích dẫn trong UI

Danh sách kiểm tra trước khi đưa vào production

  • Quy trình cập nhật chỉ mục embedding (tái xây dựng hàng ngày/hàng tuần bằng cron)
  • Fallback khi thất bại (Claude → Gemini 2.5 Flash, v.v., theo hai tầng)
  • Giới hạn tốc độ và trần chi phí (đặt giới hạn như $50 mỗi ngày cho từng khóa tại /app/keys)
  • Nhật ký và giám sát (theo dõi chi phí hàng ngày bằng bảng điều khiển usage)
  • Luật giao dịch thương mại đặc định (dành cho khách hàng Nhật Bản) — /legal/tokutei-shoutorihiki

Để bắt đầu, hãy đăng ký miễn phí. Nạp từ $10 và bắt đầu sử dụng theo hình thức thanh toán theo mức dùng, xem /docs/quickstart hoặc /docs/caching để biết chi tiết.