Skip to content

How a chatbot without an LLM works: YunoBot's C++ core in WebAssembly

Project documentation · 3 min read · updated

YunoBot is a classifier-and-retrieval chatbot, not a generative language model. A C++ core compiled to a 913,776-byte WebAssembly module turns each message into 4,096 hashed word, bigram and character-trigram features, runs them through a 96-unit ReLU layer (401,363 parameters in total) and either picks one of 82 conversational intents or declines. Factual questions are answered by quoting and linking passages from this site, ranked with BM25-style term weights.

Runs in
Your browser, inside a dedicated Web Worker
Core
C++ compiled to WebAssembly: 913,776 bytes
Network
4,096 hashed features → 96 ReLU units → 82 intents plus one out-of-scope class
Held-out accuracy
78.5% top-1 on 330 sentences never used for training (98.1% on the training sentences)
Languages
English, Turkish and mixed-language messages
Memory
Fixed 32 MiB ceiling; no threads, SIMD or shared memory

What happens to a message, step by step?

  1. The page's worker passes the text to core.wasm as UTF-8. Input is limited to 2,000 characters (8,000 bytes).
  2. The core tokenises it, folds Turkish case and builds the hashed features.
  3. It identifies the language from marker words, falling back to a learned naive-Bayes token table when the markers see nothing.
  4. The conversational network scores 82 intents plus an out-of-scope class. A confidence gate (minimum probability 0.45 and minimum margin 0.3) decides whether to accept the top intent.
  5. A confident match against reviewed site facts outranks a weak conversational guess.
  6. Otherwise it searches the public source index with BM25-style term weights, title and body evidence, bilingual term aliases and a preference for definitions.
  7. It selects or composes a reply. Replies rotate through variants so a repeated intent does not repeat the sentence, and follow-up questions stay in the previous source and skip passages already shown.

The dialogue state holds the last intent, the last source and, if the visitor says "my name is X", the name, for that conversation only.

How accurate is it?

Every build of the model writes metrics.json from a run that never saw the held-out sentences. The current file reports:

MeasureValue
Training sentences / held-out sentences1,992 / 330
Accuracy on training sentences98.1%
Top-1 accuracy on held-out sentences78.5%
Held-out sentences accepted by the confidence gate215 of 330, of which 198 were correct (92.1%) and 17 wrong
Held-out out-of-scope questions wrongly accepted3 of 92

The gap between 98.1% and 78.5% is the usual sign of a small training set; the project notes that more example sentences per intent is what raises the held-out numbers. Questions the gate does not accept fall through to source retrieval or an honest "not sure" reply. The separate regression suite is a set of checks, not an independent estimate of accuracy.

Why is the core so small?

The 401,363 parameters are mostly the first layer: 4,096 hashed inputs times 96 hidden units is 393,216 weights, stored as int8 with a per-feature scale. The module imports no host functions, has a fixed 32 MiB memory ceiling and, according to the project notes, does no heap allocation. It was exercised with 2,000 byte-input cases under AddressSanitizer and UBSan. It needs no SIMD, threads, shared memory or cross-origin-isolation headers, and the worker keeps its computation off the main thread so the page stays responsive.

The bundled corpus is 165 public pages of this site split into 933 passages, shipped as a 405,655-byte knowledge pack. Excerpts are quoted verbatim and link to their source; they may contain historical or speculative statements, and retrieval is not fact verification. The index has a capacity of 4,096 passages and a 10 MiB text pool, and passages over capacity are skipped rather than truncated into misleading quotes.

What can it get wrong?

It can misunderstand a question: roughly one in five held-out sentences is classified wrongly, and the gate trades coverage for precision. It is not a general-purpose assistant or a translator. Its answers about the site's author are reviewed, source-linked text rather than learned biography, and "latest" questions need a successfully refreshed snapshot of the sources.

Quick answers

Is YunoBot a large language model?
No. It combines a small intent classifier of 401,363 parameters, reviewed answers and BM25-style retrieval over this site's public pages. It does not generate free text: replies are selected, composed from reviewed parts or quoted from sources.
How big is the download?
The WebAssembly core is 913,776 bytes (800,524 with gzip level 9) and the bundled knowledge pack is 405,655 bytes (99,743 with gzip level 9).
Which languages does it understand?
English, Turkish and mixed-language messages. Language identification uses marker words and a learned naive-Bayes token table.
Why does it sometimes say it is not sure?
When no intent clears the confidence gate and no source passage matches well, it says so instead of guessing.

More on YunoBot