2026-09-23·by Sijie Wang#lucerna#software#architecture

linguistic-two-engines

Two linguistic engines (TS-LLM vs Python-spaCy)

Parent: key-designs

The same conceptual outputs — lemma, POS, verb table, separable-verb reconstruction, grammar structure — are computed by two parallel engines, not a producer/consumer pipeline. The live client path is LLM-first and does not call the Python service for word lookup.

TS core (@lucerna/core) — the always-present client brain, any language. Owns tokenization/elision, all-forms matching, verb-form harvesting, the raw↔display geometry, and the POS taxonomy/colors/11-language labels. All content generation is delegated to a user-configured LLM (api/llmLookup.ts): basic lookup, verb tables, full inflected form-sets, sentence analysis (the grammar-structure string), POS coloring. The practical hard part is not trusting LLM output: cleanLLMJson carves the JSON substring from first {/[ to last }/] (killing fences/preamble) then runs jsonrepair (trailing commas, single quotes, truncation), with tolerant array extraction (sentences|result|analysis|data|items or any top-level array) and HTTP-status→localized-error mapping.

Python nlp-api — the deterministic, offline engine, DE+EN only. spaCy (de_core_news_lg / en_core_web_lg) does tokenize+lemmatize; a dependency-parse analyzer (infrastructure/grammar/dep_parse_analyzer.py) produces genuinely rich grammar: clause type (Hauptsatz vs Nebensatz via subordinators / cp/rc deps), tense inference (Perfekt/Plusquamperfekt/Futur/Präteritum from Partizip-II + aux morphology), word order (Inversion / verb-final), features (Passiv, Konjunktiv II, modal, negation, reflexive), then a subject/accusative/dative constituent breakdown — surfaced as a Chinese-annotated string. A Wiktionary/Postgres dictionary (preferring Chinese-meaning rows) and a CompositeTranslator (NLLB / Opus-MT by language pair) round it out.

Why both exist (the design logic). The LLM engine works for any language with rich target-language meanings but needs a user API key and JSON-repair; the spaCy engine is deterministic, offline, key-free, but bounded to DE+EN. German separable verbs are the clearest illustration of the split: a prefix-strip heuristic in TS (separableVerbService.ts) vs a true dependency-parse svp reconstruction in Python (spacy_tokenizer.py — finds the svp-dep token whose head is the verb, rebuilds prefix+lemma, capitalizes German noun lemmas). Same problem, two fidelities — the client can always answer, the server answers correctly for its two languages.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →