🙆
Chromaを使った埋め込み検索+ドキュメント分割のベストプラクティス
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との連携例なども別途ご紹介予定です。
Discussion