LLMアプリや検索システムにおいて「ベクトル検索」を導入したい方向けに、ChromaDB(Chroma)を用いた実装方法と、検索精度に直結する「ドキュメント分割(Chunking)」および「埋め込み(Embedding)」のベストプラクティスを整理しました。
📌 TL;DR
- Chromaは軽量・ローカル完結型のベクトルDB
- 検索精度は「チャンク設計」と「埋め込みモデルの選定」が鍵
- 文単位でトークン数を管理しつつチャンク分割するのが基本
- 埋め込みは「意味空間へのマッピング」であり、類似検索の核となる
🧠 ChromaDBとは?
Pythonから簡単に扱えるローカル完結型のベクトルデータベースです。主にテキストをベクトル化(Embedding)し、クエリとの類似度に基づいて検索する用途に使われます。
主な特徴
- DuckDB + Parquet ベースで、PostgreSQLなど外部DB不要
- 永続化可能でありながら、ファイルベースで軽量
- OpenAI APIやLangChainと親和性が高い
🔧 Chromaの導入と基本的な使い方
インストール
pip install chromadb sentence-transformers
初期化
import chromadb
from chromadb.config import Settings
client = chromadb.Client(Settings(
chroma_db_impl="duckdb+parquet",
persist_directory="./chroma_store"
))
collection = client.get_or_create_collection(name="manuals")
ドキュメント+Embeddingの追加
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
documents = [
"社員は毎朝9時までに出社してください。",
"有給休暇は年間20日間取得可能です。",
"業務報告書は毎週金曜日に提出すること。",
]
embeddings = model.encode(documents).tolist()
collection.add(
documents=documents,
embeddings=embeddings,
ids=[f"id_{i}" for i in range(len(documents))]
)
類似検索(クエリ → Embedding → 検索)
query = "いつ業務報告書を提出すべき?"
query_embedding = model.encode([query]).tolist()[0]
results = collection.query(
query_embeddings=[query_embedding],
n_results=2
)
for doc in results['documents'][0]:
print("🔍 関連ドキュメント:", doc)
🧩 ドキュメントのチャンク分割 ベストプラクティス
ベクトル検索の精度を最も左右するのが、テキストのチャンク(分割)方法です。
✅ チャンク設計のポイント
項目 | 推奨内容 |
---|---|
チャンクサイズ | 256〜512トークン程度 |
オーバーラップ | 10〜50トークン(文脈保持に有効) |
分割単位 | センテンス単位または段落単位 |
日本語対応 | 句点(。)、改行単位が有効 |
トークン制御方法 |
tiktoken 、nltk 等で実現可能 |
日本語向け チャンク処理例(簡易版)
import re
def chunk_text(text, max_tokens=300, overlap=30):
sentences = re.split("(?<=[。!?])", text)
chunks = []
current_chunk = ""
current_len = 0
for sentence in sentences:
sentence_len = len(sentence)
if current_len + sentence_len > max_tokens:
chunks.append(current_chunk.strip())
current_chunk = current_chunk[-overlap:] + sentence
current_len = len(current_chunk)
else:
current_chunk += sentence
current_len += sentence_len
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
🎯 Embeddingとは?
テキストを「意味空間上のベクトル(数値の配列)」に変換するプロセスです。
例えば:
- 「猫はかわいい動物です」
- 「犬もまた人懐っこい動物です」
この2文は意味的に類似しており、Embedding空間上では近い距離に配置されます。
🔍 代表的な埋め込みモデル
モデル名 | 特徴 | 多言語対応 | トークン上限 |
---|---|---|---|
all-MiniLM-L6-v2 | 軽量・高速・中精度 | △ | 約512 |
multilingual-MiniLM | 多言語対応(日本語含む) | ◎ | 約512 |
text-embedding-3-small | OpenAI製、高精度 | ◎ | 8192 |
jina-embeddings-v2-ja | 日本語特化、RAGに最適化 | ◎ | 約512 |
📐 類似度の計算(Cosine Similarity)
from sklearn.metrics.pairwise import cosine_similarity
# 2つのEmbeddingベクトルの類似度を計算
similarity = cosine_similarity([embedding1], [embedding2])
🧩 Embedding × Chunking × Retrieval(RAG構成)
典型的なRAG構成では、次のようなフローになります:
- ドキュメントをチャンク分割(トークン数を意識)
- 各チャンクをEmbeddingしてChromaに格納
- ユーザークエリもEmbeddingして検索
- 上位チャンクをLLMに渡して回答を生成
この精度は、チャンク設計 × 埋め込みモデル精度の掛け合わせで決まります。
🧪 応用・実戦投入パターン
用途 | 内容 |
---|---|
社内PDF資料の検索 | PDF → テキスト抽出 → Chunk → Embedding → 検索 |
顧客対応履歴のFAQ化 | チャットログから有用な応答を抽出し、ナレッジ化 |
コードスニペット検索 | 関数やコメントを文脈ごとにEmbeddingして構造化 |
✅ まとめ
- Chromaはベクトル検索の学習・試作に最適な選択肢
- チャンク処理は、トークン数と意味の滑らかさのトレードオフ設計が重要
- Embeddingモデルは用途と対象言語で慎重に選定すべき
- 本番運用では、FastAPIやLangChainとの統合も視野に
📎 参考リンク
👋 最後に
この構成はPoC(概念実証)だけでなく、本番運用にも十分適用可能です。
今後、LangChainとの統合、FastAPIによるAPI化、OpenAI APIとの連携例なども別途ご紹介予定です。