オープンソースAIモデルを用いたローカル環境でのベクトル検索エンジン構築

社外秘データを守り抜く。完全オフライン・OSSで構築するベクトル検索エンジン【Python実装】

この記事は急速に進化する技術について解説しています。最新情報は公式ドキュメントをご確認ください。

約11分で読めます
文字サイズ:
社外秘データを守り抜く。完全オフライン・OSSで構築するベクトル検索エンジン【Python実装】
目次

この記事の要点

  • 機密データ保護を徹底したセキュアな運用
  • 外部APIへの依存を排除し、コストとセキュリティリスクを低減
  • 完全オフライン環境でのベクトル検索実現

「社内データをChatGPTに投げられない」悩みを技術で突破する

「生成AIやRAG(検索拡張生成)を試したいが、社内のセキュリティ規定が厳しくて外部APIが使えない」

実務の現場において、このような課題に直面するケースは決して珍しくありません。OpenAI公式サイトの発表(2026年2月時点)によると、現在の主力モデルは長文の安定処理や高度な推論を備えたGPT-5.2へと移行し、開発業務向けにはエージェント型のGPT-5.3-Codexが登場するなど、クラウド上のAIモデルは飛躍的な進化を続けています。さらに、GPT-4oなどのレガシーモデルは順次廃止され、よりセキュアで高性能な環境への自動移行が進められています。

しかし、どれほどクラウド側のモデルが高性能化・安全化しても、機密情報や顧客データが含まれるドキュメントを外部のAPIに送信することは、コンプライアンス上、許容されないケースが多いのが現状です。

だからといって、AI導入による業務効率化を諦める必要はありません。

解決策は極めて論理的かつシンプルです。「外部に出せないデータは、内部の環境で処理する」というアプローチです。

本記事では、インターネット接続が不要な「完全ローカル環境」で動作するベクトル検索システムを構築する手順を解説します。使用するのは、Pythonとオープンソースソフトウェア(OSS)のみです。API課金は発生せず、データ流出のリスクもありません。

PoC(概念実証)に留まらない、実用性を重視した手法を体系的にお伝えします。インフラエンジニアやバックエンドエンジニアの皆様、ぜひ一緒に手を動かしながら確認していきましょう。

この学習パスのゴールとロードマップ

コードを書く前に、構築するシステムの全体像と得られるメリットを整理します。プロジェクトの目的を明確に保つことで、環境構築時の手戻りやつまずきを防ぐことができます。

なぜ今「ローカル」でベクトル検索なのか

クラウド全盛の時代に、あえてローカル構築を選ぶ理由は大きく3つあります。

  1. セキュリティ(Data Sovereignty): データが自社サーバー(あるいはローカルPC)から一歩も出ません。特許情報や個人情報など、機密性の高いデータを扱う業務では非常に有効なアプローチです。
  2. コスト予測の容易さ: APIのトークン課金ではなく、計算リソース(CPU/GPU/メモリ)への投資のみで完結します。ランニングコストが固定されるため、ROI(投資対効果)の算出がしやすく、予算承認が得やすいという利点もあります。
  3. ブラックボックスの排除: 検索ロジックやデータの加工プロセスをすべて自社でコントロールできます。「なぜこの検索結果が出たのか」という説明責任が求められる業務システムにおいて、プロセスの透明性は欠かせません。

作成するシステムの全体像

今回構築するシステムのデータフローは以下の通りです。特にデータベース部分では、確実なデータ保存のための永続化手法を採用し、最新のライブラリ動向を反映させます。

  1. ドキュメント読み込み: 社内のPDFやMarkdownファイルをPythonで読み込みます。
  2. チャンク分割: 長い文章を意味のある単位(チャンク)に分割します。
  3. Embedding(ベクトル化): Hugging Faceなどで公開されているOSSの軽量モデルを使い、テキストを数値列(ベクトル)に変換します。なお、最新のTransformers v5(2026年1月リリース)ではPyTorch中心のアーキテクチャに最適化され、TensorFlowのサポートは終了しています。また、ggml.aiの合流によりGGUFフォーマットが標準化されたことで、ローカル環境での推論がより軽量かつ高速に実行できるようになりました。既存のTensorFlowベースのコードを利用している場合は、PyTorchへの移行が必要です。これから環境を構築する際は、PyTorchベースでの実装を基本としてください。
  4. Vector DBへの格納: ローカルで動作する「ChromaDB」を使用します。データをメモリ上だけでなくディスクに保存するため、PersistentClient(永続化クライアント)を用いた実装を行います。
  5. 検索実行: ユーザーの質問文をベクトル化し、DB内から「意味が近い」ドキュメントを探し出します。

完了までの所要時間と到達レベル

  • 所要時間: Python環境が整っていれば、約1〜2時間で動作確認まで完了します。
  • 到達レベル: 自力でRAG(検索拡張生成)の基盤となる検索システム(Retriever)を構築・運用できる基礎力が身につきます。

前提知識と開発環境の準備

前提知識と開発環境の準備 - Section Image

まずは必要な環境を整えましょう。高価なGPUサーバーは必須ではありません。最近の一般的な開発用PC(メモリ16GB以上推奨)であれば十分動作します。

必要なPCスペックとライブラリ

大規模なLLM(70Bパラメータなど)を動かすわけではなく、今回は「Embeddingモデル(埋め込みモデル)」と「ベクトルDB」が主役です。これらは比較的軽量に動作します。

  • OS: Linux, macOS, Windows (WSL2推奨)
  • CPU: 最近のIntel/AMD/Apple Silicon
  • RAM: 8GB以上(16GB以上あると快適です)
  • GPU: あれば高速ですが、CPUのみでも数千件のドキュメントなら実用的な速度で処理可能です。

DockerとPython環境のセットアップ

再現性を高めるため、Python 3.10以上の環境を用意してください。仮想環境(venvやconda)の使用を強く推奨します。

# プロジェクトディレクトリの作成
mkdir local-vector-search
cd local-vector-search

# 仮想環境の作成と有効化
python3 -m venv venv
source venv/bin/activate  # Windowsの場合は venv\Scripts\activate

次に、必要なライブラリをインストールします。今回は以下の主要ライブラリを使用します。

  • langchain: AIアプリ開発のフレームワーク
  • langchain-community: LangChainのコミュニティ拡張
  • langchain-huggingface: Hugging Faceモデルを利用するための統合ライブラリ
  • chromadb: 軽量で扱いやすいOSSベクトルデータベース
  • sentence-transformers: テキストのベクトル化ライブラリ
  • pypdf, unstructured: ドキュメント読み込み用

requirements.txt を作成し、一括インストールしましょう。

# requirements.txt
langchain==0.2.0
langchain-community==0.2.0
langchain-huggingface==0.0.3
chromadb==0.5.0
sentence-transformers==2.7.0
pypdf==4.2.0
pip install -r requirements.txt

使用するOSSモデルの選定基準

ここが最初の重要なポイントです。どのEmbeddingモデルを採用するかで、検索精度(特に日本語の精度)が大きく左右されます。

今回は、Hugging Faceで公開されている intfloat/multilingual-e5-base を推奨します。

  • 理由1: 多言語対応(Multilingual)であり、日本語の精度が非常に高い点。
  • 理由2: モデルサイズが手頃(約1GB)で、ローカルPCでもスムーズに動作する点。
  • 理由3: 多くのベンチマーク(MTEB)で上位の成績を収めている実績がある点。

Step 1:ベクトルの基礎とEmbeddingの実践

いきなりデータベースを構築する前に、「テキストをベクトルにする」とはどういうことか、Pythonコードを通じて論理的に理解しておきましょう。この基礎を押さえておくことが、後のトラブルシューティングで役立ちます。

コンピュータはどう言葉を理解するか

ベクトル検索において、言葉は「意味の座標」に変換されます。例えば、「猫」と「犬」は近い座標に、「猫」と「車」は遠い座標に配置されます。

Embeddingモデルは、テキストを数百〜数千次元の数値のリスト(ベクトル)に変換する翻訳機のような役割を果たします。

Sentence-Transformersでテキストを数値化する

以下のコードを step1_embedding.py として保存し、実行してみてください。

# step1_embedding.py
from langchain_huggingface import HuggingFaceEmbeddings
import numpy as np

# モデルのロード(初回はダウンロードに時間がかかります)
# multilingual-e5-baseを使用
model_name = "intfloat/multilingual-e5-base"
model_kwargs = {'device': 'cpu'} # GPUがある場合は 'cuda' または 'mps'
encode_kwargs = {'normalize_embeddings': True} # コサイン類似度計算用に正規化

print("モデルをロード中...")
embeddings = HuggingFaceEmbeddings(
    model_name=model_name,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs
)

# テキストをベクトル化
text = "社内規定により、クラウドストレージの利用は禁止されています。"
vector = embeddings.embed_query(text)

print(f"\n元のテキスト: {text}")
print(f"ベクトルの次元数: {len(vector)}")
print(f"ベクトルの先頭5要素: {vector[:5]}")

実行結果を見ると、テキストが [-0.012, 0.045, ...] といった数値の羅列に変換されたことが確認できます。これがAIにとっての「意味」の表現形式です。

コサイン類似度で「意味の近さ」を計算する

次に、異なる文章同士の類似度を計算してみましょう。

# 比較するテキスト
texts = [
    "クラウドストレージは使えません。",  # 意味が近い
    "今日のランチはカレーです。"        # 意味が遠い
]

# query: 接頭辞をつけるのが e5 モデルのお作法(検索クエリ側)
query_text = "query: ファイルサーバーの利用ルールについて教えて"

# ドキュメント側には passage: 接頭辞をつける(e5モデル特有の処理)
doc_vectors = embeddings.embed_documents([f"passage: {t}" for t in texts])
query_vector = embeddings.embed_query(query_text)

# コサイン類似度を計算する関数
def cosine_similarity(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

print("\n--- 類似度計算結果 ---")
for text, doc_vec in zip(texts, doc_vectors):
    score = cosine_similarity(query_vector, doc_vec)
    print(f"クエリとの類似度: {score:.4f} | テキスト: {text}")

「クラウドストレージは使えません」の方が、「カレー」よりも高いスコア(1.0に近い値)が出力されるはずです。単語が完全に一致していなくても意味が近いものを探せること、これがベクトル検索の最大の強みです。


Step 2:ローカルベクトルデータベースの構築

Step 2:ローカルベクトルデータベースの構築 - Section Image

ベクトル化の仕組みを理解したところで、次はこれを保存・検索できるデータベース「ChromaDB」を構築します。ChromaDBはPythonだけで完結し、サーバー構築が不要なファイルベースで動作するため、ローカル開発に最適な選択肢です。

ChromaDBの基本操作と永続化

ChromaDBはデフォルトではメモリ上で動作しますが、再起動するとデータが消えてしまいます。実用的なシステムにするためには、データをディスクに保存(永続化)する実装が必要です。

社内ドキュメント(PDF/Markdown)の取り込み

実務での利用を想定し、仮想的な社内規定ドキュメントを取り込んでみましょう。

まず、プロジェクト直下に data フォルダを作成し、適当なテキストファイル(例: rules.txt)を配置します。

data/rules.txt の例:

【ITセキュリティガイドライン】
第1条:クラウドサービスの利用
業務データを含むファイルを、許可されていない外部クラウドストレージ(Dropbox, Google Drive等)にアップロードすることを禁じます。

第2条:パスワード管理
パスワードは12桁以上とし、大文字・小文字・数字・記号を混在させる必要があります。90日ごとの変更を推奨します。

第3条:VPN利用
社外から社内ネットワークへ接続する際は、必ず会社支給のVPNクライアントを使用してください。

チャンク分割の戦略とベストプラクティス

長い文章をそのままベクトル化すると、意味の焦点がぼやけてしまいます。適切な長さ(チャンク)に分割することが、検索精度を高めるコツです。日本語の場合、文字数だけで機械的に区切ると文脈が途切れることがあるため、RecursiveCharacterTextSplitter を活用し、改行や句読点を基準に分割します。

以下は、データの読み込みからDB保存までを一貫して行うコードです(step2_build_db.py)。

# step2_build_db.py
import os
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

# 1. ドキュメントの読み込み
print("ドキュメントを読み込んでいます...")
loader = DirectoryLoader('./data', glob="**/*.txt", loader_cls=TextLoader)
documents = loader.load()

# 2. チャンク分割
# 日本語の場合、chunk_sizeは300~500文字程度が目安
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,
    chunk_overlap=50,  # 前後の文脈を保持するために少し重複させる
    separators=["\n\n", "\n", "。", "、", " "]
)
split_docs = text_splitter.split_documents(documents)
print(f"{len(documents)}つのファイルを {len(split_docs)}つのチャンクに分割しました。")

# 3. Embeddingモデルの準備(Step1と同じ)
embeddings = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base",
    model_kwargs={'device': 'cpu'},
    encode_kwargs={'normalize_embeddings': True}
)

# 4. ChromaDBへの保存(永続化)
persist_directory = "./chroma_db" # データを保存するフォルダ

print("ベクトルDBを構築中...(これには少し時間がかかります)")
# ドキュメントの内容に "passage: " を付与してからベクトル化する処理が含まれるラッパーを使うか、
# あるいはシンプルにそのまま格納しても検索は可能ですが、精度向上のため工夫が必要です。
# 今回は簡易化のため、LangChainの標準機能で格納します。

vectorstore = Chroma.from_documents(
    documents=split_docs,
    embedding=embeddings,
    persist_directory=persist_directory
)

print("完了!データベースは './chroma_db' に保存されました。")

このスクリプトを実行すると、chroma_db フォルダが作成され、そこにベクトルデータが永続的に保存されます。


Step 3:検索エンジンの実装と精度検証

いよいよ検索システムを稼働させます。保存されたDBを読み込み、質問に対して適切な回答箇所(チャンク)が返ってくるかを検証します。

セマンティック検索の実装コード

step3_search.py を作成します。

# step3_search.py
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

# 1. 保存済みDBのロード
persist_directory = "./chroma_db"
embeddings = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base",
    model_kwargs={'device': 'cpu'}
)

vectorstore = Chroma(
    persist_directory=persist_directory,
    embedding_function=embeddings
)

# 2. 検索実行関数
def search_docs(query_text, k=3):
    print(f"\n質問: {query_text}")
    print("-" * 30)
    
    # 類似度スコア付きで検索 (scoreが小さいほど距離が近い=類似している)
    # Chromaのデフォルト距離指標はL2(ユークリッド距離)など設定によるが、
    # LangChain経由だと通常コサイン距離などが使われる。Chromaの場合は距離なので0に近いほど類似。
    results = vectorstore.similarity_search_with_score(query_text, k=k)
    
    for doc, score in results:
        print(f"[スコア: {score:.4f}] 内容: {doc.page_content[:100]}...")

# 3. テスト
search_docs("Googleドライブにファイルを上げてもいい?")
search_docs("パスワードのルールを教えて")

キーワード検索との違いを体感する

実行結果を確認してください。「Googleドライブ」という単語は元のテキスト(rules.txt)に含まれていても、「上げてもいい?」という口語表現は存在しません。しかし、ベクトル検索は文脈を理解し、「第1条:クラウドサービスの利用...禁じます」というチャンクを的確にヒットさせるはずです。

これが、従来のキーワード一致型検索(grep検索)では実現が難しい、AI駆動のセマンティック検索の優位性です。

検索結果のフィルタリングとメタデータ活用

実際のプロジェクトでは「営業部のドキュメントだけ検索したい」「2023年以降の資料に絞りたい」といった要件が頻出します。ChromaDBはメタデータフィルタリングに対応しているため、柔軟な検索制御が可能です。

データを登録する際にメタデータを付与しておけば、以下のように絞り込みを実行できます。

# メタデータフィルタの例(検索時)
results = vectorstore.similarity_search(
    "売上データ",
    k=3,
    filter={"department": "sales"} # メタデータで絞り込み
)

Step 4:実務適用への応用と最適化

ここまでで、基本的な検索エンジンは完成しました。しかし、これを実際の業務フローに組み込み、価値を生み出すためには、もう一段階の工夫が求められます。

RAG(検索拡張生成)への接続イメージ

検索結果を表示するだけでは、ユーザーは自ら文章を読み解く必要があります。ここからさらに一歩進め、「検索結果をAIに読み込ませて、回答を生成させる」のがRAGのアプローチです。

ローカル環境でこれを実現するには、Llama.cppOllama といったローカルLLM実行環境と、今回構築したベクトル検索を連携させます。

# 概念コード(RAGの流れ)
retrieved_docs = vectorstore.similarity_search(query)
context = "\n".join([doc.page_content for doc in retrieved_docs])

prompt = f"以下の情報を元に質問に答えてください。\n\n情報: {context}\n\n質問: {query}"
# このpromptをローカルLLM(Ollama等)に投げる

処理速度と精度のトレードオフ調整

  • インデックス構築の高速化: ドキュメントが数万件規模になると、CPUでのEmbedding処理に時間を要します。その場合は、NVIDIA GPU(CUDA)の導入を検討するか、より軽量なモデル(all-MiniLM-L6-v2など)への切り替えを検討するのが現実的です。
  • 精度の向上: 検索結果が期待に満たない場合、「チャンクサイズの調整」が効果的な解決策となることが多々あります。文脈が不自然に切断されていないかを確認し、chunk_overlap の値を増やして調整してみてください。

まとめ:ローカルAI開発の第一歩を踏み出そう

3. Embeddingモデルの準備(Step1と同じ) - Section Image 3

今回解説した手順により、外部APIを一切使用せず、自社の機密データを安全に検索できる基盤が構築できました。

  1. PythonとOSSだけで完結: コストとセキュリティのリスクを排除。
  2. Embeddingの理解: AIが言葉をどう処理しているかを論理的に把握。
  3. ChromaDBの実装: 永続化可能なベクトルDBの構築。

これは、AIプロジェクトを成功に導くための入り口に過ぎません。ここから、Web UI(Streamlitなど)を実装してユーザビリティを高めたり、ローカルLLMと接続して完全オフラインのチャットボットを構築したりと、ビジネス課題に応じた応用が可能です。

セキュリティ要件を障壁とせず、適切な技術選定によってAIの力を実務に取り入れていきましょう。

社外秘データを守り抜く。完全オフライン・OSSで構築するベクトル検索エンジン【Python実装】 - Conclusion Image

コメント

コメントは1週間で消えます
コメントを読み込み中...