Entity extraction is one of those tasks that seems to demand a language model — until you look at what the task actually requires. In a closed domain with a known vocabulary, named entity recognition (NER) is a lookup problem with typo tolerance, not a reasoning problem. Regex against a curated gazetteer, fuzzy string matching, and a small knowledge base can extract entities with high precision, in single-digit milliseconds, with zero data leaving the device. For a browser-based, privacy-first chatbot, that is often the right trade. Rule-based NER is the right tool when three things hold: the domain is bounded, the entity vocabulary is curated, and precision matters more than recall of obscure variants. A game-theory glossary has a finite set of terms; a history reference has a finite set of names; a data-science chatbot has a finite set of concepts. When the vocabulary is finite and known, a gazetteer plus patterns beats a general-purpose model on precision, and it costs nothing at runtime. The failure mode is the opposite situation: an open domain where new entities appear constantly. No gazetteer covers the long tail — but rule-based systems handle the short head extremely well, which is exactly the part users type most often.

Pass 1: Exact Alias Regex

The first pass is a straightforward gazetteer match. Collect every entity with its aliases, escape the strings, and build one alternation regex:

const aliases = ['Nash equilibrium', 'Nash equilibria', 'mixed strategy', "Prisoner's dilemma"];
const pattern = new RegExp(`\\b(${aliases.map(escapeRegex).join('|')})\\b`, 'gi');

for (const match of text.matchAll(pattern)) {
  entities.push({ term: match[0], start: match.index });
}

Two details make this pass reliable. First, escape every alias before building the alternation — entity names contain dots, parentheses, and apostrophes. Second, sort alternatives by length descending so Nash equilibrium matches before Nash, and use word boundaries so mixed strategy does not match inside mixed strategy games incorrectly. ReLU.chat's entity extraction runs this exact-alias regex as its first pass.

Pass 2: Fuzzy Matching

Users misspell entity names, and a pure regex pass silently misses them. The second pass is fuzzy: after the exact pass, compare unmatched tokens against candidate terms using word overlap, Levenshtein distance, and substring containment. Normalize first — lowercase, strip punctuation — so nash-equilibrium and Nash equilibrium compare equal.

def fuzzy_score(candidate: str, token: str) -> float:
    if token in candidate or candidate in token:
        return 1.0
    return 1.0 - (levenshtein(token, candidate) / max(len(token), len(candidate)))

Accept a match only above a threshold (0.8 is a reasonable start) and only for the best candidate per window. Fuzzy matching against the whole knowledge base is a precision disaster; fuzzy-match only against candidates the exact pass or a coarse prefix filter already surfaced. This layered structure — exact first, fuzzy second — keeps false positives low because fuzzy matching only resolves near-misses, never open guesses. ReLU.chat's three-pass extraction uses fuzzy word-overlap with Levenshtein plus substring containment as its second pass, catching exactly the class of errors that would otherwise break retrieval: "nash equilibrum" still finds the Nash equilibrium entry.

Pass 3: Notation Pattern Matching

Many entities are not words at all: years, dates, prices, version numbers, percentages, equations. No gazetteer can enumerate them, but patterns can:

const notationPatterns = [
  { type: 'year', re: /\b(1[89]\d{2}|20\d{2})\b/ },
  { type: 'price', re: /\$\d+(\.\d{2})?/ },
  { type: 'percentage', re: /\d+(\.\d+)?%/ },
  { type: 'version', re: /\bv?\d+\.\d+(\.\d+)?\b/ },
];

Notation entities are especially valuable in a math-heavy domain, where the entity might be a formula the chat renders with KaTeX. Pattern matching captures them deterministically and cheaply, and it doubles as input validation: a year entity can constrain date-based queries before they reach retrieval.

Knowledge Bases, Disambiguation, and Session Memory

A gazetteer tells you a string is an entity; a knowledge base tells you which entity it is. Ambiguity is the reason you need the second. Nash could be John Nash, a town, or the general in a game-theory entry; entropy could be information entropy or thermodynamic entropy. Disambiguate with context: other entities in the same turn, domain-specific keywords nearby, or a frequency prior from the knowledge base. On-device, the knowledge base is a compact JSON map, so disambiguation is a few dictionary lookups with no server round trip — and the user's text never leaves the tab.

In a conversation, entities also persist. The user mentions mixed strategy in turn 3 and refers to "it" in turn 10; the retrieval layer needs to know which entities are still live. Deterministic NER makes this cheap to track per turn. ReLU.chat's session memory keeps up to 30 turns with importance-based eviction, and entity relevance decays with a half-life of about 5 turns — an entity stays hot while the conversation is about it and fades as the topic moves on.

Measuring Precision and Recall

Rule-based systems lean precise by construction; fuzzy passes add recall at the cost of precision. Tune on a labeled evaluation set per entity type, and treat the thresholds as parameters, not constants. If typos are rare in your domain, raise the fuzzy threshold. If the exact pass is producing false positives from common words, tighten the gazetteer rather than adding more fuzzy recall. The whole pipeline stays measurable, auditable, and — unlike a model's latent representations — inspectable line by line.

Key Takeaway

For closed domains, NER does not need an LLM. Exact alias regex handles the head, fuzzy matching with Levenshtein and substring containment handles typos, notation patterns handle years and prices, and a knowledge base resolves ambiguity — all in milliseconds, all on-device, all auditable. Layer the passes from precise to fuzzy, measure precision and recall per entity type, and the result is extraction quality that general models struggle to match on the vocabulary users actually type.