ChromaDBのインストールと使い方:AIエージェントの知識ストレージシステムを構築するためのオープンソースVector Database

Database tutorial - IT technology blog
Database tutorial - IT technology blog

ChromaDBをインストールして5分で動かす

AI AgentやRAGシステムを構築しているなら、ChromaDBはすぐに知っておくべきツールだ。まずコードから入り、説明は後でする。

インストール:

pip install chromadb
# ローカルのembeddingモデルが必要な場合
pip install chromadb sentence-transformers

初期化してすぐにデータを追加:

import chromadb

# インメモリクライアント — 素早くテストでき、ファイル不要
client = chromadb.Client()

# collectionを作成(SQLのtableに相当)
collection = client.create_collection(name="my_knowledge_base")

# documentsを追加
collection.add(
    documents=[
        "Dockerはアプリケーションのコンテナ化プラットフォーム",
        "Kubernetesは大規模なコンテナ管理ツール",
        "Redisは高速インメモリデータベース"
    ],
    ids=["doc1", "doc2", "doc3"]
)

# セマンティック検索
results = collection.query(
    query_texts=["container orchestration tool"],
    n_results=2
)

print(results['documents'])
# Output: [['Kubernetesは大規模なコンテナ管理ツール', 'Dockerはアプリケーションのコンテナ化プラットフォーム']]

以上。これがChromaDBの基本だ — SQLを一行も書かずにセマンティック検索ができる。

ChromaDBとは?なぜ必要なのか?

ChromaDBはオープンソースのベクターデータベースで、元々AIアプリケーション向けに構築されたものだ。一般的なデータベースとの違いは、完全一致(exact match)ではなく意味的類似性(semantic similarity)で検索する点にある — “container orchestration”とクエリすれば、単語が一致しなくてもKubernetesに関する結果が返ってくる。

以前、チームのドキュメントを検索する社内ツールを構築したとき、Elasticsearchのフルテキスト検索を試したが、結果はかなり良くなかった。ユーザーが「コンテナがクラッシュする」と入力しても「KubernetesのPod再起動ループ」という記事が見つからない。ChromaDBとembeddingに切り替えたところ、結果は明らかに改善された。

ChromaDBはどのように動作するのか?

  1. 入力テキスト → Embeddingモデルがベクトル(実数の配列)に変換
  2. ベクトルはオプションのメタデータとともにChromaDBに保存される
  3. クエリ時、クエリテキストもベクトルにembedされる
  4. ChromaDBがコサイン類似度を計算し、最も近いdocumentsを返す

デフォルトでChromaDBはsentence-transformersのall-MiniLM-L6-v2を使用している — 軽量で高速、ほとんどのユースケースで十分だ。より高い精度が必要なら、OpenAI embeddingsやカスタムモデルに切り替えることもできる — collection作成時にembedding functionを変えるだけでいい。

Persistent Storage:データをファイルに保存

インメモリクライアントは素早いテストに便利だが、再起動するとデータがすべて消える。本番環境ではPersistentClientを使おう:

import chromadb

# ローカルディレクトリに保存
client = chromadb.PersistentClient(path="./chroma_db")

collection = client.get_or_create_collection(
    name="devops_knowledge",
    metadata={"hnsw:space": "cosine"}  # コサイン類似度
)

データはdocumentsを追加した時点で./chroma_dbに保存される。プロセスを再起動してもデータはそのまま残る。

AI AgentのためのKnowledge Baseを構築する

RAG(Retrieval-Augmented Generation)は、自分がChromaDBを最もよく使うユースケースだ。ドキュメント全体をLLMのコンテキストに詰め込む代わりに(50ページのdocsファイルだけで~40kトークンを消費する)、ChromaDBに保存して必要なときに関連する3〜5段落だけをretrieveする。

ChromaDBへのドキュメントのインデックス作成

import chromadb
from pathlib import Path

client = chromadb.PersistentClient(path="./knowledge_db")
collection = client.get_or_create_collection("docs")

def index_markdown_files(docs_dir: str):
    docs_path = Path(docs_dir)

    for md_file in docs_path.glob("**/*.md"):
        content = md_file.read_text(encoding="utf-8")

        # 段落ごとにチャンク化し、短すぎる段落はスキップ
        chunks = [c.strip() for c in content.split("\n\n") if len(c.strip()) > 50]

        if not chunks:
            continue

        collection.add(
            documents=chunks,
            ids=[f"{md_file.stem}_{i}" for i in range(len(chunks))],
            metadatas=[{"source": str(md_file), "chunk": i} for i in range(len(chunks))]
        )
        print(f"Indexed {md_file.name}: {len(chunks)} chunks")

index_markdown_files("./docs")

コンテキストのクエリとretrieve

def ask_knowledge_base(question: str, n_results: int = 3) -> str:
    results = collection.query(
        query_texts=[question],
        n_results=n_results,
        include=["documents", "metadatas", "distances"]
    )

    contexts = results["documents"][0]
    sources = [m["source"] for m in results["metadatas"][0]]
    distances = results["distances"][0]

    # distance < 1.5 = 関連あり(コサイン距離:0=同一、2=完全に異なる)
    relevant = [
        (ctx, src)
        for ctx, src, dist in zip(contexts, sources, distances)
        if dist < 1.5
    ]

    if not relevant:
        return "関連情報が見つかりませんでした。"

    return "\n---\n".join([ctx for ctx, _ in relevant])

context = ask_knowledge_base("systemdでサービスを再起動するには?")
print(context)

Metadata Filtering:条件付き検索

collectionに複数種類のドキュメントが含まれている場合、Metadata filterで検索範囲を絞り込める — 関連性が高い結果が得られ、速度も上がる:

# Linuxに関するdocsのみ検索
results = collection.query(
    query_texts=["ディスクのマウント方法"],
    n_results=3,
    where={"category": "linux"}
)

# 複数条件の組み合わせ
results = collection.query(
    query_texts=["backup database"],
    n_results=5,
    where={
        "$and": [
            {"category": {"$in": ["postgresql", "mysql"]}},
            {"language": "vi"}
        ]
    }
)

DockerでChromaDB Serverを実行

複数のサービスや複数の人が共有して使う場合、ChromaDBをDockerで独立して動かすのが最もクリーンな方法だ:

docker run -d \
  --name chromadb \
  -p 8000:8000 \
  -v ./chroma_data:/chroma/chroma \
  chromadb/chroma:latest

# サーバーが起動しているか確認
curl http://localhost:8000/api/v1/heartbeat
# PythonからConnect
import chromadb

client = chromadb.HttpClient(host="localhost", port=8000)
collection = client.get_or_create_collection("shared_knowledge")

他のサービスと統合したい場合はDocker Composeも使える:

version: "3.8"
services:
  chromadb:
    image: chromadb/chroma:latest
    ports:
      - "8000:8000"
    volumes:
      - chroma_data:/chroma/chroma
    environment:
      - ALLOW_RESET=true  # 開発時のみ有効にする

volumes:
  chroma_data:

ChromaDBを使う際の実践的なTips

Chunk sizeは検索品質に大きく影響する

実験を通じて、300〜500トークン程度のチャンクが最良の結果をもたらすことがわかった。小さすぎるとコンテキストが失われ、大きすぎるとembeddingが要点を捉えられなくなる。長い技術ドキュメントにはスライディングウィンドウを使い、隣接するチャンク間で20%のオーバーラップを持たせることで境界での情報損失を防ぐ。

source metadataは必ず完全に保存する

retrieveした後に情報の出所がわからないのでは意味がない — citationもできないし、デバッグもできない。最低限、source file、セクションタイトル、更新日は保存しておこう。以前、sourceを保存し忘れて500ファイル全体を再インデックスするはめになった — このステップを省いただけで2時間を無駄にした。

documentの更新にはupsertを使う

# ChromaDBには独立したUPDATEがない — upsertを使う
collection.upsert(
    documents=["更新されたコンテンツ"],
    ids=["doc1"]  # 既存のID → 上書き
)

ChromaDBのバックアップは非常に簡単

PersistentClientのデータは、指定したディレクトリにSQLite + バイナリファイル形式で保存される。従来のデータベースと比べてバックアップがずっと簡単だ — ディレクトリをそのままコピーすれば完了:

# 手動バックアップ
cp -r ./chroma_db ./chroma_db_backup_$(date +%Y%m%d)

# 容量節約のため圧縮
tar -czf chroma_backup_$(date +%Y%m%d).tar.gz ./chroma_db

ChromaDBかQdrant — どちらを選ぶか?

ブログにはQdrantについて別の記事があるので、ここでは簡単にまとめる:ChromaDBの方がインストールが簡単でPython-friendlyなAPIを持ち、素早いプロトタイプや小規模チームに向いている。Qdrantはより大規模なproduction環境、複雑なfiltering、多言語クライアントのサポートで優れている。サイドプロジェクトや社内ツールを作っているなら?ChromaDBから始めよう。後でスケールアップするときにQdrantへ移行しても遅くはない。

Share: