🙆

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トークン(文脈保持に有効)
分割単位 センテンス単位または段落単位
日本語対応 句点(。)、改行単位が有効
トークン制御方法 tiktokennltk等で実現可能

日本語向け チャンク処理例(簡易版)

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構成では、次のようなフローになります:

  1. ドキュメントをチャンク分割(トークン数を意識)
  2. 各チャンクをEmbeddingしてChromaに格納
  3. ユーザークエリもEmbeddingして検索
  4. 上位チャンクをLLMに渡して回答を生成

この精度は、チャンク設計 × 埋め込みモデル精度の掛け合わせで決まります。


🧪 応用・実戦投入パターン

用途 内容
社内PDF資料の検索 PDF → テキスト抽出 → Chunk → Embedding → 検索
顧客対応履歴のFAQ化 チャットログから有用な応答を抽出し、ナレッジ化
コードスニペット検索 関数やコメントを文脈ごとにEmbeddingして構造化

✅ まとめ

  • Chromaはベクトル検索の学習・試作に最適な選択肢
  • チャンク処理は、トークン数と意味の滑らかさのトレードオフ設計が重要
  • Embeddingモデルは用途と対象言語で慎重に選定すべき
  • 本番運用では、FastAPIやLangChainとの統合も視野に

📎 参考リンク


👋 最後に

この構成はPoC(概念実証)だけでなく、本番運用にも十分適用可能です。
今後、LangChainとの統合、FastAPIによるAPI化、OpenAI APIとの連携例なども別途ご紹介予定です。

Discussion