embed-tree
embed-tree turns content nodes into a browsable, labeled hierarchy.
The public model is intentionally small:
ContentNode(id=..., text=..., metadata={...})
BranchNode(id, label=None, children=[])
EmbedTree(embedder, config=None, state=None, labeler=None)
ContentNode.text is the string passed to the embedder. metadata is opaque
user data returned by queries and preserved in exported branches.
Install
pip install embed-tree
Optional integrations:
pip install "embed-tree[openai]"
pip install "embed-tree[local]"
pip install "embed-tree[sql]"
Quick Start
from embed_tree import ContentNode, EmbedTree, TagSetEmbedder, TreeConfig
nodes = [
ContentNode(id="doc-1", text="import pipeline docs", metadata={"tags": ["docs", "ingest"]}),
ContentNode(id="doc-2", text="retry handling for ingestion", metadata={"tags": ["ingest"]}),
ContentNode(id="doc-3", text="summary generation latency", metadata={"tags": ["analysis"]}),
ContentNode(id="doc-4", text="schema mapping examples", metadata={"tags": ["docs", "schemas"]}),
]
tree = EmbedTree(
embedder=TagSetEmbedder(["docs", "ingest", "analysis", "schemas"]),
config=TreeConfig(max_branches=4, leaf_target=2),
)
tree.add_nodes(nodes)
tree.organize() # rebalance the hierarchy, then label each branch
print(tree.show())
branch = tree.to_branch()
Use a real text embedder in production:
from embed_tree import ContentNode, EmbedTree, OpenAITextEmbedder
tree = EmbedTree(OpenAITextEmbedder(model="text-embedding-3-small", api_key="..."))
tree.add_node(
ContentNode(
id="doc-1",
text="Some document summary",
metadata={"source": "docs"},
)
)
Core API
tree.add_node(ContentNode(...)) # -> id
tree.add_nodes([ContentNode(...)]) # -> list[id]
tree.add_branch(BranchNode(...)) # -> list[id], inserts all content leaves
tree.query("query text", k=10, exhaustive=False)
tree.remove(node_id)
tree.remove_batch([node_id])
tree.rebalance()
tree.label(labeler=None)
tree.organize(labeler=None) # rebalance + re-label
tree.to_branch(max_items=None)
tree.show(max_items=3)
len(tree)
BranchNode is the public tree shape. It can represent an input branch from a
loader or the organized output from EmbedTree.to_branch().
TreeConfig.split_mode controls how branches are discovered:
TreeConfig(split_mode="fixed") # default: capacity-driven KMeans
TreeConfig(split_mode="adaptive") # choose k only when a split improves cohesion
leaf_target is the desired maximum number of content items to leave directly
under one leaf before attempting a split. In adaptive mode, max_branches is
the maximum candidate fan-out, not the target fan-out. min_cluster_size,
min_parent_dispersion, parent_dispersion_decay, min_split_gain, and
min_split_gain_ratio control when a branch is coherent enough to keep
together. The legacy leaf_capacity and min_samples_to_split names are still
accepted as aliases for leaf_target.
Enable split diagnostics with standard Python logging:
import logging
logging.basicConfig(level=logging.INFO)
config = TreeConfig(split_mode="adaptive", log_split_decisions=True)
This logs candidate k values, depth, cluster sizes, cohesion gain, relative gain, separation, imbalance, final accept/skip decisions, and skip reasons.
For folder-based trees, FileSystemTreeLoader uses the file content MD5 as
id. Its optional text_generator(path, raw_text) can derive the embed text
from raw file text while preserving file identity. Its optional
additional_metadata_derivers is a list of callables that derive metadata such
as new_file_name from file content; derived dictionaries are merged in order,
with later keys winning. FolderTreePersister moves existing files only when a
node has a content MD5 as its id or explicit MD5 metadata and that MD5 exists
under the current root. If no current file matches, path metadata can point to a
source file to copy when its MD5 matches the same identity. If neither exists,
missing_node_file controls the result: "skip" warns and skips by default,
"create" writes a .txt snapshot containing text and metadata, and
"raise" raises MissingNodeFileError. new_file_name can rename moved/copied
files or snapshots.
EmbedTree has internal runtime nodes and content records which are not
public API.
Persistence
Use a state loader that can save materialized state:
from embed_tree import EmbedTree, JsonTreeLoader
tree = EmbedTree(embedder, state=JsonTreeLoader("./tree.json"))
Development
uv sync --extra dev
uv run --extra dev pytest -q
Metadata
Release files for embed-tree 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| embed_tree-0.1.4.tar.gz | 163.5 kB | With provenance | Details for embed_tree-0.1.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| embed_tree-0.1.4-py3-none-any.whl | Python 3 | none | any | Details for embed_tree-0.1.4-py3-none-any.whl |
Total release size: 207.3 kB
Release files / embed_tree-0.1.4.tar.gz
| Download URL | embed_tree-0.1.4.tar.gz |
|---|---|
| Size | 163.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd4ade4aa04c198da2f394ad1d6aa2577163f516a4008acd63fa2505638dbd04
|
|
BLAKE2b-256 checksum How to use checksums |
76b1bb49350bccf0e641ff9f05e7d49ea8b9de205ef1c65e078439f7b0a4f9ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.
Transparency logRelease files / embed_tree-0.1.4-py3-none-any.whl
| Download URL | embed_tree-0.1.4-py3-none-any.whl |
|---|---|
| Size | 43.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
157b01aa598b132ee5f9b1bf46c0ff7fa3ca402d874b06974198334994eae9d0
|
|
BLAKE2b-256 checksum How to use checksums |
93bc037caacc16fd30641f93cbe45e145b6ddd064b9483e7dc785f7edfc6b6fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.
Transparency logSecurity for embed-tree 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for embed_tree-0.1.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.