prometheusnn 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prometheusnn-0.1.0/.github/workflows/ci.yml +18 -0
- prometheusnn-0.1.0/.github/workflows/publish.yml +23 -0
- prometheusnn-0.1.0/.gitignore +10 -0
- prometheusnn-0.1.0/CHANGELOG.md +39 -0
- prometheusnn-0.1.0/LICENSE +21 -0
- prometheusnn-0.1.0/PKG-INFO +166 -0
- prometheusnn-0.1.0/README.md +134 -0
- prometheusnn-0.1.0/docs/beam-search-strategy.md +230 -0
- prometheusnn-0.1.0/docs/roadmap-self-aware-server.md +53 -0
- prometheusnn-0.1.0/pyproject.toml +69 -0
- prometheusnn-0.1.0/scripts/parse_hts.py +265 -0
- prometheusnn-0.1.0/scripts/parse_mesh.py +147 -0
- prometheusnn-0.1.0/src/prometheus/__init__.py +1 -0
- prometheusnn-0.1.0/src/prometheus/core/__init__.py +0 -0
- prometheusnn-0.1.0/src/prometheus/core/config.py +51 -0
- prometheusnn-0.1.0/src/prometheus/core/embedding.py +61 -0
- prometheusnn-0.1.0/src/prometheus/core/logging.py +24 -0
- prometheusnn-0.1.0/src/prometheus/core/lru.py +47 -0
- prometheusnn-0.1.0/src/prometheus/core/mcp_safety.py +49 -0
- prometheusnn-0.1.0/src/prometheus/core/responses.py +61 -0
- prometheusnn-0.1.0/src/prometheus/data/__init__.py +0 -0
- prometheusnn-0.1.0/src/prometheus/data/loader.py +267 -0
- prometheusnn-0.1.0/src/prometheus/data/splits.py +62 -0
- prometheusnn-0.1.0/src/prometheus/data/taxonomy.py +143 -0
- prometheusnn-0.1.0/src/prometheus/inference/__init__.py +0 -0
- prometheusnn-0.1.0/src/prometheus/inference/beam_search.py +337 -0
- prometheusnn-0.1.0/src/prometheus/inference/novelty.py +150 -0
- prometheusnn-0.1.0/src/prometheus/inference/predictor.py +164 -0
- prometheusnn-0.1.0/src/prometheus/inference/router.py +175 -0
- prometheusnn-0.1.0/src/prometheus/inference/summary.py +577 -0
- prometheusnn-0.1.0/src/prometheus/insights.py +360 -0
- prometheusnn-0.1.0/src/prometheus/intake/__init__.py +36 -0
- prometheusnn-0.1.0/src/prometheus/intake/detector.py +202 -0
- prometheusnn-0.1.0/src/prometheus/intake/models.py +92 -0
- prometheusnn-0.1.0/src/prometheus/intake/profiler.py +117 -0
- prometheusnn-0.1.0/src/prometheus/intake/recommender.py +179 -0
- prometheusnn-0.1.0/src/prometheus/intake/scoper.py +294 -0
- prometheusnn-0.1.0/src/prometheus/py.typed +0 -0
- prometheusnn-0.1.0/src/prometheus/server/INSTRUCTIONS.md +184 -0
- prometheusnn-0.1.0/src/prometheus/server/__init__.py +138 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/__init__.py +13 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/evaluation.py +283 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/inference.py +296 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/intake.py +98 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/management.py +185 -0
- prometheusnn-0.1.0/src/prometheus/server/tools/training.py +261 -0
- prometheusnn-0.1.0/src/prometheus/storage/__init__.py +0 -0
- prometheusnn-0.1.0/src/prometheus/storage/model_store.py +218 -0
- prometheusnn-0.1.0/src/prometheus/training/__init__.py +0 -0
- prometheusnn-0.1.0/src/prometheus/training/calibration.py +141 -0
- prometheusnn-0.1.0/src/prometheus/training/cascade.py +87 -0
- prometheusnn-0.1.0/src/prometheus/training/classifier.py +74 -0
- prometheusnn-0.1.0/src/prometheus/training/noise.py +100 -0
- prometheusnn-0.1.0/src/prometheus/training/trainer.py +275 -0
- prometheusnn-0.1.0/tests/__init__.py +0 -0
- prometheusnn-0.1.0/tests/conftest.py +69 -0
- prometheusnn-0.1.0/tests/fixtures/naics_test.csv +1013 -0
- prometheusnn-0.1.0/tests/test_beam_search.py +215 -0
- prometheusnn-0.1.0/tests/test_calibration.py +34 -0
- prometheusnn-0.1.0/tests/test_classifier.py +40 -0
- prometheusnn-0.1.0/tests/test_insights.py +360 -0
- prometheusnn-0.1.0/tests/test_intake.py +273 -0
- prometheusnn-0.1.0/tests/test_integration.py +369 -0
- prometheusnn-0.1.0/tests/test_noise.py +55 -0
- prometheusnn-0.1.0/tests/test_novelty.py +50 -0
- prometheusnn-0.1.0/tests/test_predictor.py +76 -0
- prometheusnn-0.1.0/tests/test_router.py +75 -0
- prometheusnn-0.1.0/tests/test_server.py +421 -0
- prometheusnn-0.1.0/tests/test_splits.py +42 -0
- prometheusnn-0.1.0/tests/test_storage.py +108 -0
- prometheusnn-0.1.0/tests/test_summary.py +915 -0
- prometheusnn-0.1.0/tests/test_taxonomy.py +79 -0
- prometheusnn-0.1.0/tests/test_trainer.py +91 -0
- prometheusnn-0.1.0/uv.lock +1873 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
lint:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v4
|
|
8
|
+
- uses: astral-sh/setup-uv@v5
|
|
9
|
+
- run: uv sync --extra dev
|
|
10
|
+
- run: uv run ruff check src/ tests/
|
|
11
|
+
- run: uv run ruff format --check src/ tests/
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv sync --extra dev
|
|
18
|
+
- run: uv run python -m pytest tests/ -v --tb=short
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write # required for PyPI Trusted Publishers (OIDC)
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build-and-publish:
|
|
13
|
+
name: Build and publish
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
- name: Build distribution
|
|
21
|
+
run: uv build
|
|
22
|
+
- name: Publish to PyPI
|
|
23
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-04-30
|
|
9
|
+
|
|
10
|
+
First public release on PyPI. PrometheusNN is an MCP server that guides users
|
|
11
|
+
from a CSV file to a trained hierarchical text classifier through a conversational
|
|
12
|
+
interface — the LLM mediates, the user never writes Python.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Entry-point tools** for LLM-mediated onboarding:
|
|
16
|
+
- `start_here(file_path)` — profiles a CSV/JSON, detects problem type,
|
|
17
|
+
surfaces concerns *before* training, returns pre-filled `next_steps`
|
|
18
|
+
so the LLM can fire the right tool without guessing column names.
|
|
19
|
+
- `scope_problem(goal_description)` — assesses whether Prometheus fits
|
|
20
|
+
a goal that doesn't have data yet; politely redirects non-fits
|
|
21
|
+
(regression, clustering, NER) to appropriate alternatives.
|
|
22
|
+
- **20 MCP tools** covering the full workflow: intake, training, inference,
|
|
23
|
+
model management, evaluation, threshold tuning, feedback.
|
|
24
|
+
- **Hierarchical cascade classifier** with parent-noise injection, beam search
|
|
25
|
+
with adaptive widening, fork detection, temperature-calibrated confidence,
|
|
26
|
+
and dual-signal novelty detection.
|
|
27
|
+
- **Three-tier routing** (AUTO_ACCEPT / HUMAN_REVIEW / OUT_OF_TAXONOMY)
|
|
28
|
+
with per-level threshold tuning.
|
|
29
|
+
- Typed configuration objects via Pydantic v2 throughout — `RouterConfig`,
|
|
30
|
+
`TrainingConfig`, `TaxonomyInfo`, `ColumnProfile`, `ProblemAssessment`, etc.
|
|
31
|
+
- LRU-bounded predictor and embedding caches with mtime-based invalidation
|
|
32
|
+
so stale models don't return stale predictions.
|
|
33
|
+
|
|
34
|
+
### Notes
|
|
35
|
+
- Requires Python 3.11+.
|
|
36
|
+
- Heavy ML dependencies (`torch`, `sentence-transformers`) are required —
|
|
37
|
+
the package only does meaningful work with them.
|
|
38
|
+
- Models are saved to `~/.prometheus/models/` as JSON manifests + `.pt`
|
|
39
|
+
state dicts. No pickle.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fahad Baig
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prometheusnn
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Frictionless hierarchical text classification — an MCP server that guides you from CSV to trained classifier
|
|
5
|
+
Project-URL: Homepage, https://github.com/mfbaig35r/prometheusnn
|
|
6
|
+
Project-URL: Repository, https://github.com/mfbaig35r/prometheusnn
|
|
7
|
+
Author-email: Fahad Baig <mfbaig35r@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Text Processing :: General
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: mcp[cli]>=1.2.0
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Requires-Dist: pandas>=2.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: scikit-learn>=1.3
|
|
26
|
+
Requires-Dist: sentence-transformers>=2.2
|
|
27
|
+
Requires-Dist: torch>=2.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# PrometheusNN
|
|
34
|
+
|
|
35
|
+
Build a hierarchical text classifier without writing code. Point Claude (or any MCP client) at your data and it walks you from "I have a CSV" to a trained model — profiling your columns, detecting whether the problem fits, surfacing concerns before training, and recommending the right setup.
|
|
36
|
+
|
|
37
|
+
PrometheusNN is the ML expert sitting next to you. You bring the data and the goal; it handles the rest.
|
|
38
|
+
|
|
39
|
+
## What this looks like
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
You: I have a CSV of product descriptions and I want to categorize them.
|
|
43
|
+
Can you help?
|
|
44
|
+
|
|
45
|
+
Claude: [calls start_here on the file]
|
|
46
|
+
|
|
47
|
+
I looked at your data — 11,400 product descriptions across three
|
|
48
|
+
nested category levels (Chapter → Heading → Subheading). One thing
|
|
49
|
+
worth flagging before we train: the deepest level has ~2 examples
|
|
50
|
+
per class on average, which usually means poor accuracy at that
|
|
51
|
+
depth. Want to do all three levels, or simplify to the top two?
|
|
52
|
+
|
|
53
|
+
You: Top two.
|
|
54
|
+
|
|
55
|
+
Claude: [fires analyze_training_data → train_model with the simpler setup]
|
|
56
|
+
|
|
57
|
+
Done. Chapter accuracy 92%, Heading accuracy 78%. Want to try
|
|
58
|
+
classifying something?
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
No column-name guessing, no parameter cargo-culting, no premature training runs.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install prometheusnn
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then add to your MCP client config (Claude Desktop, Cursor, Zed, etc.):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"prometheus": {
|
|
75
|
+
"command": "prometheusnn"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
That's it. Open your MCP client and start a conversation about classifying something.
|
|
82
|
+
|
|
83
|
+
## How it works under the hood
|
|
84
|
+
|
|
85
|
+
Two entry-point tools handle every new conversation:
|
|
86
|
+
|
|
87
|
+
- **`start_here(file_path)`** — profiles your CSV, detects whether it's a hierarchical, flat, or multi-label classification problem, surfaces concerns before training, and returns pre-filled next steps so the LLM never has to guess column names.
|
|
88
|
+
|
|
89
|
+
- **`scope_problem(goal_description)`** — for when you don't have data yet. Tells you whether Prometheus fits your goal, what data you'll need, and what to watch for. Politely redirects non-fits (regression, clustering, NER) to the right tools.
|
|
90
|
+
|
|
91
|
+
From there, the LLM routes through the existing tool surface (training, evaluation, threshold tuning, prediction) based on what `start_here` recommended.
|
|
92
|
+
|
|
93
|
+
## What's actually being built
|
|
94
|
+
|
|
95
|
+
Under the conversational surface, PrometheusNN trains a cascade of neural network classifiers — one per level of your taxonomy — with parent-noise injection so deeper levels stay robust when upper levels are uncertain. Beam search explores alternative paths when confidence drops. Temperature calibration produces probabilities you can trust for routing. Dual-signal novelty detection catches items that don't belong.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Text descriptions + taxonomy labels
|
|
99
|
+
│
|
|
100
|
+
▼
|
|
101
|
+
┌─────────────┐
|
|
102
|
+
│ Embedding │ sentence-transformers (384d or 768d)
|
|
103
|
+
└──────┬──────┘
|
|
104
|
+
▼
|
|
105
|
+
┌─────────────┐
|
|
106
|
+
│ Cascade │ N classifiers, one per level
|
|
107
|
+
│ Classifier │ parent noise injection for robustness
|
|
108
|
+
└──────┬──────┘
|
|
109
|
+
▼
|
|
110
|
+
┌─────────────┐
|
|
111
|
+
│ Beam Search │ adaptive widening when confidence drops
|
|
112
|
+
└──────┬──────┘
|
|
113
|
+
▼
|
|
114
|
+
┌─────────────┐
|
|
115
|
+
│ Router │ calibrated thresholds → accept / review / reject
|
|
116
|
+
│ + Novelty │ centroid z-score + kNN distance
|
|
117
|
+
└─────────────┘
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
You don't need to know any of this to use it — but it's there if you want to dig in.
|
|
121
|
+
|
|
122
|
+
## Tool reference
|
|
123
|
+
|
|
124
|
+
20 MCP tools total. The two you'll be told about; the rest the LLM picks for you.
|
|
125
|
+
|
|
126
|
+
**Entry points** (start here)
|
|
127
|
+
- `start_here` — assess a file, detect problem type, return pre-filled next steps
|
|
128
|
+
- `scope_problem` — assess a goal description (no data yet), check fit and data requirements
|
|
129
|
+
|
|
130
|
+
**Training & data**
|
|
131
|
+
- `analyze_training_data`, `train_model`, `resume_training`
|
|
132
|
+
|
|
133
|
+
**Inference**
|
|
134
|
+
- `predict`, `predict_batch`, `classify_with_context`
|
|
135
|
+
|
|
136
|
+
**Model management**
|
|
137
|
+
- `list_models`, `describe_model`, `delete_model`, `export_model`
|
|
138
|
+
|
|
139
|
+
**Evaluation & tuning**
|
|
140
|
+
- `evaluate_model`, `explain_prediction`, `get_confusion_matrix`
|
|
141
|
+
- `get_threshold_report`, `set_thresholds`
|
|
142
|
+
|
|
143
|
+
**Other**
|
|
144
|
+
- `submit_feedback`, `list_embedding_models`, `build_code_mapping`
|
|
145
|
+
|
|
146
|
+
## Environment variables
|
|
147
|
+
|
|
148
|
+
| Variable | Default | Description |
|
|
149
|
+
|----------|---------|-------------|
|
|
150
|
+
| `PROMETHEUS_HOME` | `~/.prometheus` | Base directory for models, logs, feedback |
|
|
151
|
+
| `PROMETHEUS_EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Default sentence-transformer model |
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git clone https://github.com/mfbaig35r/prometheusnn
|
|
157
|
+
cd prometheusnn
|
|
158
|
+
uv sync --extra dev
|
|
159
|
+
uv run python -m pytest tests/ -v --tb=short # 179 tests
|
|
160
|
+
uv run ruff check src/ tests/ # lint
|
|
161
|
+
uv run ruff format --check src/ tests/ # format
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# PrometheusNN
|
|
2
|
+
|
|
3
|
+
Build a hierarchical text classifier without writing code. Point Claude (or any MCP client) at your data and it walks you from "I have a CSV" to a trained model — profiling your columns, detecting whether the problem fits, surfacing concerns before training, and recommending the right setup.
|
|
4
|
+
|
|
5
|
+
PrometheusNN is the ML expert sitting next to you. You bring the data and the goal; it handles the rest.
|
|
6
|
+
|
|
7
|
+
## What this looks like
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
You: I have a CSV of product descriptions and I want to categorize them.
|
|
11
|
+
Can you help?
|
|
12
|
+
|
|
13
|
+
Claude: [calls start_here on the file]
|
|
14
|
+
|
|
15
|
+
I looked at your data — 11,400 product descriptions across three
|
|
16
|
+
nested category levels (Chapter → Heading → Subheading). One thing
|
|
17
|
+
worth flagging before we train: the deepest level has ~2 examples
|
|
18
|
+
per class on average, which usually means poor accuracy at that
|
|
19
|
+
depth. Want to do all three levels, or simplify to the top two?
|
|
20
|
+
|
|
21
|
+
You: Top two.
|
|
22
|
+
|
|
23
|
+
Claude: [fires analyze_training_data → train_model with the simpler setup]
|
|
24
|
+
|
|
25
|
+
Done. Chapter accuracy 92%, Heading accuracy 78%. Want to try
|
|
26
|
+
classifying something?
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No column-name guessing, no parameter cargo-culting, no premature training runs.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install prometheusnn
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then add to your MCP client config (Claude Desktop, Cursor, Zed, etc.):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"prometheus": {
|
|
43
|
+
"command": "prometheusnn"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it. Open your MCP client and start a conversation about classifying something.
|
|
50
|
+
|
|
51
|
+
## How it works under the hood
|
|
52
|
+
|
|
53
|
+
Two entry-point tools handle every new conversation:
|
|
54
|
+
|
|
55
|
+
- **`start_here(file_path)`** — profiles your CSV, detects whether it's a hierarchical, flat, or multi-label classification problem, surfaces concerns before training, and returns pre-filled next steps so the LLM never has to guess column names.
|
|
56
|
+
|
|
57
|
+
- **`scope_problem(goal_description)`** — for when you don't have data yet. Tells you whether Prometheus fits your goal, what data you'll need, and what to watch for. Politely redirects non-fits (regression, clustering, NER) to the right tools.
|
|
58
|
+
|
|
59
|
+
From there, the LLM routes through the existing tool surface (training, evaluation, threshold tuning, prediction) based on what `start_here` recommended.
|
|
60
|
+
|
|
61
|
+
## What's actually being built
|
|
62
|
+
|
|
63
|
+
Under the conversational surface, PrometheusNN trains a cascade of neural network classifiers — one per level of your taxonomy — with parent-noise injection so deeper levels stay robust when upper levels are uncertain. Beam search explores alternative paths when confidence drops. Temperature calibration produces probabilities you can trust for routing. Dual-signal novelty detection catches items that don't belong.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Text descriptions + taxonomy labels
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
┌─────────────┐
|
|
70
|
+
│ Embedding │ sentence-transformers (384d or 768d)
|
|
71
|
+
└──────┬──────┘
|
|
72
|
+
▼
|
|
73
|
+
┌─────────────┐
|
|
74
|
+
│ Cascade │ N classifiers, one per level
|
|
75
|
+
│ Classifier │ parent noise injection for robustness
|
|
76
|
+
└──────┬──────┘
|
|
77
|
+
▼
|
|
78
|
+
┌─────────────┐
|
|
79
|
+
│ Beam Search │ adaptive widening when confidence drops
|
|
80
|
+
└──────┬──────┘
|
|
81
|
+
▼
|
|
82
|
+
┌─────────────┐
|
|
83
|
+
│ Router │ calibrated thresholds → accept / review / reject
|
|
84
|
+
│ + Novelty │ centroid z-score + kNN distance
|
|
85
|
+
└─────────────┘
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You don't need to know any of this to use it — but it's there if you want to dig in.
|
|
89
|
+
|
|
90
|
+
## Tool reference
|
|
91
|
+
|
|
92
|
+
20 MCP tools total. The two you'll be told about; the rest the LLM picks for you.
|
|
93
|
+
|
|
94
|
+
**Entry points** (start here)
|
|
95
|
+
- `start_here` — assess a file, detect problem type, return pre-filled next steps
|
|
96
|
+
- `scope_problem` — assess a goal description (no data yet), check fit and data requirements
|
|
97
|
+
|
|
98
|
+
**Training & data**
|
|
99
|
+
- `analyze_training_data`, `train_model`, `resume_training`
|
|
100
|
+
|
|
101
|
+
**Inference**
|
|
102
|
+
- `predict`, `predict_batch`, `classify_with_context`
|
|
103
|
+
|
|
104
|
+
**Model management**
|
|
105
|
+
- `list_models`, `describe_model`, `delete_model`, `export_model`
|
|
106
|
+
|
|
107
|
+
**Evaluation & tuning**
|
|
108
|
+
- `evaluate_model`, `explain_prediction`, `get_confusion_matrix`
|
|
109
|
+
- `get_threshold_report`, `set_thresholds`
|
|
110
|
+
|
|
111
|
+
**Other**
|
|
112
|
+
- `submit_feedback`, `list_embedding_models`, `build_code_mapping`
|
|
113
|
+
|
|
114
|
+
## Environment variables
|
|
115
|
+
|
|
116
|
+
| Variable | Default | Description |
|
|
117
|
+
|----------|---------|-------------|
|
|
118
|
+
| `PROMETHEUS_HOME` | `~/.prometheus` | Base directory for models, logs, feedback |
|
|
119
|
+
| `PROMETHEUS_EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Default sentence-transformer model |
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git clone https://github.com/mfbaig35r/prometheusnn
|
|
125
|
+
cd prometheusnn
|
|
126
|
+
uv sync --extra dev
|
|
127
|
+
uv run python -m pytest tests/ -v --tb=short # 179 tests
|
|
128
|
+
uv run ruff check src/ tests/ # lint
|
|
129
|
+
uv run ruff format --check src/ tests/ # format
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Beam Search Strategy
|
|
2
|
+
|
|
3
|
+
This document describes the beam search inference pipeline in Prometheus, including adaptive beam widening, diversity-aware pruning, fork detection, and self-interpreting insights.
|
|
4
|
+
|
|
5
|
+
## File Reference
|
|
6
|
+
|
|
7
|
+
- Beam search: `src/prometheus/inference/beam_search.py`
|
|
8
|
+
- Router & forks: `src/prometheus/inference/router.py`
|
|
9
|
+
- Predictor: `src/prometheus/inference/predictor.py`
|
|
10
|
+
- Insights: `src/prometheus/server.py` (`_analyze_forks`)
|
|
11
|
+
- Tests: `tests/test_beam_search.py`
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Prometheus uses an N-level hierarchical cascade classifier. At inference time, beam search explores multiple classification paths through the taxonomy tree and returns the highest-scoring complete path along with alternatives.
|
|
16
|
+
|
|
17
|
+
Two classifiers are available:
|
|
18
|
+
- **GreedyClassifier** — picks the single best class at each level. Fast, no alternatives.
|
|
19
|
+
- **BeamSearchClassifier** — maintains `beam_width` candidate paths. Slower, but surfaces competing hypotheses.
|
|
20
|
+
|
|
21
|
+
## Standard Beam Search
|
|
22
|
+
|
|
23
|
+
At each taxonomy level (depth 0 through N-1):
|
|
24
|
+
|
|
25
|
+
1. For each existing beam, run the level classifier to get class probabilities.
|
|
26
|
+
2. Expand each beam with the top-k children (k = `beam_width`).
|
|
27
|
+
3. Score candidates by cumulative log-probability: `log_score = sum(log(p_i))` across all levels.
|
|
28
|
+
4. Prune to `beam_width` beams by global score.
|
|
29
|
+
|
|
30
|
+
This works well when the model is confident at every level. The problem arises when it isn't.
|
|
31
|
+
|
|
32
|
+
## Problem: Score-Based Pruning Kills Minority Branches
|
|
33
|
+
|
|
34
|
+
Consider classifying "plumbing and heating contractor" in a 4-level NAICS taxonomy:
|
|
35
|
+
|
|
36
|
+
| Level | Top candidate | Confidence | Runner-up | Confidence |
|
|
37
|
+
|-------|--------------|------------|-----------|------------|
|
|
38
|
+
| Sector | Construction | 0.99 | — | — |
|
|
39
|
+
| Subsector | Specialty Trade | 0.99 | — | — |
|
|
40
|
+
| IndustryGroup | Building Finishing | 0.56 | Building Equipment | 0.19 |
|
|
41
|
+
| NAICSIndustry | (varies) | ~0.17 | — | — |
|
|
42
|
+
|
|
43
|
+
At IndustryGroup (depth 2), the model is genuinely uncertain — 0.56 vs 0.19. Building Equipment Contractors is the correct answer for plumbing/heating, but its log-score deficit from depth 2 compounds at depth 3. With pure score-based pruning, all final beams collapse into the Building Finishing branch. The correct path never appears in the output.
|
|
44
|
+
|
|
45
|
+
## Solution: Adaptive Widening + Diversity-Aware Pruning
|
|
46
|
+
|
|
47
|
+
Two mechanisms work together to fix this.
|
|
48
|
+
|
|
49
|
+
### 1. Adaptive Beam Widening
|
|
50
|
+
|
|
51
|
+
When `confidence_thresholds` are configured, the beam width dynamically increases at uncertain levels.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
effective_width = beam_width
|
|
55
|
+
if max_prob < confidence_thresholds[depth]:
|
|
56
|
+
effective_width = beam_width * adaptive_factor # default: 2x
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This happens in both `_init_beams()` (depth 0) and `_expand_beams()` (depth 1+). Widening creates more candidates at the uncertain level, giving minority branches a chance to enter the beam. But widening alone isn't enough — at the next level, the expanded candidates get pruned back down, and pure score ranking still kills the minority branches.
|
|
60
|
+
|
|
61
|
+
### 2. Diversity-Aware Pruning
|
|
62
|
+
|
|
63
|
+
When widening triggers, the system records the **first uncertain level** as `_diversity_depth`. This depth becomes a diversity anchor: during pruning at all subsequent levels, at least one representative from each unique class at the diversity depth is guaranteed to survive.
|
|
64
|
+
|
|
65
|
+
#### Tracking
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Reset per prediction
|
|
69
|
+
self._diversity_depth = None
|
|
70
|
+
|
|
71
|
+
# Set on first widening (in _init_beams or _expand_beams)
|
|
72
|
+
if self._diversity_depth is None:
|
|
73
|
+
self._diversity_depth = depth
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `is None` guard ensures only the first uncertain level becomes the anchor. If multiple levels are uncertain, deeper diversity is handled implicitly — diverse branches at the anchor level naturally produce diverse candidates downstream.
|
|
77
|
+
|
|
78
|
+
#### Pruning Algorithm (`_prune_with_diversity`)
|
|
79
|
+
|
|
80
|
+
Input: candidate beams (pre-sorted by log_score descending), target width.
|
|
81
|
+
|
|
82
|
+
**Phase 1 — Guarantee representation:**
|
|
83
|
+
- Group beams by their class index at `_diversity_depth`.
|
|
84
|
+
- Take the highest-scored beam from each group (one representative per branch).
|
|
85
|
+
|
|
86
|
+
**Phase 2 — Fill by score:**
|
|
87
|
+
- If representatives don't fill the width, add remaining beams in score order.
|
|
88
|
+
- Re-sort the final list by log_score.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Beams at depth 3 (6 candidates, width=6):
|
|
92
|
+
BF→OtherFinishing score=-2.32 ← best of BF group
|
|
93
|
+
BF→FinishCarpentry score=-2.50
|
|
94
|
+
BF→Flooring score=-2.51
|
|
95
|
+
BE→Electrical score=-2.72 ← best of BE group (guaranteed slot)
|
|
96
|
+
BE→Plumbing score=-3.10
|
|
97
|
+
BF→Painting score=-3.20
|
|
98
|
+
|
|
99
|
+
After diversity pruning (width=6): all 6 survive (no pruning needed)
|
|
100
|
+
After diversity pruning (width=3): BF→OtherFinishing, BF→FinishCarpentry, BE→Electrical
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Without diversity pruning at width=3, the result would be the top 3 BF paths. With it, the BE branch survives.
|
|
104
|
+
|
|
105
|
+
#### Fallback
|
|
106
|
+
|
|
107
|
+
When `_diversity_depth` is `None` (no thresholds configured, or all levels confident), `_prune_with_diversity` returns `beams[:width]` — identical to the previous pure-score behavior. This means:
|
|
108
|
+
- Models without `confidence_thresholds` are completely unaffected.
|
|
109
|
+
- Predictions where all levels are confident use plain score ranking.
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
Both mechanisms are controlled via `BeamSearchClassifier` constructor parameters:
|
|
114
|
+
|
|
115
|
+
| Parameter | Default | Description |
|
|
116
|
+
|-----------|---------|-------------|
|
|
117
|
+
| `beam_width` | 3 | Base number of paths to maintain |
|
|
118
|
+
| `confidence_thresholds` | None | Dict mapping depth → minimum confidence. Levels below threshold trigger widening. |
|
|
119
|
+
| `adaptive_factor` | 2 | Multiplier for beam width when widening triggers |
|
|
120
|
+
|
|
121
|
+
Thresholds are auto-generated during training as linear decay from 0.90 (root) to 0.75 (leaf). They can be overridden via `set_thresholds`.
|
|
122
|
+
|
|
123
|
+
## Edge Cases
|
|
124
|
+
|
|
125
|
+
**Single-level taxonomies:** No beam expansion occurs. Diversity pruning is never triggered.
|
|
126
|
+
|
|
127
|
+
**Out-of-taxonomy items:** Novelty detection runs independently. Diversity pruning may activate if thresholds are configured, but the routing decision is governed by the novelty detector, not the beam structure.
|
|
128
|
+
|
|
129
|
+
**Greedy mode (beam_width=1):** Uses `GreedyClassifier`, which has no beam expansion, widening, or diversity logic.
|
|
130
|
+
|
|
131
|
+
**Many competing classes at diversity depth:** If more unique classes exist at the anchor depth than the effective beam width, representatives are capped at `width` (taken in score order). This means the lowest-scored branches may still be dropped. In practice this is rare — uncertain levels typically have 2-4 competing classes, well within a width of 3-6.
|
|
132
|
+
|
|
133
|
+
## Fork Detection
|
|
134
|
+
|
|
135
|
+
After beam search and diversity pruning produce the final set of beams, the router (`ThresholdRouter.route_with_forks`) groups them into `forked_completions` — alternative paths presented to the caller.
|
|
136
|
+
|
|
137
|
+
### How forks are detected
|
|
138
|
+
|
|
139
|
+
The router identifies a **fork depth** — the level where the beams diverge:
|
|
140
|
+
|
|
141
|
+
- **`confident_depth` exists** (e.g., NAICS: L1 and L2 are confident): `fork_depth = confident_depth + 1`. Forks are alternative completions below the confident prefix. All forks share the same upper levels (e.g., Construction → Specialty Trade) and diverge at the uncertain level (IndustryGroup).
|
|
142
|
+
|
|
143
|
+
- **`confident_depth` is None** (e.g., MeSH: L1 itself is uncertain): `fork_depth = 0`. The entire path is uncertain, so forks are grouped by L1 class — one representative per top-level branch, each showing the best full path through that branch.
|
|
144
|
+
|
|
145
|
+
### Why both modes matter
|
|
146
|
+
|
|
147
|
+
NAICS "plumbing and heating contractor": L1 (Construction, 0.99) and L2 (Specialty Trade, 0.99) are confident. The question is which IndustryGroup, so forks show competing L3→L4 suffixes below the shared prefix.
|
|
148
|
+
|
|
149
|
+
MeSH "rheumatoid arthritis": L1 itself is split (Musculoskeletal 0.43 vs Connective Tissue 0.14). There's no confident prefix. Forks show competing full paths — one through Musculoskeletal, one through Connective Tissue — because both are legitimate MeSH placements.
|
|
150
|
+
|
|
151
|
+
### Format
|
|
152
|
+
|
|
153
|
+
Each `ForkedCompletion` contains:
|
|
154
|
+
- `suffix` — labels from `fork_depth` onward
|
|
155
|
+
- `suffix_indices` — class indices from `fork_depth` onward
|
|
156
|
+
- `suffix_score` — cumulative log-prob of the suffix portion
|
|
157
|
+
- `per_level_probs` — probability at each suffix level
|
|
158
|
+
|
|
159
|
+
Forks are sorted by `suffix_score` descending (best first).
|
|
160
|
+
|
|
161
|
+
## Self-Interpreting Insights
|
|
162
|
+
|
|
163
|
+
The `insights` field in the predict response is generated by `_analyze_forks()` in `server.py`. It runs three checks against the already-computed `forked_completions` — no additional model calls, no ML, just pattern matching on beam search output.
|
|
164
|
+
|
|
165
|
+
### Insight 1: Minority branch with higher leaf confidence
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
"Alternative path via 'Skin and Connective Tissue Diseases' has higher leaf confidence
|
|
169
|
+
for 'Arthritis, Rheumatoid' (95% vs 61%)"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**What it detects:** A forked completion whose leaf-level probability exceeds the best path's leaf probability by more than 5 percentage points.
|
|
173
|
+
|
|
174
|
+
**Why it matters:** In polyhierarchical taxonomies, the best path (highest cumulative score) often has a weak leaf prediction because its strength comes from a confident upper level. A minority branch with low L1 confidence can produce a much more confident leaf — this insight flags exactly that.
|
|
175
|
+
|
|
176
|
+
**What a caller should do:** Present both the best path and the flagged alternative. The alternative's leaf prediction may be more reliable even though its overall path score is lower.
|
|
177
|
+
|
|
178
|
+
### Insight 2: Branch convergence
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
"Multiple branches ('Musculoskeletal Diseases' and 'Skin and Connective Tissue Diseases')
|
|
182
|
+
converge on 'Arthritis, Rheumatoid'"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**What it detects:** Two or more forked completions that arrive at the same leaf label through different upper-level branches.
|
|
186
|
+
|
|
187
|
+
**Why it matters:** Independent paths reaching the same conclusion is strong corroboration. If the Musculoskeletal branch and the Connective Tissue branch both end at "Arthritis, Rheumatoid", the model is confident in the leaf even though it's uncertain about which branch to use. This is the hallmark of polyhierarchical data — the item genuinely belongs in multiple places.
|
|
188
|
+
|
|
189
|
+
**What a caller should do:** Treat the converged leaf as high-confidence. Optionally present both branches as valid placements.
|
|
190
|
+
|
|
191
|
+
### Insight 3: Low best-path leaf confidence
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
"Best path leaf confidence is low (22%), review forked_completions for alternatives"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**What it detects:** The best path's leaf probability is below 30%, and neither of the above insights fired.
|
|
198
|
+
|
|
199
|
+
**Why it matters:** Safety net. The model picked a path but isn't confident about where it ends. The forked completions may contain a better answer or the item may need human review.
|
|
200
|
+
|
|
201
|
+
**What a caller should do:** Don't present the best path as authoritative. Show the top 2-3 forked completions and let the user decide.
|
|
202
|
+
|
|
203
|
+
### When insights are absent
|
|
204
|
+
|
|
205
|
+
No `insights` field means: the best path is the clear winner and no fork has a stronger leaf prediction. The caller can trust the best path.
|
|
206
|
+
|
|
207
|
+
## Design Philosophy
|
|
208
|
+
|
|
209
|
+
Each layer does one simple thing:
|
|
210
|
+
|
|
211
|
+
| Layer | What it does | Complexity |
|
|
212
|
+
|-------|-------------|-----------|
|
|
213
|
+
| Beam search | Explore multiple paths | Expansion + log-score |
|
|
214
|
+
| Diversity pruning | Don't let score kill minority branches | One dict lookup per beam |
|
|
215
|
+
| Fork detection | Group paths by where they diverge | Prefix matching |
|
|
216
|
+
| Insights | Compare leaf confidences across groups | Arithmetic |
|
|
217
|
+
|
|
218
|
+
No layer "understands" polyhierarchy. Stacked together, they surface it — because they don't throw away information prematurely.
|
|
219
|
+
|
|
220
|
+
## Tested On
|
|
221
|
+
|
|
222
|
+
**NAICS** (4 levels, 20 sectors, clean hierarchy, 1,012 samples):
|
|
223
|
+
- Diversity pruning keeps Building Equipment Contractors alive for "plumbing and heating contractor"
|
|
224
|
+
- Forks show competing IndustryGroup completions below the confident Sector/Subsector prefix
|
|
225
|
+
|
|
226
|
+
**MeSH Diseases** (4 levels, 22 L1 categories, 2,459 leaf classes, polyhierarchical, 10,984 samples):
|
|
227
|
+
- Diversity pruning keeps minority L1 branches alive (Connective Tissue at 14% L1 confidence finds RA at 95% leaf confidence)
|
|
228
|
+
- Fork detection at depth 0 surfaces all competing top-level branches
|
|
229
|
+
- Convergence insight detects when two branches agree on the same leaf
|
|
230
|
+
- Composite text ("Name: scope note") improved L4 accuracy by 2.3 points
|