日本語RAGの精度は「エンベディングモデルの選定」「形態素解析ベースのチャンキング」「ハイブリッド検索の日本語設定」の3点で決まる。英語ベースのデフォルト設定のまま日本語を扱うと、精度が大きく落ちる。本記事では日本語特有の課題と対策を実装コード例付きで解説する。

RAGの日本語対応が難しい3つの理由

RAGを日本語環境で構築する際に英語とは異なる固有の課題がある。この3点を理解しておかないと、精度が低いまま原因を特定できずにハマることになる。

第一の課題はトークナイズの非自明性だ。日本語はスペースで単語を区切らないため、「東京都知事」が「東京 / 都 / 知事」「東京都 / 知事」「東 / 京都 / 知事」など複数の解釈が可能だ。英語向けのトークナイザーをそのまま使うと、意味的に不自然な分割が発生する。

第二の課題はエンベディングモデルの日本語性能だ。英語に最適化されたモデルは日本語の意味的類似性の捉え方が不正確になりやすく、検索精度が落ちる。日本語性能を評価したベンチマーク (JMTEB等) での比較が必要だ。

第三の課題はハイブリッド検索のBM25設定だ。BM25はキーワード検索のアルゴリズムだが、デフォルトでは日本語の形態素解析が適切に設定されていない。適切なアナライザーなしでは、日本語キーワード検索の精度が著しく低下する。

日本語エンベディングモデルの選定

日本語RAGで最初に選ぶべき要素がエンベディングモデルだ。日本語性能のベンチマーク (JMTEB) と実用面を合わせて比較する。

モデル 次元数 日本語MTEB API/OSS コスト 特徴
BGE-M3 1024 高 (多言語SOTA級) OSS 無料 (自己ホスト) 多言語対応、Dense/Sparse/ColBERTの3モード
multilingual-e5-large 1024 OSS 無料 (自己ホスト) 軽量で推論速度が速い
OpenAI text-embedding-3-large 3072 API 中 ($0.13/1M tokens) 高精度・API簡単導入
OpenAI text-embedding-3-small 1536 中〜高 API 低 ($0.02/1M tokens) コスパ重視向け
Cohere embed v3 (ja) 1024 API 日本語特化版あり
from sentence_transformers import SentenceTransformer
import numpy as np

# BGE-M3を使った日本語エンベディング例
model = SentenceTransformer("BAAI/bge-m3")

texts = [
    "データ基盤の構築には適切なアーキテクチャ設計が重要です",
    "データウェアハウスの設計にはスタースキーマがよく使われます",
    "今日は天気が良いですね",
]

# エンベディングを生成
embeddings = model.encode(texts, normalize_embeddings=True)

# コサイン類似度を計算 (normalize済みなので内積でOK)
query = "データプラットフォームの設計方法"
query_emb = model.encode([query], normalize_embeddings=True)
scores = np.dot(query_emb, embeddings.T)[0]

for text, score in sorted(zip(texts, scores), key=lambda x: -x[1]):
    print(f"Score: {score:.4f} | {text}")

日本語テキストのチャンキング戦略

日本語のチャンキングは文字数ベースの固定長分割では意味が途中で切れることがある。形態素解析を使った分割が品質向上の鍵となる。

手法 ツール 精度 速度 適した文書タイプ
固定長 (文字数) なし 低 (意味が途中で切れる) 最速 精度不要の概念実証 (PoC) のみ
形態素解析ベース MeCab、SudachiPy 高 (単語境界を正確に認識) ニュース、一般文書
文単位 形態素解析 + 句点検出 説明文、マニュアル
段落・セクション単位 HTMLパーサー、正規表現 最高 (意味の完結性が高い) 構造化文書、Wiki
意味的チャンキング 埋め込み類似度 最高 最低 長文・論文・レポート
from sudachipy import tokenizer
from sudachipy import dictionary

class JapaneseSentenceChunker:
    """日本語テキストを文単位でチャンキングするクラス"""
    
    def __init__(self, max_chunk_size=300, overlap=50):
        self.tokenizer_obj = dictionary.Dictionary().create()
        self.mode = tokenizer.Tokenizer.SplitMode.C
        self.max_chunk_size = max_chunk_size
        self.overlap = overlap
    
    def split_sentences(self, text: str) -> list:
        """句読点で文を分割"""
        sentences = []
        current = ""
        for char in text:
            current += char
            if char in ("。", "!", "?", "!", "?"):
                sentences.append(current.strip())
                current = ""
        if current.strip():
            sentences.append(current.strip())
        return sentences
    
    def chunk(self, text: str) -> list:
        """文をmax_chunk_sizeに収まるよう結合"""
        sentences = self.split_sentences(text)
        chunks = []
        current_chunk = ""
        for sentence in sentences:
            if len(current_chunk) + len(sentence) > self.max_chunk_size:
                if current_chunk:
                    chunks.append(current_chunk)
                current_chunk = sentence
            else:
                current_chunk += sentence
        if current_chunk:
            chunks.append(current_chunk)
        return chunks

chunker = JapaneseSentenceChunker(max_chunk_size=300)
text = "データ基盤の構築は複雑です。適切なアーキテクチャの選定が重要です。まずはビジネス要件を整理することが第一歩となります。"
chunks = chunker.chunk(text)
for i, chunk in enumerate(chunks):
    print(f"Chunk {i}: {chunk}")

ハイブリッド検索の日本語設定

精度の高いRAGにはベクトル検索 (意味的類似性) とBM25 (キーワード完全一致) を組み合わせたハイブリッド検索が有効だ。日本語でBM25を正しく機能させるには形態素解析によるトークナイザーの設定が必須となる。

from rank_bm25 import BM25Okapi
from sudachipy import dictionary, tokenizer

class JapaneseBM25:
    """日本語形態素解析対応のBM25検索"""
    
    def __init__(self):
        self.tokenizer_obj = dictionary.Dictionary().create()
        self.mode = tokenizer.Tokenizer.SplitMode.C
    
    def tokenize(self, text: str) -> list:
        """SudachiPyで形態素解析して単語リストを返す"""
        morphemes = self.tokenizer_obj.tokenize(text, self.mode)
        # 名詞・動詞・形容詞のみ抽出 (ストップワード除去)
        pos_keep = ("名詞", "動詞", "形容詞")
        tokens = [m.dictionary_form() for m in morphemes
                  if m.part_of_speech()[0] in pos_keep]
        return tokens
    
    def build_index(self, documents: list):
        tokenized_docs = [self.tokenize(doc) for doc in documents]
        self.bm25 = BM25Okapi(tokenized_docs)
        self.documents = documents
    
    def search(self, query: str, k: int = 5) -> list:
        query_tokens = self.tokenize(query)
        scores = self.bm25.get_scores(query_tokens)
        ranked = sorted(enumerate(scores), key=lambda x: -x[1])
        return [(self.documents[i], score) for i, score in ranked[:k]]

# ベクトルスコアとBM25スコアのRRF (Reciprocal Rank Fusion) 統合
def reciprocal_rank_fusion(vector_results, bm25_results, k=60):
    scores = {}
    for rank, (doc, _) in enumerate(vector_results):
        scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)
    for rank, (doc, _) in enumerate(bm25_results):
        scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: -x[1])

日本語RAGの精度評価

日本語RAGの精度評価には、Faithfulness (生成内容がソースに忠実か) とRelevancy (回答がクエリと関連しているか) の2指標が基本となる。日本語の評価で注意すべき点がいくつかある。

  • 評価プロンプトを日本語で記述する: 英語の評価プロンプトをそのまま使うと、LLMが日本語コンテキストを正確に評価できないことがある。RAGASなどのフレームワークを使う場合も、日本語用のプロンプトに置き換えることを推奨する。
  • 敬語・文体の違いへの対応: 日本語のコーパスには丁寧語・敬語・口語など文体の差異がある。チャンキング時に文体を統一するか、エンベディングモデルが文体に頑健かを確認する。
  • テスト用Q&Aセットの品質: 自動生成したQ&Aセットで評価する場合、日本語の自然さを人間がサンプルチェックすることが重要だ。

まとめ――日本語RAGは「前処理」が勝負を分ける

  • 日本語RAGの精度はエンベディングモデル・チャンキング・ハイブリッド検索の3要素で決まる
  • エンベディングはBGE-M3またはOpenAI text-embedding-3-largeが日本語の第一選択
  • チャンキングは形態素解析 (SudachiPy) を使った文単位分割が精度・速度のバランスで推奨
  • BM25には日本語形態素解析のトークナイザーを設定しないとキーワード検索が機能しない

よくある質問

Q. RAGで日本語の精度が低い原因は?

主に3つの原因があります。エンベディングモデルの日本語性能不足、チャンキングがトークン分割の都合で意味的に不適切な位置で切れること、BM25検索のトークナイザーが日本語に最適化されていないことです。

Q. 日本語RAGに最適なエンベディングモデルは?

2026年時点ではBGE-M3 (OSS) またはOpenAI text-embedding-3-large (API) が日本語性能と利便性のバランスで推奨されます。コストを重視する場合はmultilingual-e5-large (OSS) も良い選択肢です。

Q. 日本語RAGのチャンキングで注意すべきことは?

文字数ベースの固定長分割だと、日本語の文の途中で切れて意味が崩れるリスクがあります。形態素解析 (MeCab、SudachiPy等) を使った文単位の分割か、段落・セクション単位の分割が推奨されます。