この記事は、実際に使って確かめた記録ではありません。この記事は公式の文書や論文を読んでまとめたものです。

3行でいうと

  • RAGは「文書を小さく切る、数値のベクトルにする、似たものを探す、見つけた断片を質問と一緒にAIに渡す」の流れです。
  • 日本語の文書なら、埋め込みモデルは日本語を扱えるものにします。Chromaの既定のモデルは英語向けです。
  • 保存先は、Chroma、SQLiteの拡張(sqlite-vec)、Postgresの拡張(pgvector)が候補です。
  • 登録時と検索時は、同じ埋め込みモデルを使います。Ollamaの文書もそう書いています。

先に結論

見立てです。個人や小さなチームで、文書が数千〜数万の断片までなら、「Ollama(埋め込み)+ Chroma(保存)」が、部品の少なさで手を付けやすいと思います。すでにPostgresを使っているなら pgvector、アプリがSQLiteで完結しているなら sqlite-vec が自然です。どれも公式の文書で使い方が確認できました。

数字に頼る比較は避けました。検索の精度や速さは、文書と質問で変わるためで、この記事では測っていません。

前提:言葉の整理

全体の流れ

2つの段階があります。

登録(最初に1回、文書が増えたら追加) 1. 文書を、チャンクに切る。 2. 各チャンクを、埋め込みモデルでベクトルにする。 3. ベクトルと、元の文章・出どころ(ファイル名など)を、保存先に入れる。

質問のとき 1. 質問を、同じ埋め込みモデルでベクトルにする。 2. 保存先から、近いチャンクを上位数件取り出す。 3. 取り出した文章を質問に添えて、会話用のAI(LLM)に渡し、答えさせる。

埋め込みモデルと会話用のモデルは、別物です。小さな専用モデルを埋め込みに使い、大きなモデルを答えに使います。

部品1:埋め込みを手元で動かす

Ollama

Ollamaの公式文書(Embeddings)にあるおすすめモデルは、embeddinggemma、qwen3-embedding、all-minilmです。呼び出し口は /api/embed です。公式文書の例です。この記事では、実行していません。

curl -X POST http://localhost:11434/api/embed \
  -H "Content-Type: application/json" \
  -d '{"model": "embeddinggemma", "input": "The quick brown fox jumps over the lazy dog."}'

文書とAPI仕様から分かる点です。

Ollamaのソースコード(mainブランチ、server/sched.go)では、埋め込みモデルは常に並列数1で読み込まれます。大量の文書を登録しながら、同時に質問に答えさせる使い方では、待ちが出る可能性があります。

LM Studio

LM Studioには、2つの道があります。

llama.cpp

llama.appの文書によると、llama serve -hf unsloth/embeddinggemma-300m-GGUF --embedding で埋め込み専用のサーバーを立てられます。/v1/embeddings を呼びます。並べ替え(--rerank)のモデルも同じサーバーで動かせると書かれています。

部品2:埋め込みモデルを選ぶ

読んだモデルカードとOllamaのモデル頁を、表にします。

モデル 規模・次元・入力長 言語 ライセンス Ollamaでの大きさ
EmbeddingGemma(Google) 300M、768次元(512・256・128にも縮められる)、入力2K 100以上の言語で学習 別の利用規約あり 622MB
Qwen3-Embedding 0.6B 0.6B、最大1024次元(32〜1024で指定可)、文脈32k 100以上の言語 Apache 2.0 639MB
BGE-M3 1024次元、入力8192 100以上の言語 MIT 1.2GB
Ruri v3 310m 315M、768次元、入力8192 日本語 Apache 2.0 (確認していません)
nomic-embed-text v1.5 (次元は縮められる)、入力2K カードの言語表示は英語のみ Apache 2.0 274MB
all-MiniLM-L6-v2 小型、Ollama版は512 カードの言語表示は英語 Apache 2.0 46MB
各モデルのカードにある使い方の注意
  • EmbeddingGemma:Matryoshka(入れ子)表現学習で、768次元のベクトルを前から切って短くし、再び正規化すれば、小さい次元でも使えるとカードにあります。Hugging Faceのページは、利用の承諾が必要でした(Googleのモデル頁で内容を確認しました)。
  • Qwen3-Embedding:質問側に1文の指示(Instruct: ...\nQuery:...)を付けるのが前提です。カードによると、指示を付けないと検索の性能が約1〜5%下がります。文書側には付けません。
  • BGE-M3:1つのモデルで、密なベクトル、疎なベクトル(BM25に似た単語の重み)、複数ベクトル(ColBERT方式)の3つの検索ができるとカードにあります。
  • Ruri v3:日本語専用です。質問に「検索クエリ: 」、文書に「検索文書: 」を付ける決まりです。同じカードの表では、310mのJMTEB(日本語の埋め込み評価)平均が77.24です。
  • nomic-embed-text v1.5:文書に search_document:、質問に search_query: を必ず付けると、カードにあります。付け忘れは、精度が落ちる原因になります(見立てです)。

日本語で使うときの判断

見立てです。日本語の文書なら、次の順で考えます。

  1. 日本語と英語が混ざる、または多言語:EmbeddingGemma、Qwen3-Embedding、BGE-M3のように、100以上の言語を対象にするモデルが候補です。カードや公式の説明で、日本語で測った数字があるのは、僕らが読んだ範囲ではRuri v3だけです。
  2. 日本語だけ:Ruri v3が候補です。ただしOllamaの公式頁での配布は確認していません。Hugging Face経由でsentence-transformersから使う形になります。
  3. 英語向けのモデル(nomic、all-MiniLM)は、日本語の文書にはおすすめしません。カードが対象言語を英語としているためです。

Chromaは、埋め込み関数を指定しないと、all-MiniLM-L6-v2 を既定に使う、と文書に書いています。このモデルのカードは、言語を英語としています。Chromaで日本語を扱うときは、必ず、別の埋め込み関数(OllamaやSentence Transformers)を指定してください。

部品3:ベクトルの保存先

保存先 形 公式文書で確認できた点
Chroma Pythonなどのライブラリ。ファイルに保存、またはサーバー PersistentClient(path=...)で保存。既定のパスは .chroma。距離の既定は l2。cosine と ip も選べる
sqlite-vec SQLiteの拡張。1つのファイルで完結 vec0 という仮想テーブルに float[768] のように宣言。READMEは「v1より前なので、互換が崩れる変更に注意」と明記
pgvector Postgresの拡張 近似検索の索引にHNSWとIVFFlat。vector 型の索引は2,000次元まで。ACID、JOINなどPostgresの機能が使える

Chromaを例に

pgvectorの注意

READMEによると、近似索引は、絞り込み条件を、索引の検索の後に適用します。たとえば条件に合うのが全体の10%なら、既定の hnsw.ef_search(40)で、平均4件しか残りません。0.8.0以降は反復スキャン(hnsw.iterative_scan)で、足りない分を探し足せます。部署や公開範囲などで絞り込む使い方では、重要な点です。

チャンクの切り方

Chromaのチャンクの手引きは、文を1つずつに切ると、答えと見出しの言葉が別の断片に離れて、検索に失敗する例を挙げています。「接続のタイムアウトは何秒?」という質問に対し、「既定値は30秒」の文だけの断片は「接続のタイムアウト」という語を含まず、見つかりにくいのです。手引きは、段落のまとまりを保つ切り方を勧めています。

その上で、切る大きさには上限が2つあります。

  1. 埋め込みモデルの入力長(EmbeddingGemmaなら2K)。
  2. 会話用のモデルの文脈の長さ。取り出した断片を全部入れる必要があるためです。

僕らの計算です。1断片を約500トークン、上位5件を取り出すと、断片だけで約2,500トークンです。質問と答えの分を合わせると、Ollamaの既定の文脈が4k(VRAM 24GiB未満の場合の公式の説明)では、ぎりぎりです。件数を絞るか、num_ctx(OLLAMA_CONTEXT_LENGTH)を上げる必要があります。

よくあるつまずき

気をつけること

出典

更新履歴