LLM・RAG

LangChain BM25Retrieverの使い方|インストール・日本語対応・ハイブリッド検索

LangChain BM25Retrieverの使い方|インストール・日本語対応・ハイブリッド検索

BM25Retrieverは、LangChainでキーワード一致に基づく文書検索を担うRetrieverです。埋め込みベクトルを作らずに動くため、型番・エラーコード・専門用語のように「その語がそのまま含まれる文書」を探す用途に強く、RAGの検索段でベクトル検索と併用されます。この記事では、正しいimportと最小実装から、k1・bのチューニング、日本語文書のためのpreprocess_func、EnsembleRetrieverによるハイブリッド検索までを実装コード付きで解説します。旧来よく混同される「rank_bm25を直接使うコード」ではなく、LangChainのBM25Retrieverそのものの使い方に絞ります。

まとめ:BM25Retriever実装の要点

  • importは from langchain_community.retrievers import BM25Retriever。内部で rank_bm25 を使うため pip install rank_bm25 が必須。
  • コーパスは from_texts(文字列)か from_documents(Documentオブジェクト)で渡し、検索は .invoke(query) で実行する。
  • スコアの挙動は bm25_params の k1・b で調整する。
  • 日本語は既定の空白分割では機能しないため、形態素解析した preprocess_func を渡す。
  • 意味の言い換えに弱いので、ベクトル検索と EnsembleRetriever で組み合わせると精度が上がる。

以下、それぞれを実装コードとともに順に見ていきます。

BM25Retrieverとは|キーワード検索を担うLangChainのRetriever

BM25Retrieverは、TF-IDFを発展させたランキング関数BM25で、クエリと各文書の一致度をスコア化して上位を返すRetrieverです。ベクトル検索が埋め込みモデルで「意味の近さ」を測るのに対し、BM25は「語がどれだけ含まれるか」を測ります。埋め込みAPIの呼び出しもGPUも不要で、少量のコーパスならその場でインデックスを構築できる手軽さが利点です。

BM25のスコアリングとTF-IDFとの違い(k1・b)

BM25がTF-IDFと違うのは、単語頻度の「飽和」と文書長の「補正」を持つ点です。同じ語が何度出ても頻度スコアはk1で頭打ちになり、長い文書が有利になりすぎないようbで正規化します。rank_bm25のBM25Okapi既定値はk1=1.5、b=0.75で、この2値がBM25Retrieverの検索結果を左右する主要パラメータです。

ベクトル検索ではなくBM25Retrieverを選ぶ場面

BM25Retrieverが向くのは、ユーザーが正確な語で検索する場面です。製品コード「ABC-1200」、エラー文言「Connection refused」、条文番号や社内固有名詞などは、意味が近いだけの文書より完全一致の文書が正解になります。逆に「解約したい」を「退会」と言い換えるような表記ゆれ・同義語には弱く、ここだけをBM25で解こうとすると取りこぼします。この弱点を埋めるのが後述のハイブリッド検索です。

BM25Retrieverの導入と最小実装(インストール〜invoke)

BM25RetrieverはLangChainのcommunityパッケージに含まれ、実体の計算はrank_bm25が担います。まず両方をインストールします。

pip install langchain-community rank_bm25

正しいimportとfrom_textsでの最小検索

importはlangchain_community.retrieversから行います。文字列のリストをそのまま渡すならfrom_textsが最短です。返す件数はk(既定4)で指定します。

from langchain_community.retrievers import BM25Retriever

texts = [
    "BM25はキーワードの一致度で文書をスコアリングする",
    "ベクトル検索は意味の近さで文書を探す",
    "ハイブリッド検索は両者を組み合わせる",
]

retriever = BM25Retriever.from_texts(texts, k=2)
docs = retriever.invoke("キーワード 一致")
for d in docs:
    print(d.page_content)

クエリ実行は.invoke()を使います。以前の.get_relevant_documents()は非推奨で、新しいコードでは.invoke()に統一します。

from_documentsによるメタデータ付き文書の検索

出典やIDを結果に残したいときは、Documentにメタデータを持たせてfrom_documentsで渡します。RAGで「どのFAQから引いたか」を追跡する用途で使います。

from langchain_community.retrievers import BM25Retriever
from langchain_core.documents import Document

docs = [
    Document(page_content="請求書の再発行はマイページから", metadata={"source": "faq-01"}),
    Document(page_content="パスワードの再設定手順", metadata={"source": "faq-02"}),
]

retriever = BM25Retriever.from_documents(docs, k=3)
results = retriever.invoke("請求書 再発行")
print(results[0].metadata)

k1・bとbm25_paramsによる検索精度の調整

k1とbは、from_texts/from_documentsのbm25_paramsにまとめて渡します。

retriever = BM25Retriever.from_texts(
    texts,
    bm25_params={"k1": 1.2, "b": 0.75},
    k=3,
)

調整の指針は明確です。k1を上げると同じ語が繰り返し出る文書ほど強く優遇され、下げると1回出れば十分という評価に近づきます。bは1.0に近づけるほど長い文書へのペナルティが強くなり、0に近づけると文書長を無視します。短いFAQやチャットログのように長さが揃ったコーパスではbを小さめ、記事とスニペットが混在するコーパスでは既定の0.75付近から始めて、実クエリで上位の妥当性を見ながら詰めるのが実務的です。

BM25Retrieverでの日本語文書検索(preprocess_func)

BM25Retrieverの既定の前処理は「空白で区切る」だけです。単語間に空白のない日本語では文全体が1トークンになり、ほぼ検索が効きません。日本語では形態素解析した結果を返すpreprocess_func(Callable[[str], List[str]])を渡します。ここではJanomeを使います。

from janome.tokenizer import Tokenizer
from langchain_community.retrievers import BM25Retriever

tokenizer = Tokenizer()

def preprocess_func(text):
    return [token.surface for token in tokenizer.tokenize(text)]

texts = ["請求書の再発行はマイページから", "パスワードの再設定手順"]
retriever = BM25Retriever.from_texts(texts, preprocess_func=preprocess_func, k=2)
docs = retriever.invoke("請求書 再発行")

MeCabやSudachiでも同様に、トークンの表層形(surface)のリストを返せば置き換えられます。精度を上げるなら、助詞・記号を落とす、表記を正規化する(全角半角・カタカナ長音の統一)といった処理をpreprocess_func内に足します。なお、この関数は後述の永続化でpickle化されうるため、lambdaではなくモジュール直下のdefで定義しておくのが安全です。

ベクトル検索と組み合わせるハイブリッド検索(EnsembleRetriever)

キーワード一致のBM25と、意味の近さで探すベクトル検索は得意分野が逆で、両方を走らせて結果を統合すると取りこぼしが減ります。LangChainではEnsembleRetrieverが複数のRetrieverの結果をReciprocal Rank Fusion(RRF)でまとめます。RRFは各Retrieverでの順位の逆数を足し合わせて再ランキングする方式で、スコアの尺度が違うBM25とベクトル検索を公平に混ぜられます。

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

bm25 = BM25Retriever.from_texts(texts, preprocess_func=preprocess_func, k=4)
vector = FAISS.from_texts(texts, OpenAIEmbeddings()).as_retriever(search_kwargs={"k": 4})

ensemble = EnsembleRetriever(retrievers=[bm25, vector], weights=[0.5, 0.5])
docs = ensemble.invoke("解約したい")

weightsは各Retrieverの寄与度で、合計は自由です。ユーザーが型番やエラーメッセージで検索しがちなら[0.6, 0.4]とBM25側を厚く、自然文の質問が中心なら[0.4, 0.6]とベクトル側を厚くします。まずは[0.5, 0.5]から始め、実クエリで外した検索を見て寄せるのが定石です。意図ごとに検索戦略を切り替える上位設計はOmniRAGの使い分けパターンが参考になります。なお、ベクトル側の埋め込みはOpenAIEmbeddingsに限らず任意(ローカルならHuggingFaceEmbeddings等)に置き換えられます。

BM25Retrieverの運用上の落とし穴(永続化・API・import)

実装で事故になりやすいのは、性能よりも運用面です。

  • インメモリで永続化されない:BM25Retrieverはコーパスとインデックスをメモリ上に保持し、ディスクへ自動保存しません。プロセスを再起動するたびにfrom_textsで作り直すか、Retrieverをpickleで保存・復元します。大規模コーパスを毎回構築するとメモリと初期化時間が増えるため、コーパス更新頻度に応じて再構築か永続化かを決めます。
  • get_relevant_documentsは非推奨:古い記事のコードは.get_relevant_documents()を使いますが、現行は.invoke()です。移行時に置き換えます。
  • importパスの変更に注意:BM25Retrieverはlangchain_community、EnsembleRetrieverはlangchainに分かれています。バージョン間でパッケージ構成が動いた経緯はLangChain v1.0の変更点を確認すると把握しやすいです。引数の最終仕様はBM25Retrieverの公式APIリファレンスで確認できます。
  • preprocess_funcはmodule直下で定義:pickle化する場合、lambdaやローカル関数は保存できません。

よくある質問

Q. BM25RetrieverとTFIDFRetrieverはどちらを使うべきですか?
文書長の補正と頻度飽和を持つBM25の方が実務での精度が安定しやすく、基本はBM25Retrieverで問題ありません。どちらもLangChainのcommunityに含まれ、使い方はほぼ同じです。

Q. from_textsとfrom_documentsの違いは何ですか?
渡すデータ型の違いです。文字列のリストならfrom_texts、メタデータ付きのDocumentを扱うならfrom_documentsを使います。出典やIDを結果に残したいなら後者です。

Q. 日本語で検索結果が出ないのはなぜですか?
既定の前処理が空白分割のためです。日本語は形態素解析したpreprocess_func(Janome・MeCab・Sudachi等)を渡さないと、文全体が1トークンになり一致しません。

Q. get_relevant_documentsは使えなくなりましたか?
非推奨です。動く場合もありますが、新しいコードは.invoke(query)に統一してください。

Q. BM25Retrieverはディスクに保存できますか?
自動保存はされません。プロセスごとにfrom_textsで再構築するか、Retrieverをpickleで保存・復元します。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.07 テックブログ ファインズの不正アクセスと153万件の予約者情報|予約システムを使う店舗の報告と通知の手順
  2. 2026.10.19 テックブログ コミックシーモア不正アクセスで会員情報流出:再開時期・パスワード再設定と運営側のログ点検
  3. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更・退会の手順
  4. 2026.10.16 テックブログ バーガーキング情報漏洩どうしたらいい?約1,118万件の対象確認・補償・退会とメールの真偽
  5. 2025.09.03 コラム Google AIモードの履歴の消し方と残る理由|いつまで残るか・使い方・オフにする方法【2026年10月】

RELATED POSTS 関連記事

目次