Enquiry 6
Summary¶
ChromaDB is a genuine embedded in-process library that requires no separate server. Its default embedding function uses ONNX Runtime (not PyTorch), keeping the total install around ~150 MB on disk — far less than the "2.2 GB with PyTorch" figure from prior research. However, ChromaDB ships a monolithic dependency set (grpcio, kubernetes, uvicorn, opentelemetry) regardless of deployment mode, which is bloated for pure embedded use. Collections cannot be queried across simultaneously — for picobot's KB topics, a single collection with a topic metadata field is the correct architecture.
(a) ChromaDB Client Modes¶
ChromaDB has three client modes, two of which are fully embedded (no server process):
| Mode | Constructor | Storage | Server Needed? |
|---|---|---|---|
| Ephemeral | chromadb.EphemeralClient() |
In-memory only | No |
| Persistent | chromadb.PersistentClient(path="./chroma") |
Local disk (SQLite + binary files) | No |
| Client-Server | chromadb.HttpClient(host, port) |
Remote server | Yes (separate chroma run) |
Ephemeral — data lives only in process memory, lost on exit. Good for tests and one-shot scripts.
Persistent — data written to a directory on disk. This is the mode relevant to picobot. It is fully in-process: ChromaDB runs inside your Python process with no sockets, no background threads serving HTTP. The persist directory contains SQLite databases and binary HNSW index files. You can ship it bundled with your application.
Client-Server — connects over HTTP/gRPC to a separately running Chroma server. There is also a chromadb-client thin-client pip package (~22 MB) for this mode that strips out onnxruntime and embedding functions.
For picobot, PersistentClient is the right choice — embedded, no server, data at ~/.picobot/data/chroma/.
(b) Dependency Tree — Does It Pull In PyTorch?¶
The definitive answer: No, ChromaDB does NOT require PyTorch.¶
ChromaDB's pyproject.toml (from chroma-core/chroma main branch) lists these required dependencies:
onnxruntime >= 1.14.1 # ML inference runtime (ONNX, not PyTorch)
tokenizers >= 0.13.2 # Rust-based tokenizer (HuggingFace)
numpy >= 1.22.5
grpcio >= 1.58.0 # gRPC (for internal architecture)
kubernetes >= 28.1.0 # K8s client (!)
uvicorn[standard] >= 0.18.3 # ASGI server
opentelemetry-api, -sdk, -exporter-otlp-proto-grpc # Telemetry
pydantic, httpx, orjson, rich, typer, tqdm, bcrypt, pypika, mmh3,
posthog, pybase64, PyYAML, jsonschema, tenacity, overrides,
importlib-resources, build
PyTorch is NOT listed. Neither is sentence-transformers nor torch.
The prior research claiming "ChromaDB + PyTorch = 2.2 GB" was based on using the SentenceTransformer embedding function (chromadb.utils.embedding_functions.SentenceTransformerEmbeddingFunction), which is an optional integration that requires you to pip install sentence-transformers, which in turn pulls in PyTorch. But this is user-chosen, not a ChromaDB requirement.
Default Embedding Function: ONNX-based, No PyTorch¶
ChromaDB's default embedding function is ONNXMiniLM_L6_V2. It:
- Uses the all-MiniLM-L6-v2 model converted to ONNX format
- Runs on onnxruntime (CPU, ~17 MB wheel)
- Downloads the ONNX model (~79 MB) on first use to ~/.cache/chroma/onnx_models/all-MiniLM-L6-v2/
- Produces 384-dimensional dense vectors
- Requires zero configuration — just collection.add(documents=[...]) and it embeds automatically
No API keys, no external services, no PyTorch.
Estimated Total Install Size¶
Individual package wheel sizes (macOS ARM64):
| Package | Wheel Size | Notes |
|---|---|---|
| chromadb | ~20 MB | Core package (includes compiled Rust via maturin) |
| onnxruntime | ~17 MB | ONNX inference engine |
| grpcio | ~12 MB | gRPC (largest transitive dep) |
| numpy | ~7 MB | Array operations |
| tokenizers | ~3 MB | Rust-based tokenizer |
| kubernetes | ~2 MB | K8s client (unnecessary for embedded!) |
| uvicorn + httptools + etc. | ~5 MB | ASGI server stack |
| opentelemetry-* (3 packages) | ~3 MB | Telemetry |
| All other small deps | ~10 MB | pydantic, httpx, orjson, rich, etc. |
| Subtotal (wheels) | ~80 MB | |
| ONNX model (first-run download) | ~79 MB | Cached in ~/.cache/chroma/ |
| Total on disk | ~150-180 MB | Installed packages + cached model |
This is roughly 15x smaller than the 2.2 GB PyTorch-based setup. The estimate could be ~200 MB with all transitive dependencies fully unpacked, but the order of magnitude is correct: hundreds of MB, not gigabytes.
Bloat Concern¶
ChromaDB ships a single monolithic package for all deployment modes. Even in embedded mode you get:
- kubernetes (K8s Python client) — completely unnecessary for embedded
- grpcio — used for internal component communication and telemetry export
- uvicorn — needed only for server mode
- opentelemetry-exporter-otlp-proto-grpc — telemetry exporting
These add ~20 MB of wheels that serve no purpose in embedded mode. There is no chromadb[lite] or chromadb[embedded] extras option. This is a known community complaint but the Chroma team has not split the package.
Can You Avoid onnxruntime Entirely?¶
Yes, but you must provide your own embedding function. If you pass pre-computed embeddings directly:
collection.add(
ids=["id1"],
embeddings=[[0.1, 0.2, ...]], # pre-computed vectors
documents=["text"],
metadatas=[{"topic": "self"}]
)
...then onnxruntime is imported but never used. However, it is still a required install dependency — you cannot pip install chromadb without it being installed. You would need to either:
1. Accept the ~17 MB onnxruntime overhead (it just sits unused), or
2. Fork/patch chromadb to make onnxruntime optional (not practical)
For picobot, accepting the 17 MB is fine — it buys us a working default embedder with zero config.
(c) Collections: Can You Have One Per KB Topic?¶
Yes, But You Shouldn't¶
ChromaDB supports multiple collections within a single persistent database:
client = chromadb.PersistentClient(path="./chroma")
self_col = client.get_or_create_collection("kb-self")
user_col = client.get_or_create_collection("kb-user")
projects_col = client.get_or_create_collection("kb-projects")
domains_col = client.get_or_create_collection("kb-domains")
journal_col = client.get_or_create_collection("kb-journal")
Each collection has its own HNSW index, its own metadata namespace, and its own embedding function (or shared default).
Cross-Collection Search: NOT Supported¶
ChromaDB has no native cross-collection query. You cannot do:
# THIS DOES NOT EXIST
client.query_all_collections(query_texts=["something"])
If you use separate collections per topic, searching across the entire KB requires:
# Manual fan-out — ugly and slow
results = []
for col_name in ["kb-self", "kb-user", "kb-projects", "kb-domains", "kb-journal"]:
col = client.get_collection(col_name)
r = col.query(query_texts=["search term"], n_results=5)
results.extend(zip(r["distances"][0], r["documents"][0], r["metadatas"][0]))
# Now manually sort by distance and take top-k
results.sort(key=lambda x: x[0])
top_results = results[:10]
This has multiple problems: 1. Five separate HNSW searches instead of one 2. Manual result merging and re-ranking 3. Distance scores from different HNSW indices are not directly comparable (different index structures) 4. More complex code for every query path
Recommended: Single Collection with Metadata¶
The ChromaDB community consensus and official recommendation is to use one collection with a metadata field for categorization:
collection = client.get_or_create_collection("kb")
# Add with topic metadata
collection.add(
ids=["self-identity-001"],
documents=["I am a research agent focused on..."],
metadatas=[{"topic": "self", "title": "Identity", "tags": "identity,core"}]
)
# Search across all topics
collection.query(query_texts=["research methodology"], n_results=10)
# Search within a specific topic
collection.query(
query_texts=["research methodology"],
n_results=10,
where={"topic": "projects"}
)
This gives you:
- Single HNSW index = one search, globally comparable distances
- Topic-scoped search via where filter = equivalent to collection-per-topic
- Cross-topic search for free (omit the where clause)
- Simpler code, fewer moving parts
(d) Metadata Filtering Alongside Vector Search¶
ChromaDB supports rich metadata filtering combined with vector similarity in a single query. This is crucial for picobot's KB architecture.
Supported Operators¶
| Operator | Example | Description |
|---|---|---|
$eq |
{"topic": "self"} |
Equality (implicit default) |
$ne |
{"topic": {"$ne": "journal"}} |
Not equal |
$gt, $gte |
{"updated": {"$gt": "2025-01-01"}} |
Greater than |
$lt, $lte |
{"priority": {"$lt": 5}} |
Less than |
$in |
{"topic": {"$in": ["self", "user"]}} |
Value in list |
$nin |
{"topic": {"$nin": ["journal"]}} |
Value not in list |
$contains |
{"tags": {"$contains": "python"}} |
Array contains value |
$and |
{"$and": [cond1, cond2]} |
All conditions must match |
$or |
{"$or": [cond1, cond2]} |
Any condition matches |
Combined Vector + Metadata Query¶
# Semantic search for "deployment strategy" but only in projects and domains topics
results = collection.query(
query_texts=["deployment strategy"],
n_results=5,
where={"$or": [
{"topic": "projects"},
{"topic": "domains"}
]}
)
This performs vector similarity search after applying the metadata filter, returning only results from the specified topics ranked by semantic relevance.
Document Content Filtering¶
ChromaDB also supports where_document for filtering on the document text itself:
results = collection.query(
query_texts=["python packaging"],
where_document={"$contains": "pyproject.toml"},
n_results=5
)
This is a substring/keyword filter on the raw document text, applied alongside the vector search.
(e) Total Install Size with Minimal Embedding Function¶
Scenario: ChromaDB with default ONNX embeddings (no PyTorch)¶
| Component | Size | Required? |
|---|---|---|
chromadb wheel |
~20 MB | Yes |
onnxruntime wheel |
~17 MB | Yes (hard dep) |
grpcio wheel |
~12 MB | Yes (hard dep) |
numpy wheel |
~7 MB | Yes (hard dep) |
tokenizers wheel |
~3 MB | Yes (hard dep) |
| Other required deps (~20 packages) | ~15 MB | Yes |
| Install total | ~75-80 MB | |
| ONNX model (auto-downloaded, cached) | ~79 MB | On first embed call |
| Grand total on disk | ~155-160 MB |
Comparison with alternatives¶
| Option | Install Size | Notes |
|---|---|---|
| ChromaDB + default ONNX | ~160 MB | Full vector DB, embedded, no PyTorch |
| ChromaDB + sentence-transformers + PyTorch | ~2.2 GB | What prior research assumed |
| sqlite-vec + manual embeddings | ~50 MB | No built-in embeddings, more DIY |
| sqlite-vec + onnxruntime (manual) | ~100 MB | Comparable to ChromaDB but more code |
ChromaDB's ~160 MB is reasonable for a CLI tool. The prior "2.2 GB" figure was a misattribution of PyTorch's weight to ChromaDB itself.
(f) Architecture Recommendation for Picobot¶
Proposed Design: Single Collection, Metadata-Based Topics¶
import chromadb
# Initialize once, store at known location
client = chromadb.PersistentClient(path=os.path.expanduser("~/.picobot/data/chroma"))
collection = client.get_or_create_collection(
name="knowledge-base",
metadata={"hnsw:space": "cosine"} # cosine similarity
)
Mapping KB Topics to Metadata¶
Each KB entry gets stored with structured metadata:
collection.add(
ids=[entry_id], # e.g., "self/identity" or a UUID
documents=[entry_content], # the markdown content of the KB entry
metadatas=[{
"topic": "self", # maps to KB topic dirs: self/, user/, projects/, etc.
"title": "Identity and Goals",
"tags": "identity,core,agent",
"created": "2025-06-01",
"updated": "2025-12-15",
"path": "self/identity.md" # original file path for reference
}]
)
Query Patterns¶
# Global semantic search (across all topics)
collection.query(query_texts=["how to handle API rate limits"], n_results=10)
# Topic-scoped search
collection.query(
query_texts=["deployment patterns"],
where={"topic": "projects"},
n_results=5
)
# Multi-topic search
collection.query(
query_texts=["user preferences"],
where={"topic": {"$in": ["user", "self"]}},
n_results=5
)
# Tag-based filtering
collection.query(
query_texts=["python"],
where={"tags": {"$contains": "python"}},
n_results=5
)
# Recent entries only
collection.query(
query_texts=["anything new"],
where={"updated": {"$gte": "2025-11-01"}},
n_results=10
)
Why NOT Collections-Per-Topic¶
| Aspect | Single Collection + Metadata | Collection Per Topic |
|---|---|---|
| Cross-topic search | Native, one query | Manual fan-out + merge |
| Distance comparability | Globally consistent | Not comparable across collections |
| Code complexity | Simple | Complex (loop, merge, re-rank) |
| Topic-scoped search | where={"topic": "X"} |
Direct query on collection |
| Index efficiency | One larger HNSW index | Five smaller indices |
| Adding new topics | Add metadata value | Create new collection + update all query code |
The single-collection approach is strictly better for picobot's use case.
Sync with Existing KB¶
ChromaDB would sit alongside the existing file-based KB, not replace it. The file system remains the source of truth:
- On
picobot kb syncor at session start, scan KB files for changes - Upsert changed entries into ChromaDB (by file path as ID)
- Delete ChromaDB entries for removed KB files
- ChromaDB provides fast semantic search; file system provides human-readable browsing and git sync
Key Consideration: onnxruntime Is a Hard Dependency¶
Even if picobot uses a different embedding strategy (e.g., API-based embeddings from the LLM provider), installing chromadb will pull in onnxruntime. This is unavoidable without forking. The ~17 MB overhead is acceptable.
If picobot wanted to avoid onnxruntime entirely, it would need to use sqlite-vec or another vector store that does not bundle an inference runtime. But then picobot would need to manage embeddings generation separately.
Key Takeaways¶
-
ChromaDB IS a true embedded library —
PersistentClientruns entirely in-process with no server, sockets, or background processes. -
PyTorch is NOT required — the default embedding function uses ONNX Runtime (~17 MB), not PyTorch (~2 GB). The "2.2 GB" figure from prior research was wrong.
-
Total install: ~160 MB — chromadb wheel + onnxruntime + grpcio + other deps + cached ONNX model. Reasonable for a CLI tool.
-
No cross-collection queries — use a single collection with a
topicmetadata field, not one collection per KB topic. -
Rich metadata filtering —
$eq,$in,$and,$or,$contains, etc., all combinable with vector similarity in a single query. This maps perfectly to KB topic/tag/date filtering. -
Bloat exists but is tolerable — grpcio, kubernetes, uvicorn are dragged in unnecessarily for embedded mode (~20 MB wasted). No
[lite]install option exists. -
For picobot: single
PersistentClientwith oneknowledge-basecollection, topic/title/tags/dates as metadata, cosine similarity, default ONNX embeddings. File-based KB remains source of truth; ChromaDB is a search index synced from it.