assort
Text clustering and sorting with an LLM that discovers categories, classifies items, optionally merges overlapping themes, and cleans up labels. Designed for quick drop in use with a single function call and clear cost tracking.
Highlights
- Discovers category names from your data
- Sorts each item with calibrated confidence per category
- Merges overlapping themes when the model judges a high likelihood of overlap
- Refines the miscellaneous bucket when it is too large
- Optionally renames categories to be clearer and more specific
- Tracks tokens and final cost in USD
- Simple one function API that returns results and rich stats
Install
pip install assort
You also need an OpenAI API key available to the runtime, for example
export OPENAI_API_KEY=sk_your_key_here
Quick start
from assort import assort
texts = [
"Build a responsive landing page in React",
"How to index a Postgres table",
"Cognitive behavioral therapy exercises",
"Vector search with Azure AI Search",
"Tailwind utility classes for layouts",
"Managing anxiety before a big presentation",
]
results, stats = assort(
texts,
min_clusters=3,
max_clusters=6,
description="Short notes that mix software topics and mental health topics",
)
print(results["sorted_results"])
Example shape of sorted_results
{
"Front End Engineering": [
"Build a responsive landing page in React",
"Tailwind utility classes for layouts"
],
"Data and Search": [
"Vector search with Azure AI Search",
"How to index a Postgres table"
],
"Anxiety and CBT": [
"Cognitive behavioral therapy exercises",
"Managing anxiety before a big presentation"
],
"Miscellaneous": []
}
API
assort
results, stats = assort(
batch,
min_clusters=2,
max_clusters=5,
description="",
model=None,
rename_final=True,
show_progress=False,
)
Parameters
-
batchList of strings to categorize. Empty or blank strings are ignored. -
min_clustersandmax_clustersBounds for initial category discovery. -
descriptionOptional corpus context. Helps the model choose better category boundaries and names. -
modelOptional model name to override the default. If omitted, a capable multimodal GPT model is used by default. -
rename_finalIf true, the library proposes clearer category names at the end based on samples from each group. -
show_progressIf true, displays progress while items are assorted.
Returns
-
resultsDict with keysorted_results. Values are lists of the original items per category. AMiscellaneousbucket is always present. -
statsDict with detailed run informationmodelitems_totalinitial_categories_countfinal_categories_countmiscellaneous_countcallswith counts for internal stepsretriesfor API retries with backofftokenswithinputandoutputcountscombination_attemptsandcombined_mergeselapsed_secondscost_usdcalculated from token counts and the internal price tablecategory_sizesmapping category to item count
How it works
-
Category discovery The model reads a sample of your corpus and proposes a set of category names between your bounds.
-
Sorting Each item is scored for every discovered category with confidences high, medium, low. High confidence categories win. Ties are broken by simple rules.
-
Combining overlapping themes When items frequently score high for the same pairs or sets of categories, the library asks the model if they should be combined. On a high decision, a single concise name is requested and the merge proceeds.
-
Refining miscellaneous If
Miscellaneousgrows larger than a data guided threshold, the same discovery and sorting routine runs on that subset. Items are pulled out into new focused categories when possible. -
Renaming for clarity At the end, the library proposes clearer names that preserve meaning using a small sample from each category. Names are deduplicated.
Cost and tokens
- Token accounting uses
tiktokenwith an encoder chosen for the active model. - The final
cost_usdis calculated from token counts and an internal price table.
Advanced examples
Run and keep original names
from assort import assort
texts = [...]
results, stats = assort(
texts,
min_clusters=4,
max_clusters=8,
description="Product feedback notes",
rename_final=False,
)
Inspect stats for simple analytics
results, stats = assort(texts)
sizes = stats["category_sizes"]
by_size = sorted(sizes.items(), key=lambda kv: kv[1], reverse=True)
for name, count in by_size:
print(name, count)
Behavior notes
- Non deterministic sampling is used during corpus selection, so runs can vary.
- The module keeps a single OpenAI client and encoder in module scope. In process concurrency is not recommended. Use separate processes for parallel work.
Metadata
Release files for assort 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| assort-2.0.0.tar.gz | 9.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| assort-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.4 kB
Release files / assort-2.0.0.tar.gz
| Download URL | assort-2.0.0.tar.gz |
|---|---|
| Size | 9.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ddd73a2611a1a29b90b9a05ec23559f386a8e4633edc9655a40203d4935783bf
|
|
BLAKE2b-256 checksum How to use checksums |
34a8cd124c66788dbc4b5836cd62ff88701d509dd87d0f3e11dad8f1b5a37bb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.0
|
Release files / assort-2.0.0-py3-none-any.whl
| Download URL | assort-2.0.0-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c2f771acbf6c02e3c392426a7bd39d9f68bc5bf0970f9239760a211da99925d
|
|
BLAKE2b-256 checksum How to use checksums |
23a73810c3d394d8b0007ce27141d1e4ec74dcf3e76afc09c38b75b41d591575
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.0
|