-
Notifications
You must be signed in to change notification settings - Fork 29
Toponymy Architecture
Toponymy builds hierarchical topic models from a collection of objects and a layered clustering structure.
At a high level, it:
- Clusters objects into a hierarchy of increasingly broad topic layers.
- Extracts useful features for each cluster, such as exemplar texts and keyphrases.
- Processes layers from the most detailed layer upward.
- Builds an LLM prompt for each topic using the available per-cluster features.
- Names topics with an LLM and optionally disambiguates similar names.
- Stores learned topics and inspectable intermediate results in a
TopicModel.
The architecture separates reusable clustering from topic-model-specific enrichment. A Clusterer describes the hierarchy; a TopicModel describes the resulting topics.
flowchart TD
Objects[Objects] --> FeatureExtractors[Feature extractors]
ObjectVectors[Embedding vectors] --> FeatureExtractors
ClusterableVectors[Clusterable vectors] --> Clusterer
Clusterer -->|cluster layers and tree| TopicModel
FeatureExtractors -->|per-cluster features| TopicModel
TopicModel --> CurrentLayer[Current topic layer]
CurrentLayer --> DependentFeatures[Optional layer-dependent feature extractors]
DependentFeatures --> Template
Template --> LLMNamer
LLMNamer -->|topic names and results| TopicModel
The architecture processes Objects (the raw data items or documents) using two different vector formats. Embedded Vectors are the rich, high-dimensional machine learning representations of these objects that retain deep semantic meaning used during the information extraction and exemplar selection stages. These are often created via neural embedding techniques but can be generated by any vectorization process. Clusterable Vectors are low-dimensional representations of these objects optimized for fast, multi-scale grouping. These are often created by performing dimension reduction on the high dimensional Embedding Vectors.
Toponymy processes layers from most detailed to broadest. The optional layer-dependent feature-extraction step is skipped for the base layer, because no lower layers have yet been named. For later layers, configured LayerDependentFeatureExtractor instances read completed lower-layer results from the TopicModel and add further per-cluster features, such as subtopics.
Feature extractors store their results in the TopicModel. Templates do not communicate with extractors directly; when constructing a prompt for a topic, a template reads the relevant cluster data and features from the TopicModel.
Each component has a focused responsibility:
-
Clustererowns clustering structure. -
FeatureExtractorowns per-cluster feature extraction before topic naming. -
LayerDependentFeatureExtractorowns per-cluster features requiring completed lower layers. -
Templateowns prompt construction. -
LLMNamerowns communication with a language model. -
Embedderowns generation of new semantic vectors. -
TopicModelowns learned topic information and intermediate outputs. -
Toponymyorchestrates these components.
A fitted model should expose more than final topic names. Users and developers should be able to inspect:
- cluster layers and hierarchy;
- per-cluster extracted features;
- rendered prompts;
- generated topic names;
- name embeddings, when generated;
- summaries and explanations, when enabled;
- disambiguation inputs and results.
This makes debugging, evaluation, serialization, and custom workflows practical.
A fitted Clusterer should remain reusable. Passing it to Toponymy must not add prompts, names, features, or other topic-model runtime state to the clusterer.
Topic-specific state belongs in TopicModel. Stage outputs should be explicit records and treated as immutable once produced.
Components follow scikit-learn-style conventions where practical:
- Constructor arguments configure an object.
-
fit(...)supplies data and learns state. - Learned attributes use an underscore suffix, such as
features_orcluster_layers_. - A component may be fitted before it is supplied to
Toponymy.
Toponymy is the primary user-facing estimator and workflow orchestrator.
It is configured with:
It is configured with:
- a
Clusterer; -
FeatureExtractorinstances; - optional
LayerDependentFeatureExtractorinstances; - a
Template; - an
LLMNamer; - an optional
Embedder.
FeatureExtractor instances derive per-cluster features before topic naming begins. LayerDependentFeatureExtractor instances derive per-cluster features after lower topic layers have been named.
During construction, Toponymy validates its configured components. In particular, it determines whether any configured extractor or post-processing component requires an embedder. If one is required but no embedder was supplied, construction fails with a useful error before clustering or LLM calls begin.
During fit(...), Toponymy coordinates clustering, per-cluster feature extraction, prompt construction, topic naming, and optional disambiguation. Its principal learned result is a TopicModel.
model = Toponymy(
clusterer=PLSCANClusterer(),
feature_extractors=[
ExemplarTextExtractor(),
],
template=DefaultTemplate(),
llm_namer=llm_namer,
)
model.fit(
objects=documents,
embedding_vectors=document_embeddings,
clusterable_vectors=reduced_embeddings,
)
topic_model = model.topic_model_The exact public constructor may evolve during the v0.6 refactor, but these responsibilities are the intended architecture.
A Clusterer produces a hierarchical clustering structure from vectors.
Its responsibilities are deliberately narrow:
- accept clusterable vectors in
fit(...); - produce
cluster_layers_; - produce
cluster_tree_; - validate that the hierarchy is internally consistent.
A clusterer does not construct prompts, extract topic features, call LLMs, or store topic names.
cluster_layers_ is a sequence of label arrays, one for each hierarchy layer.
- Layer
0is the most detailed layer. - Higher layer indices represent broader groupings.
- Each label array has one entry per input object.
- Non-negative integers identify clusters.
- The label
-1identifies background noise, following the HDBSCAN convention.
For example:
cluster_layers_ = [
[0, 0, 1, 1, -1], # detailed topics
[0, 0, 0, 0, -1], # broader topic
]cluster_tree_ describes inclusion relationships between clusters in adjacent layers. It allows Toponymy to understand which detailed topics contribute to each broader topic.
The intended built-in clusterers are:
-
PLSCANClusterer, based onfast_hdbscan, and the default clusterer; -
EVoCClusterer, based on EVoC, where dependency compatibility permits; - a precomputed or label-based clusterer for user-supplied layer labels.
A clusterer may be fitted independently before it is supplied to Toponymy. This enables inspection of topic counts and hierarchy shape before any LLM calls occur.
TopicModel is the learned, topic-oriented result of running Toponymy.
It is initialized from a cluster hierarchy and progressively enriched as the workflow runs. It is the primary place for users and developers to inspect a fitted model.
A TopicModel owns:
- the topic hierarchy and layer structure;
- topic-level metadata;
- per-cluster feature outputs produced before naming;
- per-cluster feature outputs dependent on previously named layers;
- prompts;
- generated names;
- name embeddings, where applicable;
- summaries and explanations, when used;
- disambiguation results;
- serialization-friendly representations of the learned model.
This separates the question “how were these objects clustered?” from the question “what topics did we learn from those clusters?”
The existing serialization-oriented TopicModel API is part of the public compatibility surface. The runtime topic model should evolve that type rather than create an unrelated competing representation.
A FeatureExtractor derives useful information for each cluster before topic naming begins.
Typical examples include:
- exemplar texts;
- keyphrases;
- contrastive terms;
- TreeSHAP-derived feature descriptions;
- image captions or visual exemplars;
- structured metadata summaries.
A feature extractor produces a learned features_ output aligned to clusters and layers. It should retain derived features rather than large raw input objects whenever possible. This keeps memory use and serialized models manageable.
Some extractors can be fitted using standard inputs available to Toponymy, such as:
-
objects; -
embedding_vectors; -
clusterable_vectors; - the cluster hierarchy.
These extractors may be automatically fitted during Toponymy.fit(...).
Other extractors require custom data, such as a DataFrame containing domain-specific metadata. Those extractors should be fitted explicitly before being passed to Toponymy.
metadata_extractor = MetadataFeatureExtractor()
metadata_extractor.fit(
metadata=metadata_dataframe,
clusterer=clusterer,
)
model = Toponymy(
clusterer=clusterer,
feature_extractors=[metadata_extractor],
template=DefaultTemplate(),
llm_namer=llm_namer,
)This preserves flexibility without forcing Toponymy to store arbitrary user data.
A LayerDependentFeatureExtractor derives per-cluster features that require results from previously named layers.
Unlike ordinary FeatureExtractor instances, it cannot run before topic naming begins. It is skipped for layer 0, then runs once per higher layer after its required lower-layer results are available.
The central example is SubtopicExtractor:
- detailed topics are named first;
- their names and embeddings become evidence for broader topics;
- the extractor derives relevant subtopics for each broader cluster;
- those subtopics are stored in the
TopicModel; - the template reads them when constructing prompts for the higher layer.
Both extractor types produce per-cluster results. The difference is temporal:
-
FeatureExtractorresults do not depend on generated topic names. -
LayerDependentFeatureExtractorresults do depend on outputs from earlier naming stages.
A Template defines how topic-model data and extracted features become prompts for an LLM.
A template receives typed information for a particular topic from the TopicModel, such as:
- the current layer and cluster;
- exemplar texts;
- keyphrases;
- subtopics;
- corpus and object descriptions;
- requested detail level;
- prior topic names, summaries, and explanations.
It produces a prompt record that can be inspected and stored in the TopicModel.
Templates should arrange information; they should not perform clustering, derive features, or call an LLM.
An LLMNamer wraps a language-model API or local language model used to generate topic names.
It receives rendered prompts and returns structured naming results, such as:
- a topic name;
- a summary;
- an explanation;
- optional provider metadata or raw response data.
The wrapper isolates provider-specific behavior from the topic-model workflow.
An Embedder produces vector representations for new text or other objects.
It is separate from LLMNamer because an embedding model may be local, API-backed, or entirely different from the model used for naming.
An embedder is optional for Toponymy itself. It is required only when a selected component needs to embed newly created content, such as keyphrases or generated topic names.
Existing embedding_vectors supplied to fit(...) represent the original objects. They do not replace an embedder when a workflow must embed new strings produced during fitting.
An Embedder is optional for Toponymy itself. It is required only when a configured component must create vector representations for newly generated text, such as keyphrases or topic names.
The embedding_vectors supplied to fit(...) represent the original objects. They do not replace an embedder when the selected pipeline needs vectors for new strings produced during fitting.
| Component | Embedder requirement | Reason |
|---|---|---|
ExemplarTextExtractor |
No | It can use supplied object embeddings or other selection methods. |
KeyphraseExtractor |
Yes | Semantic keyphrase selection and cluster-level keyphrase ranking use keyphrase vectors. |
SubtopicExtractor |
Yes | It compares prior topic names using topic-name embeddings. |
Disambiguator |
Declared by the implementation | Some methods identify similar names through embeddings; others can use text matching or LLM-only comparison. |
| Naming from exemplars only | No | The LLM can name topics directly from exemplar text. |
Toponymy validates these requirements during initialization. If a configured component requires an embedder but none was supplied, it raises a clear error before clustering or LLM calls begin.
Toponymy(
clusterer=PLSCANClusterer(),
feature_extractors=[
ExemplarTextExtractor(),
KeyphraseExtractor(),
],
template=DefaultTemplate(),
llm_namer=llm_namer,
)This configuration should raise an error similar to:
KeyphraseExtractor requires an embedder. Pass embedder=... or remove
KeyphraseExtractor from feature_extractors.
When an embedder-dependent component needs representations of generated topic names, Toponymy creates and retains those embeddings in the TopicModel as part of that component’s workflow.
This is the intended lightweight default. It requires a clusterer and an LLM namer, but no embedder for newly generated text.
Toponymy(
clusterer=PLSCANClusterer(),
feature_extractors=[ExemplarTextExtractor()],
template=DefaultTemplate(),
llm_namer=llm_namer,
)Keyphrases are optional rather than default-enabled. They can be useful for distinctiveness and may reduce later disambiguation work, but current evaluation evidence does not show a general topic-quality benefit sufficient to require their cost in every workflow.
Toponymy(
clusterer=PLSCANClusterer(),
feature_extractors=[
ExemplarTextExtractor(),
KeyphraseExtractor(),
],
template=DefaultTemplate(),
llm_namer=llm_namer,
embedder=embedder,
)Subtopics are layer-dependent per-cluster features. They require a clusterer, detailed-layer names, and an embedder for those names.
Toponymy(
clusterer=PLSCANClusterer(),
feature_extractors=[ExemplarTextExtractor()],
layer_dependent_feature_extractors=[SubtopicExtractor()],
template=DefaultTemplate(),
llm_namer=llm_namer,
embedder=embedder,
)Toponymy processes topic layers from the most detailed layer upward.
For each layer:
- Obtain the current layer’s clusters from the
TopicModel. - For layers above the base layer, run configured
LayerDependentFeatureExtractorinstances. - Read the current cluster’s available features from the
TopicModel. - Use the selected
Templateto render prompts. - Use the
LLMNamerto generate topic names and related outputs. - Optionally disambiguate names that are too similar.
- Generate topic-name embeddings when selected later stages require them.
- Store all results in the
TopicModel.
The ordering matters because a broad topic may be named using completed names from detailed topics beneath it.
A new clusterer should:
- Implement the clusterer interface.
- Produce valid
cluster_layers_andcluster_tree_. - Preserve
-1as the noise label. - Avoid topic-model-specific state.
- Include tests for hierarchy validity and pre-fitted reuse.
A new feature extractor should:
- Implement the feature extractor interface.
- Define its per-cluster fitted
features_output clearly. - State whether Toponymy can fit it from standard inputs.
- Declare whether it requires an embedder.
- Avoid retaining unnecessary raw input data.
- Include unit tests for feature shape, cluster alignment, and fitted-state behavior.
A layer-dependent extractor should:
- Define which prior-layer outputs it requires.
- Accept the current
TopicModeland target layer. - Declare whether it requires an embedder.
- Produce a layer-scoped, per-cluster immutable feature result.
- Include a multi-layer test proving it runs only after prerequisites are available.
A new template should:
- Accept the documented per-topic prompt inputs from the
TopicModel. - Produce deterministic prompt records for deterministic inputs.
- Avoid direct LLM calls.
- Include fixture-based tests for rendered prompt content.
This architecture is the target direction for the v0.6 refactor. During the transition, compatibility adapters may preserve older interfaces such as ToponymyClusterer, ClusterLayer behavior, existing prompt-template dictionaries, and serialization methods.
New development should prefer these ownership boundaries:
- clustering structure belongs to
Clusterer; - learned topic information belongs to
TopicModel; - orchestration belongs to
Toponymy; - prompt formatting belongs to
Template; - pre-naming and layer-dependent per-cluster feature derivation belongs to extractor classes;
- optional vector generation belongs to
Embedder.