LLM-Based Hierarchical Topic Modeling Tool A new LLM-based hierarchical topic modeling tool normalizes inconsistent raw strings into a reusable taxonomy for JSONL data, steering with plain-language instructions. The tool maps values like 'bad', 'neg', and 'negative' to 'negative' or 'search' to 'Features|Search', and works with any JSONL file containing strings, nested objects, and arrays of objects. It requires Python 3.9, an API key for a LiteLLM-supported model provider, and internet access while the LLM builds or extends the taxonomy. Hierarchical topic modeling groups related labels into a taxonomy: broad topics at the top and increasingly specific subtopics below them. This tool is a practical, LLM-assisted version for JSONL data: it turns inconsistent raw strings into a reusable hierarchy that you steer with plain-language instructions. For example, it can map bad , neg , and negative to negative , or map search to Features|Search . It is designed for extracted Reddit-study data, but works with any JSONL file containing strings, nested objects, and arrays of objects. The input is a JSONL file with raw string values. The output is another JSONL file with the same records and structure , except that mapped values have been replaced by their normalized category paths. run normalization.py — the interactive normalizer. normalization.py — field discovery, hierarchical mappings, config merging, and JSONL transformation. ai helper.py — Helper for interacting with LiteLLM config.py — model IDs, API keys, and rate limits. examples\reddit extraction.jsonl — a small Reddit-shaped input example. - Windows, macOS, or Linux. - Python 3.9 probably works with other versions . - An API key for a LiteLLM-supported model provider pretty much all of them . - Internet access while the LLM is building or extending the taxonomy. Most installations use prebuilt Python packages and do not compile anything. If pip reports that Rust , rustc , or cargo is required, install Rust first rather than trying to bypass the error. On Windows, download and run the Rust installer from the official Rust installation page https://rust-lang.org/install.html . Accept the default stable toolchain. If it offers to install the Visual Studio C++ Build Tools, allow it: Rust’s Windows MSVC toolchain may need them. Close and reopen PowerShell, then verify: rustc --version cargo --version On macOS or Linux, install Rust with Rustup: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh Choose the default installation, then open a new terminal or run source "$HOME/.cargo/env" and verify: rustc --version cargo --version On Linux, a native compiler/linker may also be required when building packages from source. Install your distribution's development tools first if the Rust installation or pip build reports a linker error—for example, build-essential on Debian/Ubuntu or base-devel on Arch Linux. Rustup is the official recommended installer and installs both rustc and cargo . Rust installation documentation https://rust-lang.org/install.html Open PowerShell in this folder: cd "C:\reddit download script\normalization tool" Use your installed Python directly: python -m pip install --upgrade pip python -m pip install -r requirements.txt An isolated environment is recommended if you work on several Python projects, but it is not required. With Miniconda or Anaconda installed: conda create -n topic-normalizer python=3.11 pip -y conda activate topic-normalizer cd "C:\reddit download script\normalization tool" python -m pip install --upgrade pip python -m pip install -r requirements.txt Activate the same environment whenever you use the tool: conda activate topic-normalizer Open config.py . Its three important settings are: AI MODEL KEYS — model ID → one or more API keys. MODELS — the priority order of models to try. AI MODEL LIMITS — rate limits for each model/key. The model ID must be a valid LiteLLM model identifier for your provider. The included example uses OpenRouter. If you downloaded this tool from Git, create your private configuration first: Copy-Item config.example.py config.py This is the quickest setup. Replace the example list with your actual key: AI MODEL KEYS = { "openrouter/google/gemini-2.5-flash-lite": "sk-or-v1-your-real-key-goes-here", , } MODELS = "openrouter/google/gemini-2.5-flash-lite" Do not commit or share this edited file. Treat it like a password. This preserves the same os.getenv ... logic in config.py , but supplies those variables automatically whenever the tool starts. - .env.example in this folder and rename the copy to exactly .env . - Open .env in a text editor and fill in the key for the provider you use: OPENROUTER API KEY=sk-or-v1-your-real-key-goes-here - Ensure config.py reads the same variable name. The existing OpenRouter pattern already does this: AI MODEL KEYS = { "openrouter/google/gemini-2.5-flash-lite": key for key in os.getenv "OPENROUTER API KEY" if key , } The tool loads .env before it evaluates AI MODEL KEYS . Values already set in PowerShell, Conda, or the operating system take priority over .env . The .env file is listed in .gitignore , but it is still a plain-text secret file: do not share it, upload it, or commit it manually. The direct-paste option remains available. If you paste a key directly into AI MODEL KEYS , that direct value is used; .env is simply ignored for that entry. To use a different provider, change the model ID, the environment-variable name, and MODELS together. If you have several keys for the same model, list them all under that model. The helper rotates between them and tracks the limits configured in AI MODEL LIMITS . The input must be valid JSON Lines .jsonl : one complete JSON object per non-empty line. Values that you want to normalize should still be raw strings. {"id":"kdnmrkk","overall platform sentiment":"bad","categories discussed": {"target feature":"search","sentiment":"neg"} } {"id":"abc123","overall platform sentiment":"negative","categories discussed": {"target feature":"Search","sentiment":"bad"} } Nested arrays work automatically. In this example, categories discussed.sentiment refers to the sentiment value of every object in the categories discussed array. python -m run normalization The script asks for: Input JSONL path — for example examples\reddit extraction.jsonl . Output JSONL path — press Enter to save beside the input as