Source: Simon Willison — 2026-08-14
Summary
Simon Willison links to and endorses a tagging technique from Doug Turnbull aimed at classifying content against a huge, ever-growing tag vocabulary — Willison's own blog has accumulated 1,856 distinct tags over the years. The standard approach feeds the LLM the entire tag list and asks it to pick from it, which gets expensive and brittle as the list grows. Turnbull's alternative lets the LLM freely "hallucinate" plausible tags for a piece of content without ever seeing the existing vocabulary, then uses vector-embedding similarity search to snap each hallucinated tag to the nearest real tag already in the system — or flag it as genuinely new if nothing is close enough. It inverts the usual "hallucination is bad" framing: the model's free-associative guess becomes the useful part, and the embedding step is what makes it safe.
Key Takeaways
- The problem: classifying content against a huge tag vocabulary by stuffing the whole list into the LLM's context doesn't scale — it's expensive per call and gets more brittle as the vocabulary grows (Willison's blog alone has 1,856 tags).
- Turnbull's inversion: don't show the LLM the existing tags at all. Let it generate plausible tags freely, based purely on the content — essentially letting it "hallucinate" a natural-language guess.
- Safety step: each hallucinated tag gets embedded and compared via vector similarity search against the embeddings of real tags already in the vocabulary.
- If a hallucinated tag is close enough to an existing real tag, it snaps to that existing tag instead of creating a near-duplicate (e.g., "llm" vs. "large-language-models").
- If nothing in the existing vocabulary is close enough, the hallucinated tag is flagged as a genuinely new tag candidate rather than silently discarded or forced onto a wrong match.
- The reframe is the interesting part: this treats the LLM's free-association (normally the thing you're trying to suppress) as the actual generative step, while the embedding-match step does the job of keeping it grounded and safe to use.
Reel Script
Hook: Everyone tells you hallucination is the enemy — the thing you're supposed to engineer away. Here's a technique that does the opposite: it deliberately lets the model hallucinate, on purpose, and turns out that's the better approach for a problem a lot of people are quietly stuck on.
Core Concept: The problem is tagging content against a big, existing vocabulary — Simon Willison's own blog has 1,856 tags built up over years, and he needs to pick the right ones for new posts without inventing duplicates like "llm" and "large-language-model" side by side. The obvious approach is to dump the entire tag list into the LLM's prompt and ask it to classify the content by picking from that list. That works at first, but it scales badly: more tags means a bigger prompt every single call, more cost, and more chances for the model to get confused choosing between near-identical options buried in a long list. Doug Turnbull's alternative, which Willison is highlighting, flips the order of operations. Instead of constraining the model with the vocabulary upfront, you let it generate tags completely freely, based only on what the content is actually about, with zero knowledge of what tags already exist. That's the "hallucination" — a natural, unconstrained guess. The safety net comes after, not before: you take whatever the model made up, convert it to a vector embedding, and search for the nearest real tags already in your system.
Hands-On: The flow is genuinely three simple steps, which is exactly why it's worth sketching as a diagram. Step one: feed the LLM the content — no tag list, no constraints — and let it freely generate whatever tags feel natural, its unconstrained guess at what this piece is about. Step two: take each generated tag and run it through an embedding model to get a vector, then do a nearest-neighbor similarity search against the embeddings of every tag already in the real vocabulary. Step three: branch on the result — if the nearest existing tag is close enough, snap to it and reuse the real tag instead of minting a near-duplicate; if nothing in the vocabulary is close enough, keep the hallucinated tag as a genuinely new candidate instead of silently dropping it. The elegance is that the LLM never has to hold 1,856 tags in its context window at inference time, and the vocabulary can keep growing without every classification call getting more expensive — the embedding index scales instead of the prompt.
Takeaway: This is a small technique, but it's a genuinely useful pattern anywhere you're matching free text against a large, evolving controlled vocabulary — tags, categories, entity linking, product matching — not just blog tags. If you're currently stuffing a growing list into every prompt to do classification, try flipping it: generate freely, then match by embedding similarity, and only then decide what's new.