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はどのように動作するのか?
- 入力テキスト → Embeddingモデルがベクトル(実数の配列)に変換
- ベクトルはオプションのメタデータとともにChromaDBに保存される
- クエリ時、クエリテキストもベクトルにembedされる
- 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へ移行しても遅くはない。
