はじめに — 対象読者とこの記事のゴール
ChatGPTやClaudeに「社内ドキュメントを読み込ませて質問に答えさせる」機能、聞いたことはありませんか?
最近はいろんなAIサービスで「ナレッジ検索」や「RAG」という言葉を見かけるようになりました。
でも、その「AIが自分たちの文書の中から正しい答えを見つける仕組み」の中身がどう動いているか、考えたことはあるでしょうか?
実はその心臓部にあるのが 「ベクトル検索」 という技術で、それを支えるのが 「ベクトルデータベース」 と呼ばれる専用のデータベースです。
この記事の対象読者
- AIサービスの「ナレッジ検索 / RAG」は聞いたことがある
- でも「ベクトル検索」「ベクトルDB」は聞いたことがない / よく分からない
- 手を動かして理解したい
この記事のゴール
- Milvus(代表的なオープンソースのベクトルDB)を実際に動かす
- 手を動かしながら「ナレッジ検索の裏側」を自分の手で体験する
- 記事を読み終わる頃には「あの機能、こう動いてたのか!」と腑に落ちる
理論の説明は最小限にして、なるべく早くコードを動かせる構成にしました。では、いきましょう。
1. キーワード検索と、AIのナレッジ検索は何が違うのか
まずは「みんなが知っている検索」と比較して、ベクトル検索の特徴を浮き彫りにします。
キーワード検索の限界
これまでの検索(Google検索や社内Wikiの検索など)は、基本的に キーワード検索 です。
「指定した単語を含む文書」を探します。シンプルで強力ですが、弱点があります。
例えば、ナレッジベースにこんな文書があるとします。
Doc A: 「猫は毎日毛づくろいをして毛並みを綺麗に保ちます。」
Doc B: 「我が家の猫は窓辺で日向ぼっこをするのが日課です。」
ここでユーザーが 「猫のお手入れ方法は?」 と検索したとします。
- 「お手入れ」「方法」という単語は、Doc A にも Doc B にも出てきません
(Doc A が言っているのは「毛づくろい」で、表現が違います) - 一致するのは 「猫」 だけ。すると両方の文書が同じ重みでヒットし、「どちらがお手入れに関係あるか」を区別できません
本当は Doc A(毛づくろい) の方が「お手入れ」の答えに近いのに、キーワードが揃っていないと、その関連度で上位に押し上げられない。
これがキーワード検索の限界です。「言い換え」「表現ゆれ」「違う単語でも同じ意味」 に弱いのです。
AIのナレッジ検索は「意味が近いもの」を探す
一方、AIのナレッジ検索では 「言葉の意味が近い文書」 を探す手法が大きな役割を果たします。
単語が一致していなくても、言いたいことが近ければヒットします。
「猫のお手入れ方法は?」
→ 「毛づくろい」=「お手入れ」と意味で捉えて、Doc A を上位で返す
「意味が近い」って、どうやって判定するの? —— ここで登場するのが ベクトル です。
※ 補足:実際のAIナレッジ検索システムでは、すべてをベクトル検索にするのではなく、全文検索(キーワード検索)と組み合わせた「ハイブリッド検索」なども広く使われています。本記事ではその中心となるベクトル検索の仕組みに焦点を当てて解説します。
2. ベクトル検索を1枚の図で理解する(概念編)
ここがこの記事の山場です。用語を最小限にして、直感で捉えましょう。
「テキストをベクトルに変換する」とは
ベクトル とは、要するに 「数字の並び」 です。例えば [0.12, -0.45, 0.88, ...] のような数値のリスト。
AIは、テキストを 意味をとらえた数値の並び(ベクトル) に変換できます。これを 埋め込み(embedding) と呼びます。
重要な性質はこれです:
意味が近いテキストほど、ベクトル同士が近い位置に来る
これだけです。これがベクトル検索の原理のすべてです。
図でイメージする
ベクトルは本来何百もの数字の並び(数百次元)で描けませんが、イメージとしては「意味の空間にテキストが点として配置される」と考えてください。
意味の空間(イメージ):意味が近い文ほど、近い位置に配置される
《ペット》のあたり 《環境・エネルギー》のあたり
● 犬・散歩 ● 電気自動車
● 犬・伴侶 ● 太陽光発電
● 猫・毛づくろい
● 猫・日向ぼっこ
↓ クエリ「猫のお手入れは?」をベクトル化
クエリのベクトルは「猫・毛づくろい」の近くに落ちる
(「お手入れ」≒「毛づくろい」と意味が近いから)
→ だから「猫・毛づくろい」が検索結果として返る!
クエリが「猫」や「お手入れ」という単語を含んでいなくても、意味の空間で近くに配置される文書が拾える。これがベクトル検索の魔法(というか、仕組み)です。
登場人物を整理(用語はここだけ押さえればOK)
| 用語 | ざっくり意味 | この記事での役割 |
|---|---|---|
| ベクトル | 意味をとらえた数字の並び | 検索の単位 |
| 埋め込み(embedding) | テキスト→ベクトルの変換 | sentence-transformersが担当 |
| 類似度 | ベクトル同士の近さ | COSINE類似度を使う |
| 埋め込みモデル | 変換をしてくれるAIモデル | paraphrase-multilingual-MiniLM-L12-v2(多言語・軽量モデル) |
「埋め込みモデルがテキストをベクトルにし、ベクトル同士の近さで似ているものを探す」——これがベクトル検索です。以上で概念は終わり! 次はこれを蓄える箱の話です。
3. ベクトルデータベースとは何者か(なぜ普通のDBではダメなのか)
ここまでで「テキストをベクトルにして、近いものを探す」という仕組みは分かりました。
じゃあ、そのベクトルをどこに保存しますか?
100件なら普通の配列で十分
データが100件程度なら、Pythonのリストにベクトルを入れておいて、クエリのベクトルと 全部のベクトルの距離を1つずつ計算して近い順にソート すれば済みます。NumPyを使えば数行です。
100万件になったら破綻する
でもデータが 100万件、1億件 になったら?
毎回全件と距離を計算するのは時間がかかりすぎて使いものになりません。
ここで必要になるのが ベクトルデータベース です。
ベクトルDBの役割
大量のベクトルを蓄え、高速に「似ているベクトル」を探し出す ことに特化したデータベース。
「近いものを探す」ための専用のインデックス(仕組み)を持っているおかげで、数億件あってもミリ秒単位で検索できます。普通のRDB(PostgreSQLやMySQL)はこういう処理に向いていないので、専用システムが必要になります。
Milvus(ミルバス)について
この記事で使う Milvus は、代表的なオープンソースのベクトルデータベースです。
- オープンソースで無料
- 本番環境でもガッツリ使われている実績
- Pythonクライアント(pymilvus)がシンプルで初心者向け
それでは、いよいよ手を動かしていきましょう!
4. 【ハンズオン準備】MilvusをDockerで動かす + uvでPython環境
ここからハンズオンです。やることは2つ:
- DockerでMilvusサーバーを起動(データを蓄える箱)
- uvでPython環境を作る(クライアント側:ベクトル化+検索のプログラムを動かす)
必要なもの
- Docker(Docker Desktop または Docker Engine + Compose v2)
-
uv(Pythonのパッケージ/環境管理ツール。超軽量で速い)
- インストールしていない場合: uvの公式ドキュメント を参照。Mac/Linuxなら
curl -LsSf https://astral.sh/uv/install.sh | shで入ります。
- インストールしていない場合: uvの公式ドキュメント を参照。Mac/Linuxなら
ステップ1: MilvusをDockerで起動する
Milvus公式が提供している Standalone 用の起動スクリプト(standalone_embed.sh)を使用すると、非常に簡単に起動できます。
ターミナルで以下のコマンドを実行し、スクリプトのダウンロードとMilvusの起動を行ってください。
# 1. 公式の起動スクリプトをダウンロード
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# 2. Milvusコンテナの起動
bash standalone_embed.sh start
初回はDockerイメージのダウンロードとコンテナ初期化が行われるため、1〜2分程度かかります。
起動後、以下のコマンドでコンテナの状態を確認できます。
docker ps
出力例:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9e77620ab203 milvusdb/milvus:v3.0.0 "/tini -- milvus run…" 44 seconds ago Up 43 seconds (healthy) 0.0.0.0:2379->2379/tcp, :::2379->2379/tcp, 0.0.0.0:9091->9091/tcp, :::9091->9091/tcp, 0.0.0.0:19530->19530/tcp, :::19530->19530/tcp milvus-standalone
milvus-standalone コンテナが Up または healthy になっていれば、Milvusサーバー(http://localhost:19530)の起動は完了です。
⚠️ Milvusの起動には少し時間がかかる場合があります。コンテナが立ち上がるまで待ってから次のステップへ進んでください。
ステップ2: uvでPython環境を作る
uv の初期化コマンドとパッケージ追加コマンドを使って環境を作ります。手動で設定ファイルを編集する必要はありません。
# 1. プロジェクトの初期化(pyproject.toml や仮想環境が自動生成されます)
uv init
# 2. 必要なパッケージを追加(依存関係の解決・インストールが自動で実行されます)
uv add "pymilvus==3.0.1" "sentence-transformers>=3.0,<4.0"
これだけで、pymilvus(Milvusクライアント)と sentence-transformers(テキスト→ベクトル変換機)がプロジェクトに追加され、環境構築まで一括で完了します。
📦
sentence-transformersは依存として PyTorch を引くため、初回のuv addは 数百MB〜1GB程度のダウンロード になり時間がかかります。通信環境のある場所で実行してください。
さらに初回スクリプト実行時に、埋め込みモデル本体(paraphrase-multilingual-MiniLM-L12-v2, 約460MB)も追加ダウンロードされます。日本語を含む多言語に対応したモデルを選んでいます(日本語記事なので、日本語をきちんと扱えるモデルが重要です)。
準備完了です! いよいよコードを動かします。
5. 【ハンズオン①】ベクトルを入れて、似ているものを探す
いきなり「ナレッジ検索」を作る前に、まずは 「ベクトル検索の感触」 を掴む小さな例を動かします。
hands-on/01_vector_search.py を作成します。
# hands-on/01_vector_search.py
from pymilvus import MilvusClient
from sentence_transformers import SentenceTransformer
# 0. 埋め込みモデルを読み込む(テキスト→ベクトルの変換機)
# 日本語を扱うため、多言語対応のモデルを使います
print("埋め込みモデルを読み込んでいます... (初回はダウンロードが発生します)")
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
DIM = model.get_sentence_embedding_dimension()
print(f"ベクトルの次元数: {DIM}")
# 1. サンプル文(猫/犬/環境エネルギーの3グループ)
documents = [
"猫は毎日毛づくろいをして毛並みを綺麗に保ちます。", # 猫
"犬は散歩が大好きで飼い主と外を歩くことを喜びます。", # 犬
"電気自動車は排ガスを出さず環境への負荷が小さいです。", # エネルギー
"我が家の猫は窓辺で日向ぼっこをするのが日課です。", # 猫
"犬は古くから人間の友好的な伴侶として共に生きてきました。", # 犬
"太陽光発電は再生可能エネルギーの代表格として普及しています。", # エネルギー
]
# 2. テキストをベクトルに変換する(=埋め込み)
vectors = model.encode(documents).tolist()
data = [{"id": i, "vector": vectors[i], "text": documents[i]} for i in range(len(documents))]
# 3. Milvusに接続してコレクションを作る
client = MilvusClient(uri="http://localhost:19530")
COLLECTION = "demo_vector_search"
if client.has_collection(COLLECTION):
client.drop_collection(COLLECTION)
# クイックセットアップ: 名前と次元だけ指定すれば作成〜ロードまで自動
# consistency_level="Strong" は「検索時に直前のinsertまで確実に参照する」設定。
# これを指定しない(デフォルト)と、登録直後の検索でデータが見つからないことがあります。
client.create_collection(
collection_name=COLLECTION,
dimension=DIM,
metric_type="COSINE",
consistency_level="Strong",
)
# 4. データを登録
client.insert(collection_name=COLLECTION, data=data)
# 5. 検索: クエリ文に意味が近い上位3件を探す
query = "猫はどんな身だしなみをしますか?"
query_vector = model.encode([query]).tolist()
results = client.search(
collection_name=COLLECTION,
data=query_vector,
limit=3,
output_fields=["text"],
search_params={"metric_type": "COSINE", "params": {}},
)
# 6. 結果表示
print(f"\nクエリ: 「{query}」")
print("=== 検索結果(意味が近い順)===")
for hits in results:
for rank, hit in enumerate(hits, start=1):
print(f"{rank}位 [score={hit['distance']:.4f}] {hit['entity']['text']}")
client.drop_collection(COLLECTION)
💡 コラム:MilvusClientのコレクション作成(Quick Setup と Customized Setup)
上記のコードでは
client.create_collection(collection_name=COLLECTION, dimension=DIM)というシンプルな書き方をしています。これは PyMilvus の Quick Setup(簡易作成) という機能です。スキーマを1から定義しなくても、MilvusClient が以下を自動で行ってくれます:
- デフォルトフィールドの作成: 主キー
id(INT64) およびベクトルvectorフィールドを自動生成- Dynamic Field の有効化:
textなどその他のデータをスキーマ指定なしで柔軟に挿入可能- インデックス作成とロード: インデックス構築(AUTOINDEX)とメモリへのロード (
load) を自動完了※ 本番運用などで特定のデータ型指定や複合インデックス構成が必要な場合は、
MilvusClient.create_schema()やprepare_index_params()を使って明示的にスキーマを定義する Customized Setup も利用できます。
実行します。
uv run python hands-on/01_vector_search.py
結果の例
クエリ: 「猫はどんな身だしなみをしますか?」
=== 検索結果(意味が近い順)===
1位 [score=0.7273] 猫は毎日毛づくろいをして毛並みを綺麗に保ちます。
2位 [score=0.6279] 我が家の猫は窓辺で日向ぼっこをするのが日課です。
3位 [score=0.1261] 犬は散歩が大好きで飼い主と外を歩くことを喜びます。
※
scoreはCOSINE類似度(1.0に近いほど意味が近い)。1位・2位に 「猫」の文が2つ連続 で入っていますね。値は実行環境やモデルのバージョンによって多少異なります。
ここで「おっ!」となるポイント
クエリは 「猫はどんな身だしなみをしますか?」 です。
1位の文は 「猫は毎日毛づくろいをして…」 ですね。
よく見てください。クエリには 「毛づくろい」「毛並み」という単語は一つもありません。
それなのに、意味が近いと判断されて1位でヒットしています。
キーワード検索なら「猫」という単語で猫の文は拾えても、「身だしなみ」と「毛づくろい」の結びつきまでは分からない(「猫」を含む文が順不同で並ぶだけ)。
ベクトル検索は「身だしなみ ≒ 毛づくろい」と意味で捉えるからこそ、毛づくろいの文を上位に持ってこられたのです。
これが 第1章で言った「意味が近いものを探す」の正体 です。あなたは今、自分の手でそれを再現しました!
6. 【ハンズオン②】冒頭の「ナレッジ検索」を自分で再現する
感触は掴めました。いよいよ本題—— AIサービスのナレッジ検索 を自分で作ります。
ここでは架空のサービス 「CloudNote」 のFAQを小さなナレッジベースとしてMilvusに登録し、ユーザーの質問に意味が近いFAQを検索します。
hands-on/02_knowledge_search.py を作成します。
# hands-on/02_knowledge_search.py
from pymilvus import MilvusClient
from sentence_transformers import SentenceTransformer
# 0. 埋め込みモデル(日本語対応の多言語モデル)
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
DIM = model.get_sentence_embedding_dimension()
# 1. ナレッジベース(文書チャンク)。実運用ではPDF/Wikiを分割したチャンクがここに入る
knowledge_base = [
{"text": "CloudNoteの料金プランは、無料プランと月額980円のプロプランの2種類です。", "source": "料金ページ"},
{"text": "プロプランでは無制限のノート作成と10GBの添付ファイル保存が可能です。", "source": "機能ページ"},
{"text": "CloudNoteのデータはAES-256で暗号化され、保存時に保護されます。", "source": "セキュリティページ"},
{"text": "サポートは平日9時から18時まで、メールとチャットで受け付けています。", "source": "サポートページ"},
{"text": "ノートはフォルダとタグで整理でき、全文検索にも対応しています。", "source": "機能ページ"},
{"text": "スマートフォンアプリはiOSとAndroidの両方に対応しています。", "source": "機能ページ"},
{"text": "過去30日間のノートは自動的にバージョン履歴として保存され、いつでも復元できます。", "source": "機能ページ"},
{"text": "プロプランの解約はいつでも可能で、解約後は月末まで利用できます。", "source": "料金ページ"},
]
# 2. ベクトル化してMilvusに登録
texts = [c["text"] for c in knowledge_base]
vectors = model.encode(texts).tolist()
data = [{"id": i, "vector": vectors[i], "text": c["text"], "source": c["source"]}
for i, c in enumerate(knowledge_base)]
client = MilvusClient(uri="http://localhost:19530")
COLLECTION = "demo_knowledge_search"
if client.has_collection(COLLECTION):
client.drop_collection(COLLECTION)
# consistency_level="Strong": 登録直後の検索で0件になるのを防ぐため、最新insertまで確実に参照させる
client.create_collection(collection_name=COLLECTION, dimension=DIM, metric_type="COSINE", consistency_level="Strong")
client.insert(collection_name=COLLECTION, data=data)
# 3. 質問文でナレッジ検索
questions = [
"料金はいくらですか?",
"データは安全に保存されますか?",
"スマホから使うことはできますか?",
"間違えて編集してしまいました、元に戻せますか?",
]
for question in questions:
print(f"\n質問: 「{question}」")
query_vector = model.encode([question]).tolist()
results = client.search(
collection_name=COLLECTION,
data=query_vector,
limit=2,
output_fields=["text", "source"],
search_params={"metric_type": "COSINE", "params": {}},
)
for hits in results:
for rank, hit in enumerate(hits, start=1):
print(f" {rank}位 [score={hit['distance']:.4f}] ({hit['entity']['source']}) {hit['entity']['text']}")
client.drop_collection(COLLECTION)
実行します。
uv run python hands-on/02_knowledge_search.py
結果の例
質問: 「料金はいくらですか?」
1位 [score=0.3469] (料金ページ) CloudNoteの料金プランは、無料プランと月額980円のプロプランの2種類です。
...
質問: 「データは安全に保存されますか?」
1位 [score=0.6282] (セキュリティページ) CloudNoteのデータはAES-256で暗号化され、保存時に保護されます。
...
質問: 「間違えて編集してしまいました、元に戻せますか?」
1位 [score=0.3218] (機能ページ) 過去30日間のノートは自動的にバージョン履歴として保存され、いつでも復元できます。
...
※
scoreはCOSINE類似度(1.0に近いほど意味が近い)。値は実行環境やモデルのバージョンによって多少異なります。...は2位以下の結果を省略した表示です。
最後の質問に注目してください。
「間違えて編集してしまいました、元に戻せますか?」 に対して、
「バージョン履歴として保存され、いつでも復元できます」 がヒットしています。
「バージョン履歴」「復元」という単語は質問にありませんが、「元に戻したい」という意図 を意味で捉えて正しいFAQを引っ張ってきました。これがナレッジ検索の力です。
これがRAGの「検索フェーズ」
ここまでで何が起きたか整理します。
ユーザーの質問
│
▼
┌──────────────┐
│ 埋め込みモデル │ ← 質問文をベクトル化
└──────────────┘
│
▼
┌──────────────┐
│ Milvus │ ← ナレッジベースから意味が近いチャンクを検索
└──────────────┘
│
▼
関連チャンク(上位N件)
│
▼
┌──────────────┐
│ LLM │ ← 「このチャンクを読んで質問に答えて」
└──────────────┘
│
▼
AIの回答 ← あなたがChatGPTで見ているアレ!
本記事では 上の3段(埋め込み→Milvus検索→関連チャンク取得) を実装しました。
最後の「LLMにチャンクを渡して回答生成」の部分を繋げば、それが RAG(=AIのナレッジ検索) の完成です。
💡 お気づきでしょうか — 冒頭で「AIが社内文書から答えを見つける仕組み、中身はどう動いているか?」と問いかけました。その答えが まさに今あなたが動かしたコード です。「質問をベクトル化 → ベクトルDBで意味検索 → 関連文書を取得」、これがナレッジ検索の正体でした。
7. 終わりに / 次のステップ
今回やったこと(1行ずつ)
- ベクトル検索 = テキストをベクトルに変換し、意味の近さで似ているものを探す仕組み
- ベクトルDB(Milvus) = 大量のベクトルを蓄え、高速に類似検索する専用データベース
- ハンズオン① = 小さなサンプルで「意味で検索する」感触を掴んだ
- ハンズオン② = FAQのナレッジ検索を作り、RAGの検索フェーズを体験した
「ナレッジ検索って聞いたことあるけど中身が分からない」状態から、自分の手で動かして仕組みを理解する ところまで来れました。これがこの記事のゴールでした 🎉
次のステップ(興味がある人向け)
| やりたいこと | 次の一歩 |
|---|---|
| 本当にAIに回答させたい | 取得したチャンク+質問をLLM API(Claude / OpenAI等)に渡して回答生成を繋げる |
| 大量の文書を扱いたい | PDF/Wikiをチャンクに分割する処理(LangChain / LlamaIndex等)を試す |
| 検索精度を上げたい(ハイブリッド検索) | ベクトル検索とキーワード検索(BM25)を組み合わせる: Milvus Full Text Search(日本語) |
| 特定条件で絞り込みたい(メタデータフィルタ) | 日付やカテゴリなどのスカラー属性でフィルタリングする: Milvus Boolean Expression Rules(日本語) |
| マルチテナント構成にしたい(データの個別分離) | Partition Key等を使ってユーザー・組織ごとにデータを分離管理する: Milvus Multi-tenancy(日本語) |
| 検索スピード・メモリを最適化したい | データ規模に合わせてインデックス型(HNSW/IVF等)を調整する: HNSW(日本語) |
| 本番構成・運用を知りたい | 分散クラスタ構成を構築する: Milvus Cluster Deployment(日本語) / マネージドサービスを調べる: Zilliz Cloud |
📣 Milvusのハンズオンイベントをご案内(2026年9月17日)
「記事を読んで、もっと実践的に手を動かしてみたい!」と思った方に朗報です。
Milvusの開発元である Zilliz が、無料の対面ハンズオンを開催します。
| 項目 | 内容 |
|---|---|
| イベント名 | Zilliz ハンズオン|全文検索とベクトル検索を、一つのエンジンで |
| 日時 | 2026年9月17日(木)18:00〜21:00 |
| 会場 | TKP東京駅カンファレンスセンター(JR東京駅 八重洲中央口 徒歩1分)・対面開催 |
| 参加費 | 無料(定員40名・先着順) |
| 主催 | Zilliz(Milvus Group) |
本記事では「ベクトル検索の基本」を扱いましたが、このイベントではさらに一歩進んで 「全文検索+ベクトル検索のハイブリッド検索」「マルチモーダル検索」「Milvus 3.0の新機能デモ」 などを、Zilliz Cloud上で実際に構築しながら学べます。本記事の「次のステップ」としてぴったりです。
📖 お申し込み・詳細: milvus.connpass.com/event/403452
後片付け
ハンズオンで使ったMilvusコンテナは、使い終わったら止めておきましょう。
bash standalone_embed.sh stop # コンテナの停止
bash standalone_embed.sh delete # コンテナおよびデータの削除
参考
ここまで読んでくださりありがとうございました!
「ベクトル検索、意外とシンプルじゃん」と思ってもらえたら、この記事を読んだあなたの勝ちです。
質問や「こういう観点も書いてほしい」があれば、ぜひコメント欄で教えてください 👋
