GitHub avatar

Fox's Blog

From ELIZA to LLMs: 60 Years of Conversational AI, Rebuilt in TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- five radically different architectures for the same problem, ported to TypeScript with their original data. From 1966 to modern LLMs, how conversational AI learned to talk, and what a chatbot repo teaches us about 60 years of research.

From ELIZA to LLMs: 60 years of conversational AI, rebuilt in TypeScript

In 1966, Joseph Weizenbaum wrote 420 lines of MAD-SLIP on an IBM 7094 to create the first chatbot in history. The program was called ELIZA, and it simulated a Rogerian psychotherapist with basic patterns and sentence permutations. Six decades later, conversational AI has gone mainstream -- ChatGPT, Claude, Gemini are in every conversation. When I reread that original code, I was struck by its economy.

But between those two extremes came PARRY (the paranoid chatbot, 1972), ALICE (the 99,000-category AIML king, 1995), Jabberwacky (the first to learn without rules, 1997), and Cleverbot (its industrial successor, 2008). I see them as five radically different answers to the same problem: making a machine talk.

I ported those five bots to TypeScript with their original data -- ELIZA scripts, PARRY dictionaries, ALICE AIML files. Each port is standalone, ready to run, and documented in the smallest detail. My goal with that work goes beyond just running: I want to understand how they worked, why they made history, and what their respective architectures reveal about yesterday's AI... and today's.

bun run eliza    # Talk to ELIZA (1966)
bun run parry    # Talk to PARRY (1972)
bun run alice    # Talk to ALICE (1995)
bun run jabber   # Talk to Jabberwacky
bun run cleverbot # Talk to Cleverbot
bun run meeting  # ELIZA vs PARRY automatic

I'll break down each bot with you, look at their code, then bridge to modern LLMs through the Luna Protocol articles.


ELIZA (1966): the art of sounding like you understand

I start with the oldest, and in my eyes the most impressive for its simplicity. ELIZA has no intelligence in the modern sense. It runs purely on text patterns with a bit of permutation.

The principle

The DOCTOR script (the psychotherapist version) works with a table of keywords, each tied to decomposition patterns and reassembly rules. A typical rule:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO is the keyword. 0 is a decomposition pattern saying "capture everything that follows" (like a wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. is the reassembly rule. That's it.

When you say "Hello, I'm sad today", ELIZA:

  1. Uppercases the text: HELLO I'M SAD TODAY
  2. Scans each word against its keyword table
  3. Finds HELLO → pushes it on the keyword stack
  4. Takes the highest-priority keyword
  5. Tries each decomposition pattern in order
  6. On match, picks the next reassembly rule (round-robin)
  7. Replaces (1), (2), etc. with the captured parts

But the really smart part is the PRE rules. Look at this:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

When ELIZA matches MY, it transforms the rest of the sentence (captured by 0) via the PRE rule, and reinjects the result as if the user had just said a new keyword. Concretely:

You say: "My mother hates me"
  → PRE transforms: "YOUR MOTHER HATES YOU"
  → reinjected as if you'd just said it
  → probably matches "YOU" → new reply

That's why ELIZA feels like it grasps the difference between "I" and "you": I see a perfectly designed mechanical transform simulating understanding.

Here's the full flow, from user keystroke to reply:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalises punctuation"]
    B --> C["splitUserInput()<br>splits into words"]
    C --> D["Build keyword stack<br>ordered by priority"]
    D --> E{"Stack non-empty?"}
    E -->|"Yes"| F["Pop highest-priority keyword"]
    E -->|"No"| G{"Memory recall?"}
    G -->|"Yes"| H["Recall past user statement"]
    G -->|"No"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"No"| M{"Linked keyword?"}
    M -->|"Yes"| N["Push linked keyword to stack"]
    N --> E
    M -->|"No"| O["Return NOMATCH"]
    O --> J
    L -->|"Yes"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

What made it believable

I consider Weizenbaum's pick a stroke of genius: Rogerian psychotherapy. That approach reflects the patient's words back without interpreting. "I'm sad" → "You say you're sad". That's exactly what ELIZA does, and since that therapeutic technique is recognized, nobody finds its reply strange. When you test the bot, you directly feel that mirror effect.

In the TypeScript port

In my port, I load the .ela scripts (original S-expression format), parse them fully (including the Hollerith encoding, that 60s string format), and run the same cycle: uppercasing → split → keyword stack → decomposition → reassembly → PRE/transforms. I wanted to stay faithful to the original behavior down to the details.

➡ See the source code


PARRY (1972): the first chatbot with emotions

Six years after ELIZA, Kenneth Colby (a Stanford psychiatrist) created PARRY: a chatbot simulating a patient with paranoid schizophrenia. Where ELIZA worked like an empty mirror, PARRY carries a real internal emotional model. That contrast struck me most porting them back to back.

The emotion model

PARRY has four continuous variables evolving every conversation turn:

Variable Baseline Decay/turn Description
ANGER 0 −1.0 Hostility, irritation
FEAR 0 −0.2 Paranoia (decays slowly after delusion onset)
MISTRUST 0 −0.05 Mistrust (very slow to come down)
HURT 0 −0.5 Emotional pain

Those values rise via emotional jumps (ajump, fjump, hjump) triggered by inference rules, and naturally decay toward their baselines each turn.

The belief network

PARRY has 200+ beliefs stored in the bel file:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Each belief has a category (HUM = the patient, HUM2 = others, DOC = the doctor, INT = the interrogation, INN = intentions) and a strength (0-5). Inference rules (TH2, EMOTE, IF) propagate beliefs between each other:

  • TH2: if a belief A exceeds a threshold, it reinforces itself and its consequences grow
  • EMOTE: if a belief exceeds a threshold, it triggers an emotional jump (anger/fear/hurt)
  • IF: conditional -- if A is true, then B becomes true at some level

The delusion hierarchy (flare system)

The most fascinating part of PARRY is its "flare" system -- an escalation chain gradually leading to the central delusion:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Each keyword triggers a pre-written reply (via pattern matching), and if you follow the topic, PARRY gradually drifts toward its central persecution delusion. Once a flare is "fired", it goes inactive (deadFlares) and PARRY moves to the next one, like a speaker digging into their subject. I really like that mechanic because it creates narrative progression with very little code.

Why PARRY is historic

In 1972, PARRY passed a variant of the Turing test: psychiatrists received transcripts of conversations with PARRY and with real paranoid patients, and could not reliably tell them apart. It's the first program to do it, 40 years before LLMs. I find that feat remarkable when you compare the era's means with today's.

➡ See the source code


RFC 439: when ELIZA meets PARRY

On September 18, 1972, two programs talked to each other for the first time in history, literally: it's the first recorded AI-to-AI conversation. ELIZA (running at BBN in Boston) and PARRY (running at Stanford) conversed over ARPANET, relayed by human operators typing one's output into the other's input. The transcript was published as RFC 439 ("PARRY Encounters the DOCTOR"). When I found that document, I understood it marked a founding moment for our field.

Here's a sample of what it looked like:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

The coherence of that exchange surprised me on first read. ELIZA does its therapist job: reframe, question, explore. PARRY does its paranoid patient job: complain, accuse, express mistrust. Both programs stay perfectly in role thanks to their mechanisms' combination (ELIZA patterns + PARRY emotion model) producing replies that fit together by accident. When you read the full transcript, you see how much that accidental alignment creates the illusion of deliberate dialogue.

You can reproduce that conversation with my repo:

bun run meeting

My simulation runs 25 automatic turns between both bots, with a random starting topic (horses, organized crime, emotions...). Since ELIZA and PARRY both carry non-deterministic elements (ELIZA round-robin, PARRY randomization), each run produces a different exchange. I love rerunning the command several times to compare trajectories.

What strikes me about ELIZA vs PARRY is two programs meeting, one with a stateless architecture, the other with a full emotion model, jointly producing a conversation that looks like something deliberate. For 1972, I find the result staggering.


ALICE (1995): pattern matching at scale

ALICE (Artificial Linguistic Internet Computer Entity) was created by Richard Wallace in 1995, and won the Loebner Prize three times (2000, 2001, 2004). Where ELIZA had a few hundred rules and PARRY a few thousand, ALICE has 99,524, spread over 66 AIML files. When I opened that corpus for the first time, I measured what "large scale" meant back then.

AIML: the category language

AIML (Artificial Intelligence Markup Language) is an XML format for defining question-answer pairs:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

But ALICE's power comes from wildcards and SRAI (Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- equivalent to <srai><star/></srai> -->
  </template>
</category>

SRAI lets ALICE redirect an input to another category, creating a reduction chain:

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

It's the mechanism giving ALICE its flexibility: instead of writing a reply for every possible phrasing, you write one canonical reply and redirect variations to it. The depth limit is 10: beyond that, ALICE gives up to avoid infinite loops (category design carefully avoids them, and that safety net stays essential). I find that design choice elegant because it factors thousands of phrasings with very few rules.

How ALICE matches patterns

Patterns are sorted by specificity: those with the fewest wildcards are tried first. The * and _ wildcards capture any word sequence. The engine compiles each pattern to regex, then iterates sorted categories until a match.

// Our TypeScript implementation -- simplified but faithful
function findMatch(input: string, categories: Category[]): Match | null {
  const regex = patternToRegex(cat.pattern);
  const match = input.match(regex);
  if (match) return { category: cat, wildcards: extractWildcards(match) };
  return null;
}

Why ALICE dominated the Loebners

99,524 categories is a number that changes everything. ELIZA gave the illusion of intelligence because its few rules were well designed for one specific context (therapy). ALICE covers so many topics it feels like it has real general knowledge: science, politics, humor, sports, emotions, it's all there. I take one lesson from that approach that I still apply: broad, careful coverage compensates for the absence of deep reasoning.

➡ See the source code


Jabberwacky (1997) & Cleverbot (2008): the epistemological break

All previous bots share one assumption: you must write the replies. ELIZA has its S-expression rules, PARRY its selective patterns, ALICE its AIML categories. Rollo Carpenter took the exact opposite stance with a radical question: what if past conversations supplied the replies? I find that reversal bold because it abandons all hand-written rules.

The idea

Jabberwacky (launched around 1997, became Cleverbot in 2008) relies entirely on the whole conversation history stored in a flat transcript, and when someone talks to it, it searches that history for the most similar moment and reuses what was said right after:

User: "hello"
  ↓
Search: has anyone said "hello" before?
  ↓
Yes, in session #3, line 14, someone said "hello" and the bot replied "hi there!"
  ↓
Reply: "hi there!"

You find here only a giant archive of things people said to each other, reused at the right moment. I see in that the very definition of emergence: conversational behavior born from an accumulation of human traces.

The TypeScript implementation

My TypeScript port reproduces exactly that architecture:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 seed lines + history"]
    B --> C["withReplies()<br>extracts pairs<br>(line → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>context before that line)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidates sorted"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>of winning pair"]
    J -->|"None"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append to transcript<br>save() → JSON"]
    L --> M

Here's the scoring core, with my own heuristic inspired by Cleverbot's public descriptions:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): similarity between user input and the historical line
  • contextFit (0.25): similarity between recent conversation and what preceded the historical line
  • recencyBonus (0.10): recent memories count a bit more (the bot's personality drifts over time)

The pick is probabilistic (roulette-wheel selection): the best candidate wins more often, with a share of variability adding variety. I appreciate that choice because it makes conversations less predictable when you test the bot several times.

Cleverbot: the two documented innovations

Cleverbot adds two mechanisms to Jabberwacky's base concept:

  1. Multi-person learning: millions of users contribute to the same shared transcript. A reply pulled from history can come from a completely different voice than the current conversation's -- which explains why Cleverbot suddenly switches personality.

  2. Deferred learning: what you tell Cleverbot during a session is NOT available for matching during that same session. New lines are marked pending and only become matchable after a "consolidation" between sessions -- which explains why you can't teach Cleverbot a fact and reuse it in the same conversation.

// Cleverbot: recent lines are invisible until consolidation
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() runs at startup, not during the session

My TypeScript port implements both behaviors: lines have a consolidated flag, and each REPL session starts with consolidating pending lines.

➡ See the source code


Analyzing the TypeScript port: designing a common architecture

Building those five bots in the same language confronted me with an interesting question: is it possible to factor code between such different architectures?

My answer after porting them all: very little. Each bot has a fundamentally different loop:

Bot Main loop Data Learning
ELIZA Keyword stack → decomposition → reassembly .ela scripts in S-expressions None
PARRY Tokenization → selective patterns / flares / keywords / inferences 58 PDP-10 files (dictionaries, beliefs, rules) None
ALICE Sorted patterns → regex → AIML template → recursive SRAI 66 AIML XML files None
Jabberwacky Similarity → context → recency → weighted pick JSON transcript (grows with use) Continuous
Cleverbot Same as Jabberwacky + pending/consolidated + personas JSON transcript + multi-persona seeds Deferred (between sessions)

What they share is the CLI interface and TypeScript infrastructure (biome for lint, tsx for running). The rest is architecture-specific.

Common design choices

1. Fidelity to original data. For ELIZA, PARRY, and ALICE, I use the original files: ELIZA scripts found in the Weizenbaum archives in 2021, PARRY's original PDP-10 code (58 files), Free ALICE AIML v1.6. I keep them in original form. The bots behave like the originals because they use the same data.

2. Clean-room for proprietary parts. Jabberwacky and Cleverbot pose a different case: their source was never published (Existor/Rollo Carpenter kept it proprietary). So my ports are clean-room reimplementations, built only from public descriptions of behavior. I copied no line of code or proprietary data.

3. Minimal dependencies. The only real prerequisite is TypeScript. ALICE uses dom-js to parse AIML XML files (66 files, 99,524 categories, hand-rolled XML parsing would be a waste of time). Everything else is vanilla TypeScript. I made that choice to keep each bot readable and easy to run when you clone the repo.


From symbolic chatbots to LLMs: the conceptual leap

The five bots I just showed you share one fundamental trait: they're symbolic. Their "knowledge" rests entirely on explicit symbols: text patterns, rule tables, XML categories, transcript lines. They work with zero numerical representation of language.

So they share the same glass ceiling: they only answer what was explicitly planned or recorded. ELIZA is lost if you leave the therapeutic frame. PARRY only talks about its paranoid themes. ALICE learns nothing from its conversations. Jabberwacky only replies with already-spoken lines. When you test those limits, you quickly see why that generation plateaued.

LLMs (Large Language Models) break through that ceiling by radically changing paradigm: instead of manipulating symbols, they convert language into numbers and learn statistical relations between those numbers. They generate each token on the fly by computing probabilities, from knowledge encoded in their weights rather than pre-written replies. I find that shift fascinating because it fully inverts the previous bots' logic. Let's quickly see how it works.

1. Tokenization

The first step is cutting text into tokens -- units smaller than words but bigger than characters:

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Each token has a numeric ID in a vocabulary (typically 32,000 to 128,000 tokens for recent models). That fragmentation lets the model handle words it never saw by breaking them into known sub-words.

2. Embeddings

Each token ID converts to a vector -- an array of floats (typically 4096 dimensions for a mid-size model). That vector is an embedding encoding the token's meaning in a mathematical space where semantically close tokens have close vectors:

vector("king")  − vector("man") + vector("woman")  ≈  vector("queen")

That property emerges from training, with no explicit programming from me or researchers. I see it as a direct consequence of how words appear in similar contexts.

3. Attention

The attention mechanism (introduced by the "Attention is All You Need" paper in 2017) is what made LLMs possible. For each token, attention computes which other tokens in the sentence matter to understand this one:

"The bank refused my loan."
      ↑
Token "bank" looks at: "refused", "loan" → understands it's a financial institution

"I'm going for a walk on the bank."
      ↑
Token "bank" looks at: "walk", "on" → understands it's a riverbank

Attention lets the model capture context: each token is understood through those around it, in a fully relational reading. It's that context sensitivity that most helped me understand why LLMs beat fixed patterns.

4. Next-token prediction

LLM training rests on deceptive simplicity: you show it text, hide the last token, and ask it to predict it. Then repeat billions of times. I like that idea because a very simple learning rule produces very rich capabilities.

Input:  "Je ne comprend"
Hidden:  "pas"
Model prediction: "pas" (probability 0.87), "rien" (0.05), "jamais" (0.02)...

The goal is maximizing the true token's probability at each position. That's called next-token prediction. During training, the model tunes its billions of parameters to minimize prediction error over terabytes of text.

At inference time (when you talk to it), the model generates one token at a time in a loop:

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

Each token is sampled by its probability (temperature, top-k, top-p control the "creativity" degree). And that's it. Billions of parameters repeating that operation thousands of times. I find it mind-bending that such a repetitive mechanism produces such varied replies.

What fundamentally changes

Aspect Symbolic bots (ELIZA, PARRY, ALICE) Modern LLMs
Representation Explicit words and rules Numeric vectors (embeddings)
Generation Selection from pre-written replies Token-by-token probabilistic prediction
Knowledge Stored in rule files Encoded in network weights
Learning Manual (rule writing) Automatic (corpus training)
Robustness Zero outside planned patterns Generalizes to never-seen inputs
Interpretability Perfect (you can read the rules) Limited (black box)

Classic chatbots are transparent but fragile. An LLM is robust but opaque. I see them today as complementary tools for different needs, and I pick one or the other based on how much control you want to keep over replies.

If you want to dig into how LLMs work inside, I recommend this video as an excellent resource:

How LLMs Work - YouTube

Luna Protocol: the modern synthesis

The Luna Protocol articles (links below) are in my eyes the most polished synthesis of everything I just showed you: a modern Discord bot combining a local LLM with a sophisticated behavioral system, all built on 60 years of conversational AI lessons. It's the project that let me connect my historical ports to my current experiments.

Luna Protocol: I built an autonomous Discord bot that simulates a human being

That article details the full architecture of an LLM-based Discord bot:

  • Priority trigger system (mention > DM > name > keyword > follow-up > random)
  • Human behaviors: variable concentration, typos, hesitations (15%), forgetfulness (3%), thematic fatigue
  • Sleep schedules: the bot sleeps, slows down, or ignores by time
  • TTS pipeline: voice synthesis via Piper + ffmpeg → Discord voice messages
  • Real-time streaming: the LLM emits tokens one by one on a typed event bus

What connects that article to historical chatbots is the same quest: making someone believe they're talking to a person. ELIZA used text mirrors. PARRY used an emotion model. ALICE used 99k categories. Luna Protocol uses a fine-tuned LLM + a behavioral system simulating human imperfections. I find in that lineage the same demand: creating the illusion of a presence on the other side of the screen.

Luna Protocol: why I fine-tuned a 1.5B model

The second article explores fine-tuning and few-shot priming. My core discovery: a smaller model (1.5B) trained on less data (50k samples) beats a bigger model (3B) when properly primed with few-shot examples. I was genuinely surprised by that result when I measured it.

It's a lesson resonating directly with historical chatbots:

  • ELIZA showed that with a few well-designed rules, you can simulate understanding
  • ALICE showed that with 99k categories, you can simulate general knowledge
  • Luna Protocol shows that with good fine-tuning and 5 few-shot examples, a small LLM can simulate a human being

The technique changes, and the principle stays: data quality and system precision matter more than raw size. It's a conviction I defend in my own experiments.


Conclusion: three things to take away

1. Conversational AI didn't start with ChatGPT. ELIZA is 60 years old. PARRY passed the Turing test in 1972. ALICE won the Loebner three times. Jabberwacky laid the groundwork for transcript learning, which Cleverbot industrialized at scale. Each approach brought a piece of the puzzle. That's what my ports confirmed.

2. More data ≠ smarter. Jabberwacky's transcript has no rules. ALICE's 99k categories don't learn. Luna Protocol's fine-tuning on 50k samples beats the 3B model. Conventional wisdom says "bigger is better" -- chatbot history shows architecture and design matter as much as size. I defend that idea in each of my technical choices.

3. The problem is the same as 60 years ago. How to make a human believe they're talking to another human? ELIZA answered with text mirrors. PARRY with simulated anger. ALICE with facts. Luna Protocol with an LLM that sleeps and makes typos. The solution changes, the need stays. It's that continuity that makes me want to keep exploring.

The repo is open source, you can clone it, run each bot, and see for yourself how 60 years of conversational AI fit in a single TypeScript repo. I hope you enjoy making them talk as much as I did.

Resource Link
GitHub repo fox3000foxy/chatbots
Luna Protocol -- bot architecture Read the article
Luna Protocol -- few-shot fine-tuning Read the article
Original ELIZA scripts anthay/ELIZA
Original PARRY source lexcore/PARRY
Free ALICE AIML v1.6 drwallace/aiml-en-us-foundation-alice
Original RFC 439 PARRY Encounters the DOCTOR
Excellent LLM explainer https://www.youtube.com/watch?v=YmLp8qe87A0

Des ELIZA aux LLM : 60 ans d'IA conversationnelle, reconstruite en TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- cinq architectures radicalement différentes du même problème, portées en TypeScript avec leurs données d'origine. De 1966 aux LLM modernes, voici comment l'IA conversationnelle a appris à parler, et ce qu'un repo de chatbots nous apprend sur 60 ans de recherche.

Des ELIZA aux LLM : 60 ans d'IA conversationnelle, reconstruite en TypeScript

En 1966, Joseph Weizenbaum écrivait 420 lignes de MAD-SLIP sur un IBM 7094 pour créer le premier chatbot de l'histoire. Le programme s'appelait ELIZA, et il simulait une psychothérapeute rogérienne avec des motifs de base et des permutations de phrases. Six décennies plus tard, l'IA conversationnelle est devenue un sujet grand public -- ChatGPT, Claude, Gemini sont dans toutes les conversations. Quand j'ai relu ce code d'origine, j'ai été frappé par son économie de moyens.

Mais entre ces deux extrêmes, il y a eu PARRY (le chatbot paranoïaque, 1972), ALICE (le roi de l'AIML à 99 000 catégories, 1995), Jabberwacky (le premier à apprendre sans règles, 1997), et Cleverbot (son successeur industriel, 2008). Je les vois comme cinq réponses radicalement différentes au même problème : faire parler une machine.

J'ai porté ces cinq bots en TypeScript avec leurs données d'origine -- scripts ELIZA, dictionnaires PARRY, fichiers AIML d'ALICE. Chaque port est autonome, prêt à l'emploi, et documenté dans les moindres détails. Mon objectif avec ce travail dépasse la simple exécution : je veux comprendre comment ils fonctionnaient, pourquoi ils ont marqué l'histoire, et ce que leurs architectures respectives révèlent sur l'IA d'hier... et d'aujourd'hui.

bun run eliza    # Parle à ELIZA (1966)
bun run parry    # Parle à PARRY (1972)
bun run alice    # Parle à ALICE (1995)
bun run jabber   # Parle à Jabberwacky
bun run cleverbot # Parle à Cleverbot
bun run meeting  # ELIZA vs PARRY automatique

Je vais décortiquer chaque bot avec toi, regarder leur code, puis faire le pont avec les LLM modernes à travers les articles sur Luna Protocol.


ELIZA (1966) : l'art de faire croire qu'on comprend

Je commence par la plus ancienne, et à mes yeux la plus impressionnante par sa simplicité. ELIZA n'a aucune intelligence au sens moderne du terme. Elle fonctionne uniquement avec des motifs textuels et un peu de permutation.

Le principe

Le script DOCTOR (la version psychothérapeute) fonctionne avec un tableau de keywords, chacun associé à des décomposition patterns et des reassembly rules. Voici une règle typique :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO est le mot-clé. 0 est un pattern de décomposition qui dit "capture tout ce qui suit" (comme un wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. est la règle de réassemblage. C'est tout.

Quand tu dis "Hello, I'm sad today", ELIZA :

  1. Met le texte en majuscules : HELLO I'M SAD TODAY
  2. Scanne chaque mot contre sa table de keywords
  3. Trouve HELLO → le pousse sur la pile de keywords
  4. Prend le keyword avec la plus haute priorité
  5. Essaie chaque pattern de décomposition dans l'ordre
  6. Si ça match, sélectionne la prochaine règle de réassemblage (round-robin)
  7. Remplace les (1), (2) etc. par les parties capturées

Mais la partie vraiment intelligente, c'est les PRE rules. Regarde ça :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Quand ELIZA matche MY, elle transforme le reste de la phrase (capturé par 0) via la PRE rule, et réinjecte le résultat comme si l'utilisateur venait de dire un nouveau mot-clé. Concrètement :

Tu dis: "My mother hates me"
  → PRE transforme: "YOUR MOTHER HATES YOU"
  → réinjecté comme si tu venais de le dire
  → matche probablement "YOU" → nouvelle réponse

C'est pour ça qu'ELIZA donne l'impression de comprendre la différence entre "je" et "tu" : je vois là une transformation mécanique parfaitement conçue qui simule la compréhension.

Voici le flux complet, de la frappe utilisateur à la réponse :

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalise la ponctuation"]
    B --> C["splitUserInput()<br>découpe en mots"]
    C --> D["Build keyword stack<br>ordonné par priorité"]
    D --> E{"Stack non-vide?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

Ce qui la rendait crédible

Je considère le choix de Weizenbaum comme un coup de génie : la psychothérapie rogérienne. Cette approche consiste à refléter les propos du patient sans interpréter. "Je suis triste" → "Vous dites que vous êtes triste". C'est exactement ce qu'ELIZA sait faire, et comme cette technique thérapeutique est reconnue, personne ne trouve sa réponse étrange. Quand tu testes le bot, tu ressens directement cet effet miroir.

Dans le port TypeScript

Dans mon port, je charge les scripts .ela (format S-expression d'origine), je les parse entièrement (y compris l'encodage Hollerith, ce format de chaîne des années 60), et j'exécute le même cycle : uppercasing → split → keyword stack → décomposition → réassemblage → PRE/transforms. J'ai voulu rester fidèle au comportement d'origine jusque dans les détails.

➡ Voir le code source


PARRY (1972) : le premier chatbot avec des émotions

Six ans après ELIZA, Kenneth Colby (psychiatre à Stanford) a créé PARRY : un chatbot qui simule un patient atteint de schizophrénie paranoïde. Là où ELIZA fonctionnait comme un miroir vide, PARRY embarque un véritable modèle émotionnel interne. C'est ce contraste qui m'a le plus marqué en les portant l'un après l'autre.

Le modèle émotionnel

PARRY a quatre variables continues qui évoluent à chaque tour de conversation :

Variable Ligne de base Décroissance/tour Description
ANGER 0 −1.0 Hostilité, irritation
FEAR 0 −0.2 Paranoïa (décroît lentement après le début du délire)
MISTRUST 0 −0.05 Méfiance (très lente à redescendre)
HURT 0 −0.5 Peine émotionnelle

Ces valeurs augmentent via des emotional jumps (ajump, fjump, hjump) déclenchés par des règles d'inférence, et décroissent naturellement vers leurs lignes de base à chaque tour.

Le réseau de croyances

PARRY a 200+ croyances stockées dans le fichier bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Chaque croyance a une catégorie (HUM = le patient, HUM2 = les autres, DOC = le docteur, INT = l'interrogatoire, INN = les intentions) et une force (0-5). Les règles d'inférence (TH2, EMOTE, IF) propagent les croyances entre elles :

  • TH2 : si une croyance A dépasse un seuil, elle se renforce et ses conséquences augmentent
  • EMOTE : si une croyance dépasse un seuil, elle déclenche un saut émotionnel (anger/fear/hurt)
  • IF : conditionnel -- si A est vraie, alors B devient vraie à un certain niveau

La hiérarchie des délires (flare system)

La partie la plus fascinante de PARRY, c'est son système de "flares" -- une chaîne d'escalade qui mène progressivement vers le délire central :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Chaque mot-clé déclenche une réponse pré-écrite (via le pattern matching), et si tu suis le sujet, PARRY dérive progressivement vers son délire central de persécution. Une fois qu'un flare est "déclenché", il devient inactif (deadFlares) et PARRY passe au suivant, comme un interlocuteur qui creuse son sujet. J'aime beaucoup cette mécanique parce qu'elle crée une progression narrative avec très peu de code.

Pourquoi PARRY est historique

En 1972, PARRY a passé une variante du test de Turing : des psychiatres ont reçu des transcriptions de conversations avec PARRY et avec de vrais patients paranoïaques, et n'ont pas pu les distinguer de façon fiable. C'est le premier programme à y parvenir, 40 ans avant les LLM. Je trouve cet exploit remarquable quand tu compares les moyens de l'époque avec ceux d'aujourd'hui.

➡ Voir le code source


RFC 439 : quand ELIZA rencontre PARRY

Le 18 septembre 1972, deux programmes se sont parlé pour la première fois de l'histoire, littéralement : c'est la première conversation inter-IA jamais enregistrée. ELIZA (tournant sur BBN à Boston) et PARRY (tournant à Stanford) ont conversé via l'ARPANET, relayés par des opérateurs humains qui tapaient la sortie de l'un vers l'entrée de l'autre. La transcription a été publiée comme RFC 439 ("PARRY Encounters the DOCTOR"). Quand j'ai découvert ce document, j'ai compris qu'il marquait un moment fondateur pour notre domaine.

Voici un extrait de ce à quoi ça ressemblait :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

La cohérence de cet échange m'a surpris à la première lecture. ELIZA fait son travail de thérapeute : reformuler, questionner, explorer. PARRY fait son travail de patient paranoïaque : se plaindre, accuser, exprimer de la méfiance. Les deux programmes restent parfaitement dans leur rôle grâce à la combinaison de leurs mécanismes respectifs (patterns ELIZA + modèle émotionnel PARRY) qui produit des réponses qui s'emboîtent par hasard. Quand tu lis la transcription en entier, tu vois à quel point cet alignement accidentel crée l'illusion d'un dialogue intentionnel.

Tu peux reproduire cette conversation avec mon repo :

bun run meeting

Ma simulation lance 25 tours automatiques entre les deux bots, avec un sujet de départ aléatoire (chevaux, crime organisé, émotions...). Comme ELIZA et PARRY embarquent tous deux des éléments non-déterministes (round-robin ELIZA, randomisation PARRY), chaque exécution produit un échange différent. J'adore relancer la commande plusieurs fois pour comparer les trajectoires.

Ce qui me frappe avec ELIZA vs PARRY, c'est la rencontre entre deux programmes, l'un avec une architecture sans état interne, l'autre avec un modèle émotionnel complet, qui produisent ensemble une conversation qui ressemble à quelque chose de délibéré. Pour 1972, je trouve le résultat sidérant.


ALICE (1995) : le pattern matching à grande échelle

ALICE (Artificial Linguistic Internet Computer Entity) a été créée par Richard Wallace en 1995, et a gagné le Loebner Prize trois fois (2000, 2001, 2004). Là où ELIZA avait quelques centaines de règles et PARRY quelques milliers, ALICE en a 99 524, réparties dans 66 fichiers AIML. Quand j'ai ouvert ce corpus pour la première fois, j'ai mesuré ce que "grande échelle" voulait dire à l'époque.

AIML : le langage des catégories

AIML (Artificial Intelligence Markup Language) est un format XML pour définir des paires question-réponse :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Mais la puissance d'ALICE vient des wildcards et du SRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- équivaut à <srai><star/></srai> -->
  </template>
</category>

Le SRAI permet à ALICE de rediriger une entrée vers une autre catégorie, créant une chaîne de réduction :

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

C'est le mécanisme qui donne à ALICE sa flexibilité : au lieu d'écrire une réponse pour chaque formulation possible, tu écris une réponse canonique et tu rediriges les variations vers elle. La limite de profondeur est de 10 : au-delà, ALICE abandonne pour éviter les boucles infinies (la conception des catégories les évite soigneusement, et ce filet de sécurité reste essentiel). Je trouve ce choix de conception élégant parce qu'il factorise des milliers de formulations avec très peu de règles.

Comment ALICE matche les patterns

Les patterns sont triés par spécificité : ceux avec le moins de wildcards sont essayés en premier. Les wildcards * et _ capturent n'importe quelle séquence de mots. Le moteur compile chaque pattern en regex, puis itère les catégories triées jusqu'à trouver un match.

// Notre implémentation TypeScript -- simplifiée mais fidèle
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Pourquoi ALICE a dominé les Loebner

99 524 catégories, c'est un nombre qui change tout. ELIZA donnait l'illusion de l'intelligence parce que ses quelques règles étaient bien conçues pour un contexte spécifique (la thérapie). ALICE couvre tellement de sujets qu'elle donne l'impression d'avoir une vraie culture générale : sciences, politique, humour, sports, émotions, tout y est. Je retiens de cette approche une leçon que j'applique encore : une couverture large et soignée compense l'absence de raisonnement profond.

➡ Voir le code source


Jabberwacky (1997) & Cleverbot (2008) : la rupture épistémologique

Tous les bots précédents partagent une hypothèse : il faut écrire les réponses. ELIZA a ses règles S-expressions, PARRY ses patterns sélécifs, ALICE ses catégories AIML. Rollo Carpenter a pris le contre-pied total avec une question radicale : et si on laissait les conversations passées fournir les réponses ? Je trouve ce renversement audacieux parce qu'il abandonne toute écriture manuelle de règles.

L'idée

Jabberwacky (lancé vers 1997, devenu Cleverbot en 2008) repose entièrement sur tout l'historique des conversations stocké dans un transcript plat, et quand quelqu'un lui parle, il cherche dans cet historique le moment le plus similaire et réutilise ce qui a été dit après :

Utilisateur: "hello"
  ↓
Chercher: est-ce que quelqu'un a déjà dit "hello" avant ?
  ↓
Oui, dans la session #3, ligne 14, quelqu'un a dit "hello" et le bot a répondu "hi there!"
  ↓
Répondre: "hi there!"

Tu trouveras ici uniquement une archive géante de choses que des gens se sont dites, réutilisée au moment opportun. J'y vois la définition même de l'émergence : un comportement conversationnel naît d'une accumulation de traces humaines.

L'implémentation TypeScript

Mon port TypeScript reproduit cette architecture exacte :

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 lignes seed + historique"]
    B --> C["withReplies()<br>extrait les paires<br>(ligne → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>context avant cette ligne)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidats triés"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>de la paire gagnante"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append au transcript<br>save() → JSON"]
    L --> M

Voici le cœur du scoring, avec ma propre heuristique inspirée des descriptions publiques de Cleverbot :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : similarité entre l'entrée utilisateur et la ligne historique
  • contextFit (0.25) : similarité entre la conversation récente et ce qui précédait la ligne historique
  • recencyBonus (0.10) : les souvenirs récents comptent un peu plus (la personnalité du bot dérive avec le temps)

Le pick est probabiliste (roulette-wheel selection) : le meilleur candidat gagne plus souvent, avec une part de variabilité qui donne de la variété. J'apprécie ce choix parce qu'il rend les conversations moins prévisibles quand tu testes le bot plusieurs fois.

Cleverbot : les deux innovations documentées

Cleverbot ajoute deux mécanismes au concept de base de Jabberwacky :

  1. Apprentissage multi-personne : des millions d'utilisateurs contribuent au même transcript partagé. Une réponse tirée de l'historique peut venir d'une voix complètement différente de celle de la conversation en cours -- ce qui explique pourquoi Cleverbot change soudain de personnalité.

  2. Apprentissage différé : ce que tu dis à Cleverbot pendant une session n'est PAS disponible pour match pendant cette même session. Les nouvelles lignes sont marquées pending et ne deviennent matchables qu'après une "consolidation" entre les sessions -- ce qui explique pourquoi tu ne peux pas apprendre un fait à Cleverbot et le réutiliser dans la même conversation.

// Cleverbot : les lignes récentes sont invisibles jusqu'à consolidation
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() est appelée au démarrage, pas pendant la session

Mon port TypeScript implémente ces deux comportements : les lignes ont un flag consolidated, et chaque session de REPL commence par une consolidation des lignes en attente.

➡ Voir le code source


Analyse du port TypeScript : concevoir une architecture commune

Construire ces cinq bots dans le même langage m'a confronté à une question intéressante : est-il possible de factoriser du code entre des architectures aussi différentes ?

Ma réponse après les avoir tous portés : très peu. Chaque bot a une boucle fondamentale différente :

Bot Boucle principale Données Apprentissage
ELIZA Keyword stack → décomposition → réassemblage Scripts .ela en S-expressions Aucun
PARRY Tokenisation → patterns sélécifs / flares / keywords / inférences 58 fichiers PDP-10 (dictionnaires, croyances, règles) Aucun
ALICE Patterns triés → regex → template AIML → SRAI récursif 66 fichiers AIML XML Aucun
Jabberwacky Similarité → contexte → recency → pick pondéré Transcript JSON (grandit avec l'usage) Continue
Cleverbot Même que Jabberwacky + pending/consolidated + personas Transcript JSON + graines multi-personas Différé (entre sessions)

Ce qu'ils partagent, c'est l'interface CLI et l'infrastructure TypeScript (biome pour le lint, tsx pour l'exécution). Le reste est spécifique à chaque architecture.

Choix de conception communs

1. Fidélité aux données originales. Pour ELIZA, PARRY et ALICE, j'utilise les fichiers d'origine, scripts ELIZA retrouvés dans les archives Weizenbaum en 2021, code original PARRY du PDP-10 (58 fichiers), AIML Free ALICE v1.6. Je les conserve dans leur forme d'origine. Les bots se comportent comme les originaux parce qu'ils utilisent les mêmes données.

2. Clean-room pour les parties propriétaires. Jabberwacky et Cleverbot posent un cas différent : leur code source n'a jamais été publié (Existor/Rollo Carpenter l'a gardé propriétaire). Mes ports sont donc des clean-room reimplementations, construites uniquement à partir de descriptions publiques du comportement. Je n'ai copié aucune ligne de code ni donnée propriétaire.

3. Dépendances minimales. Le seul vrai prérequis, c'est TypeScript. ALICE utilise dom-js pour parser l'XML des fichiers AIML (66 fichiers, 99 524 catégories, le parsing XML fait maison serait une perte de temps). Tout le reste est en vanilla TypeScript. J'ai fait ce choix pour garder chaque bot lisible et facile à lancer quand tu clones le repo.


Des chatbots symboliques aux LLM : le saut conceptuel

Les cinq bots que je viens de te présenter partagent une caractéristique fondamentale : ils sont symboliques. Leurs "connaissances" reposent entièrement sur des symboles explicites : motifs textuels, tables de règles, catégories XML, lignes de transcript. Ils fonctionnent entièrement sans représentation numérique du langage.

Ils partagent donc le même plafond de verre : ils répondent uniquement à ce qui a été explicitement prévu ou enregistré. ELIZA est perdue si tu sors du cadre thérapeutique. PARRY parle uniquement de ses thèmes paranoïaques. ALICE n'apprend rien de ses conversations. Jabberwacky répond uniquement par des répliques déjà prononcées. Quand tu testes ces limites, tu comprends vite pourquoi cette génération a plafonné.

Les LLM (Large Language Models) franchissent ce plafond en changeant radicalement de paradigme : au lieu de manipuler des symboles, ils convertissent le langage en nombres et apprennent des relations statistiques entre ces nombres. Ils génèrent chaque token à la volée en calculant des probabilités, à partir de connaissances encodées dans leurs poids plutôt que de réponses pré-écrites. Je trouve ce basculement fascinant parce qu'il inverse complètement la logique des bots précédents. Voyons rapidement comment ça marche.

1. Tokenization

La première étape est de découper le texte en tokens -- des unités plus petites que des mots mais plus grandes que des caractères :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Chaque token a un ID numérique dans un vocabulaire (typiquement 32 000 à 128 000 tokens pour les modèles récents). Cette fragmentation permet au modèle de gérer des mots qu'il n'a jamais vus en les décomposant en sous-mots connus.

2. Embeddings

Chaque token ID est converti en un vecteur -- un tableau de nombres flottants (typiquement 4096 dimensions pour un modèle de taille moyenne). Ce vecteur est un plongement (embedding) qui encode le sens du token dans un espace mathématique où des tokens sémantiquement proches ont des vecteurs proches :

vecteur("roi")  − vecteur("homme") + vecteur("femme")  ≈  vecteur("reine")

Cette propriété émerge de l'entraînement, sans programmation explicite de ma part ou de celle des chercheurs. J'y vois une conséquence directe de la façon dont les mots apparaissent dans des contextes similaires.

3. Attention

Le mécanisme d'attention (introduit par le papier "Attention is All You Need" en 2017) est ce qui a rendu les LLM possibles. Pour chaque token, l'attention calcule quels autres tokens dans la phrase sont importants pour comprendre celui-ci :

"La banque a refusé mon prêt."
      ↑
Token "banque" regarde: "refusé", "prêt" → comprends qu'il s'agit d'une institution financière

"Je vais me promener sur la banque."
      ↑
Token "banque" regarde: "promener", "sur" → comprends qu'il s'agit d'une rive

L'attention permet au modèle de capturer le contexte : chaque token est compris en fonction de ceux qui l'entourent, dans une lecture pleinement relationnelle. C'est cette sensibilité au contexte qui m'a le plus aidé à comprendre pourquoi les LLM dépassent les patterns fixes.

4. Prédiction du prochain token

L'entraînement d'un LLM repose sur une simplicité trompeuse : tu lui montres un texte, tu caches le dernier token, et tu lui demandes de le prédire. Puis tu répètes l'opération des milliards de fois. J'aime cette idée parce qu'une règle d'apprentissage très simple produit des capacités très riches.

Input:  "Je ne comprends"
Caché:  "pas"
Prédiction du modèle: "pas" (probabilité 0.87), "rien" (0.05), "jamais" (0.02)...

L'objectif est de maximiser la probabilité du vrai token à chaque position. C'est ce qu'on appelle la next-token prediction. Pendant l'entraînement, le modèle ajuste ses milliards de paramètres pour minimiser l'erreur de prédiction sur des téraoctets de texte.

Au moment de l'inférence (quand tu lui parles), le modèle génère un token à la fois en boucle :

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

Chaque token est échantillonné selon sa probabilité (température, top-k, top-p contrôlent le degré de "créativité"). Et c'est tout. Des milliards de paramètres qui répètent cette opération des milliers de fois. Je trouve vertigineux qu'un mécanisme aussi répétitif produise des réponses aussi variées.

Ce qui change fondamentalement

Aspect Bots symboliques (ELIZA, PARRY, ALICE) LLM modernes
Représentation Mots et règles explicites Vecteurs numériques (embeddings)
Génération Sélection dans des réponses pré-écrites Prédiction probabiliste token par token
Connaissances Stockées dans des fichiers de règles Encodées dans les poids du réseau
Apprentissage Manuel (rédaction de règles) Automatique (entraînement sur corpus)
Robustesse Nulle hors des motifs prévus Généralise à des entrées jamais vues
Interprétabilité Parfaite (on peut lire les règles) Limité (boîte noire)

Les chatbots classiques sont transparents mais fragiles. Un LLM est robuste mais opaque. Je les vois aujourd'hui comme des outils complémentaires pour des besoins différents, et je choisis l'un ou l'autre selon le niveau de contrôle que tu veux garder sur les réponses.

Si tu veux approfondir le fonctionnement interne des LLM, je te recommande cette vidéo comme excellente ressource :

How LLMs Work — YouTube

Luna Protocol : la synthèse moderne

Les articles sur Luna Protocol (dont les liens sont ci-dessous) représentent à mes yeux la synthèse la plus aboutie de tout ce que je viens de te montrer : un bot Discord moderne qui combine un LLM local avec un système comportemental sophistiqué, le tout construit sur les leçons de 60 ans d'IA conversationnelle. C'est le projet qui m'a permis de relier mes ports historiques à mes expérimentations actuelles.

Luna Protocol : j'ai créé un bot Discord autonome qui simule un être humain

Cet article détaille l'architecture complète d'un bot Discord LLM-based :

  • Système de déclenchement à priorités (mention > DM > nom > mot-clé > follow-up > aléatoire)
  • Comportements humains : concentration variable, fautes de frappe, hésitations (15%), oublis (3%), fatigue thématique
  • Horaires de sommeil : le bot dort, ralentit, ou ignore selon l'heure
  • Pipeline TTS : synthèse vocale via Piper + ffmpeg → messages vocaux Discord
  • Streaming en temps réel : le LLM émet les tokens un par un sur un bus d'événements typé

Ce qui relie cet article aux chatbots historiques, c'est la même quête : faire croire qu'on parle à une personne. ELIZA utilisait des miroirs textuels. PARRY utilisait un modèle émotionnel. ALICE utilisait 99k catégories. Luna Protocol utilise un LLM fine-tuné + un système comportemental qui simule les imperfections humaines. Je retrouve dans cette filiation la même exigence : créer l'illusion d'une présence de l'autre côté de l'écran.

Luna Protocol : pourquoi j'ai fine-tuné un modèle de 1,5B

Le second article explore le fine-tuning et le few-shot priming. Ma découverte centrale : un modèle plus petit (1,5B) entraîné sur moins de données (50k échantillons) surpasse un modèle plus gros (3B) quand on l'amorce correctement avec des exemples few-shot. J'ai été vraiment surpris par ce résultat quand je l'ai mesuré.

C'est une leçon qui résonne directement avec les chatbots historiques :

  • ELIZA montrait qu'avec quelques règles bien conçues, on peut simuler la compréhension
  • ALICE montrait qu'avec 99k catégories, on peut simuler la culture générale
  • Luna Protocol montre qu'avec un bon fine-tuning et 5 exemples few-shot, un petit LLM peut simuler un être humain

La technique change, et le principe reste le même : la qualité des données et la précision du système comptent plus que la taille brute. C'est une conviction que je défends dans mes propres expériences.


Conclusion : trois choses à retenir

1. L'IA conversationnelle n'a pas commencé avec ChatGPT. ELIZA a 60 ans. PARRY a passé le test de Turing en 1972. ALICE a gagné le Loebner trois fois. Jabberwacky a posé les bases de l'apprentissage par transcript, que Cleverbot a industrialisé à grande échelle. Chaque approche a apporté une pièce du puzzle. C'est ce que mes ports m'ont confirmé.

2. Plus de données ≠ plus intelligente. Le transcript de Jabberwacky n'a pas de règles. Les 99k catégories d'ALICE n'apprennent pas. Le fine-tuning de Luna Protocol sur 50k échantillons surpasse le modèle 3B. La sagesse conventionnelle dit "plus c'est gros, mieux c'est" -- l'histoire des chatbots montre que l'architecture et la conception comptent autant que la taille. Je défends cette idée dans chacun de mes choix techniques.

3. Le problème est le même depuis 60 ans. Comment faire croire à un humain qu'il parle à un autre humain ? ELIZA répondait avec des miroirs textuels. PARRY avec de la colère simulée. ALICE avec des faits. Luna Protocol avec un LLM qui dort et fait des fautes de frappe. La solution change, le besoin reste. C'est cette continuité qui me donne envie de continuer à explorer.

Le repo est open source, tu peux le cloner, lancer chaque bot, et voir par toi-même comment 60 ans d'IA conversationnelle tiennent dans un seul dépôt TypeScript. J'espère que tu prendras autant de plaisir que moi à les faire parler.

Ressource Lien
Dépôt GitHub fox3000foxy/chatbots
Luna Protocol -- architecture bot Lire l'article
Luna Protocol -- fine-tuning few-shot Lire l'article
ELIZA scripts originaux anthay/ELIZA
Code source PARRY original lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 originale PARRY Encounters the DOCTOR
Excellente explication du fonctionnement des LLM https://www.youtube.com/watch?v=YmLp8qe87A0

从 ELIZA 到 LLM:60 年对话 AI,TypeScript 重建版

ELIZA、PARRY、ALICE、Jabberwacky、Cleverbot,同一问题五种完全不同的架构,带原始数据移植到 TypeScript。从 1966 到现代 LLM,对话 AI 怎么学会说话,一个聊天机器人仓库讲 60 年研究。

从 ELIZA 到 LLM:60 年对话 AI,TypeScript 重建版

1966 年,Joseph Weizenbaum 在 IBM 7094 上写 420 行 MAD-SLIP,造出史上第一个聊天机器人。程序叫 ELIZA,装 Rogeria 心理治疗师,靠基础 pattern 加语句变换。六十年后对话 AI 成大众话题,ChatGPT、Claude、Gemini 天天见。重读那份原始代码,我被它的省惊到。

但两端之间还有 PARRY(偏执聊天机器人,1972)、ALICE(9.9 万类别的 AIML 王者,1995)、Jabberwacky(第一个无规则自学的,1997)、Cleverbot(工业级后继,2008)。在我看是同一问题五种完全不同的答案:怎么让机器说话。

我把五个机器人带原始数据移植到 TypeScript,ELIZA 脚本、PARRY 词典、ALICE AIML 文件全有。每移植独立可跑,文档细到每步。我做的不只是跑起来:想懂它们怎么跑、为什么载入史册、各自架构对昨天 AI 和今天 AI 说了什么。

bun run eliza    # 和 ELIZA 聊(1966)
bun run parry    # 和 PARRY 聊(1972)
bun run alice    # 和 ALICE 聊(1995)
bun run jabber   # 和 Jabberwacky 聊
bun run cleverbot # 和 Cleverbot 聊
bun run meeting  # ELIZA 对 PARRY 自动聊

一个个拆给你看,看代码,再经 Luna Protocol 文章连到现代 LLM。


ELIZA(1966):装懂的艺术

先讲最老的,在我眼里也最 impress 的省。ELIZA 现代意义零智能。只有文本 pattern 加一点换位。

原理

DOCTOR 脚本(心理治疗师版)跑关键词表,每词配分解 pattern 和重组规则。典型规则:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO 关键词。0 分解 pattern 说"后面全收"(通配符)。HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. 重组规则。完。

你说"Hello, I'm sad today",ELIZA:

  1. 全大写:HELLO I'M SAD TODAY
  2. 逐词扫关键词表
  3. 命中 HELLO,压关键词栈
  4. 取最高优先级关键词
  5. 按序试每个分解 pattern
  6. 命中选下条重组规则(轮转)
  7. (1)、(2) 等换成捕获块

真聪明的在 PRE 规则。看这个:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

ELIZA 命中 MY,0 捕获的后半句过 PRE 规则变换,当用户新说一关键词重注。实际:

你说:"My mother hates me"
  → PRE 变换:"YOUR MOTHER HATES YOU"
  → 当你刚说的重注
  → 大概率命中 "YOU",出新回复

ELIZA 显得懂"我"你区别的原因:我看是台机械变换,设计完美,装出理解。

完整流程,从敲字到回复:

flowchart TD
    A["用户输入:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>标点归一"]
    B --> C["splitUserInput()<br>切词"]
    C --> D["按优先级建关键词栈"]
    D --> E{"栈空?"}
    E -->|"否"| F["弹最高优先级关键词"]
    E -->|"是"| G{"有记忆可回?"}
    G -->|"有"| H["回用户之前的话"]
    G -->|"无"| I["回退:zNONE 规则"]
    I --> J["返回回复"]
    H --> J
    F --> K["试分解 pattern"]
    K --> L{"命中?"}
    L -->|"否"| M{"有链接关键词?"}
    M -->|"有"| N["链接关键词压栈"]
    N --> E
    M -->|"无"| O["返回 NOMATCH"]
    O --> J
    L -->|"是"| P["选下条重组(轮转)"]
    P --> Q{"重组类型?"}
    Q -->|"PRE"| R["词变换(I→YOU)<br>压链接关键词"]
    R --> N
    Q -->|"NEWKEY"| S["跳下关键词"]
    S --> E
    Q -->|"标准"| T["(1)、(2)、(0)<br>展开成最终回复"]
    T --> J

为什么像真的

Weizenbaum 的选择我看是神来之笔:Roger 式心理治疗。这流派只反射病人的话不解读。"我难过"回"你说你难过"。ELIZA 只会这个,偏偏这疗法正经,没人觉得回复怪。跑机器人直接感到镜面效应。

TypeScript 移植

我移植载 .ela 脚本(原始 S-expression 格式),全解析(含 Hollerith 编码,60 年代那套字符串格式),跑同样循环:大写,切词,关键词栈,分解,重组,PRE/变换。行为细节尽量贴原版。

➡ 看源码


PARRY(1972):第一个有情绪的聊天机器人

ELIZA 六年后,Kenneth Colby(斯坦福精神科医生)做 PARRY:装偏执型精神分裂病人的聊天机器人。ELIZA 是空镜子,PARRY 内置真情绪模型。两个挨着移植,对比最扎我。

情绪模型

PARRY 四个连续变量,每轮对话变:

变量 基线 每轮衰减 说明
ANGER 0 -1.0 敌意、烦躁
FEAR 0 -0.2 偏执(妄想起来后降得慢)
MISTRUST 0 -0.05 猜疑(降超慢)
HURT 0 -0.5 情绪受伤

值经情绪跳变(ajump、fjump、hjump)被推理规则推高,每轮向基线自然衰减。

信念网络

PARRY bel 文件 200+ 信念:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

每信念有类别(HUM=病人,HUM2=别人,DOC=医生,INT=审问,INN=意图)和强度(0-5)。推理规则(TH2、EMOTE、IF)在信念间传:

  • TH2:信念 A 过阈,强化自己,推高后果
  • EMOTE:信念过阈,触发情绪跳变(怒/怕/伤)
  • IF:条件,A 真则 B 到某强度

妄想层级(flare 系统)

PARRY 最迷的是"flare"链,一级级升级滑向核心妄想:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

每个关键词 pattern 匹配触发预写回复,顺话题跟,PARRY 慢慢滑向迫害妄想中心。flare 一触发变 inactive(deadFlares),PARRY 走下一条,像挖话题的人。我喜欢这机制因为极少代码跑出叙事推进。

PARRY 为什么载史册

1972 年 PARRY 过图灵测试变体:精神科医生拿到 PARRY 和真偏执病人的对话记录,分不出谁是谁。史上第一个,比 LLM 早 40 年。对比当年和现在的手段,我觉得这成绩离谱。

➡ 看源码


RFC 439:ELIZA 遇上 PARRY

1972 年 9 月 18 日,两程序史上第一次对话,字面意思:史上第一次 AI 间对话有记录。ELIZA(BBN 波士顿跑)和 PARRY(斯坦福跑)经 ARPANET 聊,人类操作员两边手敲转述。记录发成 RFC 439("PARRY Encounters the DOCTOR")。挖到这文档我懂它在我领域是奠基时刻。

摘一段当时样:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

这段连贯我初读很惊。ELIZA 干治疗师活:reformuler、问、挖。PARRY 干偏执病人活:抱怨、指责、猜疑。两程序各守角色(ELIZA pattern 加 PARRY 情绪模型)产出碰巧接得上的回复。通读全文越看越觉得这偶然对齐造出"故意对话"的幻觉。

我仓库复现:

bun run meeting

模拟自动跑 25 轮,两机器人互聊,开题随机(马、黑帮、情绪……)。ELIZA 和 PARRY 都有非确定成分(ELIZA 轮转、PARRY 随机),每次跑对话不一样。多跑几遍比轨迹我百玩不厌。

ELIZA 对 PARRY 扎我的是:两程序,一个无内部状态架构,一个全套情绪模型,拼出像有意的对话。1972 年,我觉得结果惊人。


ALICE(1995):大规模 pattern 匹配

ALICE(Artificial Linguistic Internet Computer Entity),Richard Wallace 1995 年造,三夺 Loebner 奖(2000、2001、2004)。ELIZA 几百规则,PARRY 几千,ALICE 99524 条,66 个 AIML 文件。我第一次打开语料,量到"大规模"当年什么分量。

AIML:类别语言

AIML(Artificial Intelligence Markup Language)是问答对 XML:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

ALICE 威力在通配符和 SRAI(Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- 等于 <srai><star/></srai> -->
  </template>
</category>

SRAI 把输入重定向到别的类别,串起归约链:

输入:"WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

ALICE 弹性全靠它: canonical 回复写一遍,变体全重定向过去。深度上限 10:超了 ALICE 放弃防无限循环(类别设计避环,这保险 essential)。我觉得这设计优雅因为极少规则 factorize 几千说法。

ALICE 怎么匹配 pattern

pattern 按特异性排:通配符少的先试。* 和 _ 通配符吃任意词串。引擎每 pattern 编正则,按排好序类别逐个试命中。

// TypeScript 实现,简化但保真
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

ALICE 为什么统治 Loebner

99524 类别,量变质变。ELIZA 靠几条妙规则在特定场景(治疗)装智能。ALICE 覆盖面广到像有通识:科学、政治、幽默、体育、情绪,全有。这方法我现在还用:覆盖广而细补无深推理。我捍卫。

➡ 看源码


Jabberwacky(1997)和 Cleverbot(2008):认识论断裂

前面机器人共享假设:回复要手写。ELIZA S-expression 规则,PARRY 精选 pattern,ALICE AIML 类别。Rollo Carpenter 反着走到底,问题激进:**历史对话直接当回复用行不行?**我觉得这反转勇因为整套手写规则扔了。

想法

Jabberwacky(约 1997 上线,2008 变 Cleverbot)全靠全部历史对话存扁平 transcript,有人说话,在历史找最像的时刻,复用之后说的:

用户:"hello"
  ↓
找:以前有人说过"hello"吗?
  ↓
有,session #3 第 14 行,有人说"hello"机器人回"hi there!"
  ↓
回:"hi there!"

只有人类说过话的大存档,时机对拿出来用。我看这就是涌现定义:对话行为从人类痕迹堆里长出来。

TypeScript 实现

我 TypeScript 移植原样复刻这架构:

flowchart TD
    A["用户输入:<br>'hello'"] --> B["TranscriptStore<br>332 行 seed 加历史"]
    B --> C["withReplies()<br>抽对<br>(行 → 回复)"]
    C --> D["findCandidates()"]
    D --> E["relevance = 输入和历史行相似度"]
    E --> F["contextFit = 近上下文和<br>该行前文相似度"]
    F --> G["recencyBonus = 1 / (1 + 天数/30)"]
    G --> H["分数 = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K 候选排序"]
    I --> J{"pickReply()<br>轮盘赌<br>选择"}
    J -->|"选中"| K["回复 = 胜出对<br>reply.text"]
    J -->|"无"| L["回退:'我还不知<br>这怎么接。'"]
    K --> M["追加进 transcript<br>save() → JSON"]
    L --> M

打分核,我按 Cleverbot 公开描述调的启发式:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance(0.65):用户输入和历史行相似度
  • contextFit(0.25):近对话和该历史行前文相似度
  • recencyBonus(0.10):近记忆权重高一点(机器人人格随时间漂)

pick 概率化(轮盘赌):最好常中,留方差,多跑几遍对话不重样。我喜欢因为测着不无聊。

Cleverbot:两个有文档的创新

Cleverbot 在 Jabberwacky 底子上加两机制:

  1. 多人学习:几百万用户喂同一共享 transcript。历史抽的回复可能和当前对话完全两个 voice,这就是 Cleverbot 突然换人格的原因。

  2. 延迟学习:session 里你说的当场不可匹配。新行标 pending,"consolidation" 过 session 之间才可匹配,这就是同 session 教 Cleverbot 事实转头用不上的原因。

// Cleverbot:新行 consolidation 前不可见
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() 启动时调,session 中不调

我 TypeScript 移植两行为都做了:行带 consolidated 旗,每 REPL session 开头 consolidation 等待行。

➡ 看源码


TypeScript 移植分析:通用架构可能吗

五个机器人同语言做了,有意思的问题冒出来:架构差这么大还能复用代码吗?

我全移植完答案:极少。每机器人主循环根本不同:

机器人 主循环 数据 学习
ELIZA 关键词栈 → 分解 → 重组 .ela S-expression 脚本 无
PARRY 分词 → 精选 pattern / flare / 关键词 / 推理 58 个 PDP-10 文件(词典、信念、规则) 无
ALICE 排序 pattern → 正则 → AIML 模板 → 递归 SRAI 66 个 AIML XML 文件 无
Jabberwacky 相似度 → 上下文 → 时新 → 加权 pick JSON transcript(越用越大) 持续
Cleverbot 同 Jabberwacky 加 pending/consolidated 加 persona transcript 加多 persona seed session 间延迟

共用的就 CLI 界面和 TypeScript infra(biome lint、tsx 跑)。剩下各架构各的。

共通设计选择

1. 数据保真原始。 ELIZA、PARRY、ALICE 用原始文件,Weizenbaum 档案 2021 年找回的 ELIZA 脚本、PDP-10 原始 PARRY 代码(58 文件)、AIML Free ALICE v1.6。原样保留。机器人行为像原版因为数据就是原版。

2. 专有部分 clean-room。 Jabberwacky 和 Cleverbot 情况不同:源码从没公开(Existor/Rollo Carpenter 私有攥着)。我移植是 clean-room 重实现,只按公开行为描述写。没抄一行代码没拿专有数据。

3. 依赖最少。 真前置只有 TypeScript。ALICE 用 dom-js 解析 66 个 AIML 文件 XML(99524 类别,手写 XML 解析浪费时间)。剩下纯 vanilla TypeScript。这么选克隆下来每机器人好读好跑。


从符号机器人到 LLM:概念一跃

上面五个机器人共享底色:符号。它们的"知识"全是显式符号:文本 pattern、规则表、XML 类别、transcript 行。跑起来不用一个数字表征语言。

所以共享同一天花板:只答显式写过或记过的。ELIZA 出治疗框就懵。PARRY 只聊自己偏执主题。ALICE 对话不学习。Jabberwacky 只说别人说过的。测到边界就懂这代为什么到头。

LLM(大语言模型)换范式破顶:不玩符号,把语言转数字,学数字间统计关系。按概率逐 token 生成,知识压权重里,不写预制回复。我觉得这翻转迷人因为把之前机器人逻辑整个倒过来。快速看怎么跑。

1. 分词

第一步文本切 token,比词小比字大的单元:

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

每 token 在词表有数字 ID(新模型一般 32000 到 128000 token)。切开没见过的词能按熟子词拼。

2. Embedding

每 token ID 转向量,浮点数组(中模型一般 4096 维)。向量是嵌入(embedding),在数学空间编码 token 意义,意思近的向量近:

向量("roi")  − 向量("homme") + 向量("femme")  ≈  向量("reine")

这性质训出来的,我和研究者都没显式编程。我看是词在相似上下文出现的直接后果。

3. 注意力

注意力(2017 年"Attention is All You Need"论文)机制让 LLM 成为可能。每 token 算句子里别的 token 谁对懂它重要:

"La banque a refusé mon prêt."
       ↑
token"banque"看:"refusé"、"prêt" → 懂是金融机构

"Je vais me promener sur la banque."
       ↑
token"banque"看:"promener"、"sur" → 懂是河岸

注意力给模型上下文:每 token 按周围的理解,全关系阅读。LLM 超固定 pattern,我看最关键就这层上下文敏感。

4. 下 token 预测

LLM 训练简单到骗人:给文本,藏最后 token,猜。再做几十亿遍。我喜欢因为极简学习规则长出极 rich 能力。

输入:"Je ne comprends"
藏起:"pas"
模型预测:"pas"(概率 0.87)、"rien"(0.05)、"jamais"(0.02)……

目标是每位置真 token 概率最大。这叫 next-token prediction。训练调几十亿参数,在 T 级文本上最小化预测误差。

推理(你聊天时)逐 token 循环生成:

Token 1:"Je"   (输入:"Parle-moi de toi.")
Token 2:"suis"  (输入:"Parle-moi de toi. Je")
Token 3:"un"    (输入:"Parle-moi de toi. Je suis")
Token 4:"chatbot"(输入:"Parle-moi de toi. Je suis un")
...

每 token 按概率采样(温度、top-k、top-p 管"creativity")。完。几十亿参数重复这操作几千遍。机制重复成这样,输出这么多变,我觉得晕。

根本变了什么

维度 符号机器人(ELIZA、PARRY、ALICE) 现代 LLM
表征 显式词和规则 数字向量(embedding)
生成 预写回复里选 逐 token 概率预测
知识 规则文件里存 网络权重里编码
学习 手工(写规则) 自动(语料训)
健壮 出预设 pattern 就死 没见过的输入也泛化
可解释 完美(规则可读) 有限(黑盒)

经典聊天机器人透明但脆。LLM 健壮但不透明。我现在看是不同需求互补工具,按要留多少回复控制权选。

想深挖 LLM 内部,推荐这个好视频:

How LLMs Work - YouTube

Luna Protocol:现代合成

Luna Protocol 文章(链接下面)是我看 60 年对话 AI 最完整的合成:现代 Discord 机器人,本地 LLM 加精细行为系统,拿历史教训建现在实验的桥。

Luna Protocol:我做了个装人类的自主 Discord 机器人

这篇拆 Discord 机器人完整架构:

  • 优先级触发系统(mention > 私聊 > 名字 > 关键词 > follow-up > 随机)
  • 人类行为:可变注意力、typo、犹豫(15%)、忘事(3%)、话题疲劳
  • 睡觉时间:按点睡、慢、忽略
  • TTS 管线:Piper 加 ffmpeg 合成语音 → Discord 语音消息
  • 实时流:LLM 逐 token 经类型化事件总线吐

连历史聊天机器人的线是同一个执念:让人以为对面是人。ELIZA 用文本镜。PARRY 用情绪模型。ALICE 用 9.9 万类别。Luna Protocol 用微调 LLM 加装人类瑕疵的行为系统。这条血统我看要求一样:在屏幕那边造在场幻觉。

Luna Protocol:为什么我在 5 万 Discord 样本上微调 15 亿参数模型

第二篇讲微调和 few-shot 启动。核心发现:小模型(1.5B)少数据(5 万样本训)赢大模型(3B),few-shot 示例喂对就行。我测到这结果真惊了。

直接回声历史机器人:

  • ELIZA 证明几条妙规则装理解
  • ALICE 证明 9.9 万类别装通识
  • Luna Protocol 证明好微调加 5 个 few-shot 示例,小 LLM 装人类

技术变,理不变:数据质量和系统精度比裸尺寸重要。我自己实验坚持这条。


结语:三件事记住

1. 对话 AI 不是 ChatGPT 开始的。 ELIZA 60 岁。PARRY 1972 年过图灵测试。ALICE 三夺 Loebner。Jabberwacky 开 transcript 学习,Cleverbot 大规模工业化。每块拼图少不了。移植全程印证我这条。

2. 数据多不等于聪明。 Jabberwacky transcript 无规则。ALICE 9.9 万类别不学习。Luna Protocol 5 万样本微调赢 3B 模型。俗话"越大越好",聊天机器人史说架构和设计跟尺寸一样重要。我每个技术选择捍卫这条。

3. 问题 60 年没变。 怎么让人以为对面是人?ELIZA 文本镜回。PARRY 装生气。ALICE 摆事实。Luna Protocol 睡觉打 typo 的 LLM。解法变,需求没变。这条连续性让我想继续挖。

仓库开源,克隆跑每个机器人,60 年对话 AI 装进一个 TypeScript 仓库自己看。我希望你玩得跟我一样开心。

ELIZAからLLMへ:TypeScriptで再構築する60年の会話AI

ELIZA、PARRY、ALICE、Jabberwacky、Cleverbot。同じ問題へのまったく違う5つのアーキテクチャをオリジナルデータ付きでTypeScript移植。1966年から現代LLMまで、会話AIがどう話すことを学んだか。チャットボットリポジトリが60年の研究について教えてくれること。

ELIZAからLLMへ:TypeScriptで再構築する60年の会話AI

1966年、Joseph WeizenbaumはIBM 7094上のMAD-SLIPで420行書いて史上初のチャットボットを作った。プログラム名はELIZA。基本パターンと文の置換でロジャーズ派心理療法士を真似た。60年後、会話AIは一般の話題になった。ChatGPTもClaudeもGeminiもあらゆる会話に出てくる。オリジナルコードを読み返して、その手段の少なさに打たれた。

でも両極端の間にはPARRY(パラノイアチャットボット、1972年)、ALICE(99,000カテゴリのAIML王、1995年)、Jabberwacky(ルールなしで学んだ最初、1997年)、Cleverbot(その産業的後継、2008年)がいた。同じ問題へのまったく違う5つの答えだと思う。機械に話させることだ。

この5ボットをオリジナルデータ付きでTypeScript移植した。ELIZAスクリプト、PARRY辞書、ALICEのAIMLファイルだ。各移植は自立してて、すぐ使えて、細部まで文書化してある。この作業の狙いは動かすだけを超える。どう動いてたか、なぜ歴史に残ったか、それぞれのアーキテクチャが昨日と今日のAIについて何を明かすか理解したいんだ。

bun run eliza    # ELIZAと話す(1966年)
bun run parry    # PARRYと話す(1972年)
bun run alice    # ALICEと話す(1995年)
bun run jabber   # Jabberwackyと話す
bun run cleverbot # Cleverbotと話す
bun run meeting  # ELIZA対PARRY自動対戦

各ボットを一緒に解剖して、コードを見て、Luna Protocol記事越しに現代LLMへ橋をかけるよ。


ELIZA(1966年):理解してると信じさせる技

最古のものから始める。私の目には単純さがいちばん印象的だ。ELIZAに現代意味の知能はない。テキストパターンとちょっとした置換だけで動く。

仕組み

DOCTORスクリプト(心理療法士版)はキーワードのテーブルで動く。各々に分解パターンと再組立ルールが付く。典型ルールはこうだ。

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLOがキーワードだ。0は「後ろ全部取り込め」って分解パターン(ワイルドカードみたいなもの)。HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.が再組立ルールだ。以上だ。

「Hello, I'm sad today」って言うとELIZAはこうする。

  1. テキストを大文字化する:HELLO I'M SAD TODAY
  2. 各語をキーワードテーブルと照合する
  3. HELLOを見つける → キーワードスタックに積む
  4. 最優先キーワードを取る
  5. 各分解パターンを順に試す
  6. 合えば次の再組立ルールを選ぶ(ラウンドロビン)
  7. (1)や(2)などを取り込み部分に置換する

でも本当に賢いのはPREルールの部分だ。これ見て。

(MY
    ((0)
        (PRE (1 0) (=YOU))))

ELIZAがMYに合うと、文の残り(0の取り込み)をPREルール経由で変換して、結果をユーザーが新しいキーワードを言ったみたいに再注入する。具体的にはこうだ。

君が言う:「My mother hates me」
  → PRE変換:「YOUR MOTHER HATES YOU」
  → 言ったものとして再注入
  → たぶん「YOU」に合う → 新しい返事

ELIZAが「私」と「君」の違いを分かってる感じがするのはこのためだ。理解を真似る完璧設計の機械変換だと思う。

入力から返事までの完全フローはこうだ。

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalise la ponctuation"]
    B --> C["splitUserInput()<br>découpe en mots"]
    C --> D["Build keyword stack<br>ordonné par priorité"]
    D --> E{"Stack non-vide?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

信用させたもの

Weizenbaumの選択は天才の一手だと思う。ロジャーズ派心理療法だ。この手法は解釈せず患者の言葉を映す。「悲しい」→「悲しいとおっしゃるんですね」。ELIZAができるのはまさにこれで、この療法技術は認知されてるから誰も返事を変に思わない。ボットを試すとこの鏡効果を直接感じる。

TypeScript移植では

私の移植では.elaスクリプト(オリジナルのS式形式)を読み込んで、完全にパースして(60年代文字列形式のHollerithエンコーディング込み)、同じ周期を実行する。大文字化から分割からキーワードスタックから分解から再組立からPREや変換までだ。細部までオリジナル挙動に忠実でありたかった。

➡ ソースコードを見る


PARRY(1972年):感情を持つ初のチャットボット

ELIZAの6年後、Kenneth Colby(スタンフォードの精神科医)はPARRYを作った。パラノイア型統合失調症患者を真似るチャットボットだ。ELIZAが空の鏡として動いたのに対し、PARRYは本物の内部感情モデルを積んでる。続けて移植してていちばん打たれた対比がこれだ。

感情モデル

PARRYは会話ターンごとに動く4つの連続変数を持つ。

変数 ベースライン 減衰/ターン 説明
ANGER 0 −1.0 敵意、いらだち
FEAR 0 −0.2 パラノイア(妄想開始後にゆっくり減る)
MISTRUST 0 −0.05 不信(戻りがとても遅い)
HURT 0 −0.5 感情の痛み

この値は推論ルールが起こす感情ジャンプ(ajump、fjump、hjump)で上がって、ターンごとに自然にベースラインへ減る。

信念ネットワーク

PARRYはbelファイルに200超の信念を持つ。

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

各信念にカテゴリ(HUMは患者、HUM2は他人、DOCは医者、INTは尋問、INNは意図)と強さ(0から5)がある。推論ルール(TH2、EMOTE、IF)が信念間を伝える。

  • TH2:信念Aがしきい値を超えたら強まって結果が上がる
  • EMOTE:信念がしきい値を超えたら感情ジャンプを起こす(怒りや恐怖や痛み)
  • IF:条件付き。Aが真ならBがある水準で真になる

妄想階層(flareシステム)

PARRYでいちばん魅惑的な部分は「flare」システムだ。中央妄想へ段階的に向かうエスカレーション連鎖だ。

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

各キーワードが(パターン照合経由で)書き置き応答を起こして、話題を追うとPARRYは迫害の中央妄想へ少しずつ流れる。flareが1回「発火」したら不活性化して(deadFlares)、PARRYは次に行く。話題を掘る話し相手みたいにね。少ないコードで物語の進行を作るからこの仕組み大好きだ。

なぜPARRYは歴史的か

1972年、PARRYはチューリングテスト変種を通った。精神科医たちがPARRYとの会話と本物のパラノイア患者との会話の書き起こしを受け取って、確実に区別できなかったんだ。LLMより40年早く初の到達だ。当時の手段と今日のを比べると、この偉業は驚異的だと思う。

➡ ソースコードを見る


RFC 439:ELIZAがPARRYに出会うとき

1972年9月18日、史上初めて2つのプログラムが文字通り話した。初の記録されたAI間会話だ。ELIZA(ボストンBBNで動作)とPARRY(スタンフォードで動作)がARPANET越しに会話して、人間オペレーターが一方の出力を他方の入力へ打ち込んで中継した。書き起こしはRFC 439(「PARRY Encounters the DOCTOR」)として公開された。この文書を見つけたとき、うちの分野の創設的瞬間だと分かった。

こんな感じの抜粋だ。

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

このやり取りの一貫性に初読で驚いた。ELIZAは療法士の仕事をする。言い換え、問いかけ、探る。PARRYはパラノイア患者の仕事をする。文句、非難、不信の表明だ。両プログラムはそれぞれの仕組みの組み合わせ(ELIZAパターンとPARRY感情モデル)のおかげで完璧に役に留まって、偶然はまる応答を生む。書き起こし全文を読むと、この偶然の一致が意図的対話の錯覚をどれだけ作るか分かる。

私のリポジトリでこの会話を再現できる。

bun run meeting

私のシミュレーションは両ボット間で25ターン自動で回す。開始話題はランダムだ(馬、組織犯罪、感情など)。ELIZAもPARRYも非決定的要素を積んでる(ELIZAラウンドロビン、PARRYランダム化)から、実行ごとに違うやり取りになる。軌跡比べに何回もコマンド叩き直すの大好きだ。

ELIZA対PARRYで打たれるのは2つのプログラムの出会いだ。片方は内部状態なしアーキテクチャ、もう片方は完全感情モデルで、一緒に意図的っぽい会話を生む。1972年にしてはゾッとする結果だと思う。


ALICE(1995年):大規模パターン照合

ALICE(Artificial Linguistic Internet Computer Entity)はRichard Wallaceが1995年に作って、ローブナー賞を3回取った(2000年、2001年、2004年)。ELIZAが数百ルールでPARRYが数千だったのに対し、ALICEは66 AIMLファイルに99,524ルールだ。このコーパスを初めて開いたとき、当時の「大規模」の意味を測った。

AIML:カテゴリの言語

AIML(Artificial Intelligence Markup Language)は質問応答ペアを定義するXML形式だ。

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

でもALICEの力はワイルドカードとSRAI(Symbolic Reduction)から来る。

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- <srai><star/></srai> と同じ -->
  </template>
</category>

SRAIでALICEは入力を別カテゴリへ向け直せて、短縮連鎖を作る。

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

これがALICEの柔軟性を生む仕組みだ。可能な言い回しごとに応答を書く代わり、正規応答を1つ書いて変化形をそこへ向ける。深さ上限は10だ。超えたら無限ループ避けにALICEは諦める(カテゴリ設計がループを丁寧に避けてて、この安全網は必須のまま)。少ないルールで何千もの言い回しを束ねるからこの設計選択は優雅だと思う。

ALICEのパターン照合

パターンは特異度順に並ぶ。ワイルドカードが少ないものから試す。ワイルドカード*と_は任意語列を取る。エンジンは各パターンを正規表現にコンパイルして、並んだカテゴリを合うまで回す。

// TypeScript実装。簡略だが忠実
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

なぜALICEはローブナーを支配したか

99,524カテゴリは全部変える数だ。ELIZAは少数のよく設計されたルールが特定文脈(療法)向けだったから知能の錯覚を出した。ALICEは話題を広く覆って本物の一般教養がある感じを出す。この手法から今も使う教訓を引き出してる。広く丁寧なカバーが深い推論の欠落を埋めるってことだ。

➡ ソースコードを見る


Jabberwacky(1997年)とCleverbot(2008年):認識論の断絶

前のボット全部が1つの仮説を共有する。応答は書かなきゃいけないってことだ。ELIZAはS式ルール、PARRYは選択パターン、ALICEはAIMLカテゴリを持つ。Rollo Carpenterは真逆の賭けに出た。根本的な問いとともにね。過去の会話に答えさせたらどうなるって。手書きルールを全部捨てるからこの反転は大胆だと思う。

着想

Jabberwacky(1997年頃開始、2008年にCleverbot化)は平らな書き起こしに保存された全会話履歴に完全依存する。誰かが話しかけたら、履歴でいちばん似た瞬間を探して、その後に言われたことを再利用する。

ユーザー:「hello」
  ↓
探す:前に誰か「hello」って言った?
  ↓
うん、セッション#3の14行目で誰かが「hello」って言ってボットは「hi there!」って返した
  ↓
答える:「hi there!」

ここにあるのは人々が言い合ったことの巨大アーカイブだけで、いい機会に再利用される。創発そのものの定義だと思う。人間の痕跡の集積から会話挙動が生まれる。

TypeScript実装

私のTypeScript移植はこのアーキテクチャを正確に再現する。

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>シード332行と履歴"]
    B --> C["withReplies()<br>ペアを抽出<br>(行から返答へ)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>この行の前のcontext)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["上位K候補を整列"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>勝ちペアの"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["書き起こしへ追加<br>save() → JSON"]
    L --> M

スコアリングの心臓部はこうだ。Cleverbotの公開記述に触発された自作ヒューリスティック付きでね。

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance(0.65):ユーザー入力と履歴行の類似度
  • contextFit(0.25):最近の会話と履歴行の前の文脈の類似度
  • recencyBonus(0.10):新しい記憶はちょっと重い(ボットの人格は時間とともに漂う)

選択は確率的だ(ルーレット選択)。最良候補が勝つことが多いけど、ばらつき分が多様性を出す。ボットを何回も試すと会話の予測不能さが減るからこの選択好きだ。

Cleverbot:文書化された2革新

CleverbotはJabberwackyの基本概念に2仕組みを足す。

  1. 多人数の学習:何百万ユーザーが同じ共有書き起こしに貢献する。履歴から引いた応答は進行中会話とまったく違う声のことがある。Cleverbotが突然人格を変える理由がこれだ。

  2. 遅延学習:セッション中にCleverbotに言ったことは同じセッション中は照合に使えない。新規行はpending印付きで、セッション間の「統合」後に照合可能になる。Cleverbotに事実を教えて同じ会話で再利用できない理由がこれだ。

// Cleverbot:統合まで新規行は見えない
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() は起動時に呼ばれ、セッション中じゃない

私のTypeScript移植は両挙動を実装する。行にconsolidatedフラグがあって、各REPLセッションは待機行の統合から始まる。

➡ ソースコードを見る


TypeScript移植の分析:共通アーキテクチャを設計する

この5ボットを同じ言語で作って面白い問いに向き合った。こんなに違うアーキテクチャ間でコードを共通化できるかってことだ。

全部移植した後の私の答え:ほとんどない。各ボットは根本ループが違う。

ボット 主ループ データ 学習
ELIZA キーワードスタックから分解から再組立 S式の.elaスクリプト なし
PARRY トークン化から選択パターンやflareやキーワードや推論へ PDP-10の58ファイル(辞書、信念、ルール) なし
ALICE 並んだパターンから正規表現からAIMLテンプレから再帰SRAI XMLの66 AIMLファイル なし
Jabberwacky 類似度から文脈から新しさから重み付き選択 JSON書き起こし(使うほど育つ) 継続
Cleverbot Jabberwackyと同じとpending/consolidatedとpersonas JSON書き起こしと多人格シード 遅延(セッション間)

共有するのはCLIインターフェースとTypeScript土台(lintにbiome、実行にtsx)だ。残りは各アーキテクチャ固有だ。

共通の設計選択

1. オリジナルデータへの忠実さ。 ELIZA、PARRY、ALICEでは出所ファイルを使う。2021年にWeizenbaumアーカイブで見つかったELIZAスクリプト、PDP-10のPARRYオリジナルコード(58ファイル)、AIML Free ALICE v1.6だ。出所の形のまま保つ。ボットがオリジナル通りに振る舞うのは同じデータを使うからだ。

2. 非公開部分のクリーンルーム。 JabberwackyとCleverbotは別件だ。ソースコードは公開されたことがない(ExistorやRollo Carpenterが非公開に保った)。私の移植はクリーンルーム再実装で、振る舞いの公開記述だけから作った。コード1行も非公開データも写してない。

3. 最小依存。 本当の前提はTypeScriptだけだ。ALICEはAIMLファイルのXMLパース(66ファイル、99,524カテゴリ。自作XMLパースは時間の無駄)にdom-jsを使う。残りは全部素のTypeScriptだ。リポジトリをクローンしたときに各ボットが読みやすく起動しやすいようこの選択にした。


記号チャットボットからLLMへ:概念の跳躍

今紹介した5ボットは根本特徴を共有する。記号的だってことだ。その「知識」は明示記号に完全依存する。テキストパターン、ルールテーブル、XMLカテゴリ、書き起こし行だ。言語の数値表現なしで完全動く。

だから同じガラス天井を共有する。明示的に用意か記録されたことにしか答えない。ELIZAは療法枠を出たら迷子だ。PARRYはパラノイア話題しか話さない。ALICEは会話から何も学ばない。Jabberwackyは言われたことのある台詞でしか答えない。この限界を試すと、この世代が頭打ちした理由がすぐ分かる。

LLM(Large Language Models)は枠組みを根本から変えてこの天井を破る。記号を操る代わり、言語を数に変換して、その数同士の統計関係を学ぶ。各トークンを確率計算でその場生成する。書き置き応答じゃなく重みにエンコードされた知識からだ。前ボット群の理屈を完全に裏返すからこの転換は魅惑的だと思う。どう動くか手短に見よう。

1. トークン化

最初はテキストをトークンに切ることだ。文字より大きく語より小さい単位だ。

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

各トークンは語彙内の数値IDを持つ(最近モデルでだいたい32,000から128,000トークン)。この断片化で見たことない語も既知サブワードに分解して扱える。

2. 埋め込み

各トークンIDはベクトルに変換される。浮動小数点数列だ(中規模モデルでだいたい4096次元)。このベクトルはトークンの意味をエンコードした埋め込み(embedding)で、意味的に近いトークンはベクトルも近い数学空間にある。

vecteur("roi")  − vecteur("homme") + vecteur("femme")  ≈  vecteur("reine")

この性質は訓練から創発する。私や研究者の明示プログラムなしだ。似た文脈に語が現れることの直接の帰結だと思う。

3. アテンション

アテンション機構(2017年論文「Attention is All You Need」で導入)がLLMを可能にした。各トークンについて、文中の他のどのトークンがこれを理解するのに大事か計算する。

"La banque a refusé mon prêt."
       ↑
Token "banque" regarde: "refusé", "prêt" → comprends qu'il s'agit d'une institution financière

"Je vais me promener sur la banque."
       ↑
Token "banque" regarde: "promener", "sur" → comprends qu'il s'agit d'une rive

アテンションでモデルは文脈を掴む。各トークンは周りとの関係で理解される。全関係読みだ。固定パターンをLLMが超える理由の理解にいちばん助かったのはこの文脈感度だ。

4. 次トークン予測

LLM訓練は騙されるほど単純だ。テキストを見せて、最後トークンを隠して、予測させる。何十億回も繰り返す。とても単純な学習規則がとても豊かな能力を生むからこの着想好きだ。

Input:  "Je ne comprends"
Caché:  "pas"
Prédiction du modèle: "pas" (probabilité 0.87), "rien" (0.05), "jamais" (0.02)...

目標は各位置で本物トークンの確率を最大化することだ。これを次トークン予測って呼ぶ。訓練中、モデルは何テラものテキストの予測誤差を最小化するよう何十億パラメータを調整する。

推論時(話しかけるとき)、モデルはループで1つずつトークン生成する。

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

各トークンは確率に従って抽選される(温度、top-k、top-pが「創造性」度合いを管理する)。以上だ。何十億パラメータがこの演算を何千回も繰り返す。こんなに反復的な仕組みがこんなに多様な応答を生むことに目眩がする。

根本的に変わること

側面 記号ボット(ELIZA、PARRY、ALICE) 現代LLM
表現 明示の語とルール 数値ベクトル(埋め込み)
生成 書き置き応答からの選択 トークンごとの確率予測
知識 ルールファイルに保存 ネット重みにエンコード
学習 手動(ルール執筆) 自動(コーパス訓練)
頑丈さ 想定パターン外はゼロ 見たことない入力に一般化
解釈性 完全(ルール読める) 限定的(ブラックボックス)

古典チャットボットは透明だが脆い。LLMは頑丈だが不透明だ。今日では違う需要向けの補完道具だと思う。応答への管理水準でどっちか選ぶ。

LLM内部を深掘りしたいなら、この動画を優れた資料として勧める。

How LLMs Work、YouTube

Luna Protocol:現代の統合

Luna Protocol記事(リンクは下)は、今見せたもの全部のいちばん完成した統合だと私の目に映る。ローカルLLMと洗練行動システムを組み合わせた現代Discordボットで、60年の会話AIの教訓の上に全部作ってある。歴史移植と今の実験をつなげてくれたプロジェクトだ。

Luna Protocol:人間を真似る自律Discordボットを作った

この記事はLLMベースDiscordボットの完全アーキテクチャを詳述する。

  • 優先度付き起動システム(メンションがDMより上で、名前より上で、キーワードより上で、フォローアップより上で、ランダムより上)
  • 人間挙動:変わる集中力、タイプミス、ためらい(15パーセント)、忘却(3パーセント)、話題疲れ
  • 睡眠時間:ボットは寝て、時間で遅くなったり無視したりする
  • TTSパイプライン:Piperとffmpeg経由の音声合成からDiscordボイスメッセージへ
  • リアルタイム配信:LLMが型付きイベントバス上にトークンを1つずつ出す

この記事を歴史チャットボットにつなげるのは同じ探求だ。人と話してると思わせることだ。ELIZAはテキストの鏡を使った。PARRYは感情モデルを使った。ALICEは99kカテゴリを使った。Luna Protocolは微調整LLMと人間不完全さを真似る行動システムを使う。この系譜に画面向こうの存在の錯覚を作る同じ要求を見出す。

Luna Protocol:なぜ1.5Bモデルを微調整したか

2つ目の記事は微調整とfew-shot primingを探る。中心的発見はこうだ。小さいモデル(1.5B)を少ないデータ(50kサンプル)で訓練した方が、few-shot例で正しく起動すれば大きいモデル(3B)に勝つってことだ。測ったとき本当に驚いた。

歴史チャットボットに直接響く教訓だ。

  • ELIZAはよく設計された少数ルールで理解を真似られることを示した
  • ALICEは99kカテゴリで一般教養を真似られることを示した
  • Luna Protocolは良い微調整と5つのfew-shot例で小LLMが人間を真似られることを示す

技術は変わる。原則は同じままだ。データ品質とシステム精度が素朴サイズより大事だってことだ。自分の実験で守る信念だ。


結論:覚えるべき3つ

1. 会話AIはChatGPTから始まってない。 ELIZAは60歳だ。PARRYは1972年にチューリングテストを通った。ALICEはローブナーを3回取った。Jabberwackyは書き起こし学習の基礎を置いて、Cleverbotは大規模産業化した。各手法がパズルの1片を持ってきた。移植が確かめてくれたことだ。

2. データが多いイコール賢いじゃない。 Jabberwackyの書き起こしにルールはない。ALICEの99kカテゴリは学ばない。Luna Protocolの50kサンプル微調整は3Bモデルに勝つ。常識は「大きいほど良い」って言う。チャットボットの歴史はアーキテクチャと設計がサイズと同じくらい大事って示す。各技術選択で守る考えだ。

3. 問題は60年同じだ。 どうやって人間に別人と話してると思わせるか。ELIZAはテキストの鏡で答えた。PARRYは真似た怒りでね。ALICEは事実でね。Luna Protocolは寝てタイプミスするLLMでね。解は変わる。需要は残る。この連続が探索を続けたい気にさせる。

リポジトリはオープンソースだ。クローンして各ボットを起動して、60年の会話AIが1つのTypeScriptリポジトリに収まるのを自分で見られる。話させて私と同じくらい楽しんでほしい。

リソース リンク
GitHubリポジトリ fox3000foxy/chatbots
Luna Protocol、ボットアーキテクチャ 記事を読む
Luna Protocol、few-shot微調整 記事を読む
ELIZAオリジナルスクリプト anthay/ELIZA
PARRYオリジナルソース lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439オリジナル PARRY Encounters the DOCTOR
LLMの仕組みの優れた解説 https://www.youtube.com/watch?v=YmLp8qe87A0

ELIZA에서 LLM까지: TypeScript로 다시 만든 60년의 대화형 AI

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot. 같은 문제에 대한 다섯 가지 근본적으로 다른 아키텍처를 원본 데이터와 함께 TypeScript로 옮겼어요. 1966년부터 현대 LLM까지, 대화형 AI가 말을 배우게 된 방법과 챗봇 저장소가 60년의 연구에 대해 가르쳐 주는 것.

ELIZA에서 LLM까지: TypeScript로 다시 만든 60년의 대화형 AI

1966년 Joseph Weizenbaum은 IBM 7094 위에서 420줄의 MAD-SLIP을 써서 역사상 첫 챗봇을 만들었어요. 프로그램 이름은 ELIZA였고, 기본 패턴과 문장 순열로 로저스식 심리치료사를 흉내 냈어요. 60년이 지난 지금 대화형 AI는 대중적 주제가 됐어요. ChatGPT, Claude, Gemini가 모든 대화에 나와요. 원본 코드를 다시 읽으면서 저는 그 수단의 경제성에 놀랐어요.

하지만 그 두 극단 사이에는 PARRY(편집증 챗봇, 1972년)와 ALICE(99,000개 카테고리의 AIML 왕, 1995년), Jabberwacky(규칙 없이 배우기 시작한 첫 번째, 1997년), Cleverbot(그 산업적 후계자, 2008년)이 있었어요. 저는 이들을 같은 문제에 대한 다섯 가지 근본적으로 다른 답으로 봐요. 기계에게 말을 시키기.

이 다섯 봇을 원본 데이터와 함께 TypeScript로 옮겼어요. ELIZA 스크립트, PARRY 사전, ALICE의 AIML 파일이요. 각 포트는 독립적이고 바로 쓸 수 있으며 세세하게 문서화되어 있어요. 이 작업의 목표는 단순 실행을 넘어요. 어떻게 동작했는지, 왜 역사에 남았는지, 각 아키텍처가 어제의 AI에 대해, 그리고 오늘의 AI에 대해 무엇을 보여주는지 이해하고 싶었어요.

bun run eliza    # ELIZA와 대화하기 (1966)
bun run parry    # PARRY와 대화하기 (1972)
bun run alice    # ALICE와 대화하기 (1995)
bun run jabber   # Jabberwacky와 대화하기
bun run cleverbot # Cleverbot과 대화하기
bun run meeting  # ELIZA 대 PARRY 자동 대결

각 봇을 하나씩 뜯어보고 코드를 살펴본 뒤, Luna Protocol 글들을 통해 현대 LLM과 다리를 놓을게요.


ELIZA (1966년): 이해하는 척하는 기술

가장 오래된 것부터 시작하는데, 제 눈에는 단순함으로 가장 인상적이에요. ELIZA는 현대적 의미의 지능이 전혀 없어요. 텍스트 패턴과 약간의 순열만으로 동작해요.

원리

DOCTOR 스크립트(심리치료사 버전)는 키워드 표로 동작하고, 각 키워드에 분해 패턴과 재조립 규칙이 연결되어 있어요. 전형적인 규칙은 이렇습니다.

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO가 키워드예요. 0은 "뒤에 오는 모든 것을 잡아라"(와일드카드처럼)라는 분해 패턴이에요. HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.이 재조립 규칙이에요. 그게 전부예요.

"Hello, I'm sad today"라고 말하면 ELIZA는요.

  1. 텍스트를 대문자로 바꿔요. HELLO I'M SAD TODAY
  2. 각 단어를 키워드 표와 대조해 스캔해요
  3. HELLO를 찾아요. 키워드 스택에 쌓아요
  4. 우선순위가 가장 높은 키워드를 가져와요
  5. 각 분해 패턴을 순서대로 시도해요
  6. 맞으면 다음 재조립 규칙을 골라요 (라운드 로빈)
  7. 잡힌 부분들로 (1), (2) 등을 바꿔요

하지만 진짜 영리한 부분은 PRE 규칙이에요. 이것 좀 보세요.

(MY
    ((0)
        (PRE (1 0) (=YOU))))

ELIZA가 MY와 맞추면 나머지 문장(0이 잡은)을 PRE 규칙으로 변환하고, 그 결과를 사용자가 새 키워드를 말한 것처럼 다시 주입해요. 구체적으로요.

여러분이 말해요: "My mother hates me"
  → PRE가 변환해요: "YOUR MOTHER HATES YOU"
  → 방금 말한 것처럼 다시 주입돼요
  → 아마 "YOU"와 맞아요 → 새 답장

ELIZA가 "나"와 "너"의 차이를 이해하는 인상을 주는 이유가 바로 이거예요. 제 눈에는 이해를 흉내 내는 완벽하게 설계된 기계적 변환이에요.

입력부터 답장까지 전체 흐름은 이렇습니다.

flowchart TD
    A["사용자 입력:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>구두점 정규화"]
    B --> C["splitUserInput()<br>단어로 나누기"]
    C --> D["키워드 스택 만들기<br>우선순위순"]
    D --> E{"스택이 비어 있지 않나요?"}
    E -->|"예"| F["우선순위 최고 키워드 꺼내기"]
    E -->|"아니요"| G{"기억 불러오기?"}
    G -->|"예"| H["지난 사용자 발언 불러오기"]
    G -->|"아니요"| I["폴백: zNONE 규칙"]
    I --> J["답장 반환"]
    H --> J
    F --> K["분해 패턴 맞추기"]
    K --> L{"맞았나요?"}
    L -->|"아니요"| M{"연결된 키워드?"}
    M -->|"예"| N["연결된 키워드를 스택에 쌓기"]
    N --> E
    M -->|"아니요"| O["NOMATCH 반환"]
    O --> J
    L -->|"예"| P["다음 재조립 고르기 (라운드 로빈)"]
    P --> Q{"재조립 종류?"}
    Q -->|"PRE"| R["단어 변환 (I→YOU)<br>연결 키워드 쌓기"]
    R --> N
    Q -->|"NEWKEY"| S["다음 키워드로 건너뛰기"]
    S --> E
    Q -->|"표준"| T["(1), (2), (0)을<br>최종 답장으로 펼치기"]
    T --> J

믿을 만하게 만든 것

Weizenbaum의 선택을 저는 신의 한 수로 봐요. 로저스식 심리치료요. 이 접근은 해석하지 않고 환자의 말을 비춰줘요. "슬퍼요"에서 "슬프다고 말씀하시는군요"로요. ELIZA가 정확히 할 수 있는 것이 그거고, 이 치료 기법이 공인된 것이니 아무도 그 답을 이상하게 여기지 않아요. 봇을 테스트하면 거울 효과가 바로 느껴져요.

TypeScript 포트에서

제 포트에서는 .ela 스크립트(원본 S-표현식 형식)를 불러와서 온전히 파싱하고(Hollerith 인코딩, 60년대의 문자열 형식까지 포함), 같은 사이클을 실행해요. 대문자화에서 나누기, 키워드 스택, 분해, 재조립, PRE와 변환까지요. 세세한 부분까지 원본 동작에 충실하고 싶었어요.

➡ 소스 코드 보기


PARRY (1972년): 감정이 있는 첫 챗봇

ELIZA 6년 뒤 Kenneth Colby(Stanford 정신과 의사)가 PARRY를 만들었어요. 편집성 조현병 환자를 흉내 내는 챗봇이에요. ELIZA가 빈 거울처럼 동작했다면, PARRY는 진짜 내부 감정 모델을 품고 있어요. 둘을 연달아 옮기면서 가장 마음에 남았던 대비가 바로 이거예요.

감정 모델

PARRY는 대화 턴마다 변하는 네 개의 연속 변수를 가지고 있어요.

변수 기준선 턴당 감쇠 설명
ANGER 0 −1.0 적대감, 짜증
FEAR 0 −0.2 편집증 (망상 시작 뒤 천천히 내려가요)
MISTRUST 0 −0.05 불신 (아주 천천히 내려가요)
HURT 0 −0.5 감정적 아픔

이 값들은 추론 규칙이 일으키는 감정 점프(ajump, fjump, hjump)로 오르고, 매 턴마다 자연스럽게 기준선 쪽으로 내려가요.

믿음 네트워크

PARRY는 bel 파일에 200개가 넘는 믿음을 저장하고 있어요.

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

각 믿음에는 범주(HUM = 환자, HUM2 = 타인, DOC = 의사, INT = 심문, INN = 의도)와 강도(0-5)가 있어요. 추론 규칙(TH2, EMOTE, IF)이 믿음 사이를 전파해요.

  • TH2: 믿음 A가 임계값을 넘으면 스스로 강화되고 결과가 커져요
  • EMOTE: 믿음이 임계값을 넘으면 감정 점프(분노와 공포, 아픔)를 일으켜요
  • IF: 조건문. A가 참이면 B가 일정 수준에서 참이 돼요

망상 계층 (플레어 시스템)

PARRY에서 가장 매혹적인 부분은 "플레어" 시스템이에요. 중심 망상으로 서서히 이어지는 상승 사슬이에요.

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

각 키워드가 미리 쓴 답장(패턴 매칭 경유)을 일으키고, 주제를 따라가면 PARRY는 박해 중심 망상 쪽으로 서서히 흘러가요. 플레어 하나가 "발동"되면 비활성화(deadFlares)되고 PARRY는 다음으로 넘어가요. 주제를 파고드는 대화자처럼요. 코드 몇 줄로 서사적 진행을 만드는 이 구조가 저는 정말 마음에 들어요.

PARRY가 역사적인 이유

1972년 PARRY는 튜링 테스트의 변형을 통과했어요. 정신과 의사들이 PARRY와의 대화와 진짜 편집증 환자와의 대화 기록을 받고, 믿을 만하게 구분하지 못했어요. 해낸 첫 프로그램이고, LLM보다 40년 앞서요. 당시 수단과 오늘날 수단을 비교하면 저는 이 위업이 놀랍다고 생각해요.

➡ 소스 코드 보기


RFC 439: ELIZA가 PARRY를 만났을 때

1972년 9월 18일, 두 프로그램이 역사상 처음으로 말 그대로 서로 말을 걸었어요. 기록된 최초의 AI 사이 대화예요. ELIZA(Boston의 BBN에서 돌았어요)와 PARRY(Stanford에서 돌았어요)가 ARPANET으로 대화했고, 사람 운영자들이 한쪽 출력을 다른 쪽 입력으로 쳐서 중계했어요. 기록은 RFC 439("PARRY Encounters the DOCTOR")로 나왔어요. 이 문서를 발견했을 때 저는 우리 분야의 창립 순간을 표시한다고 이해했어요.

어떤 모습이었는지 발췌예요.

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

이 교환의 일관성이 첫 독서 때 저를 놀라게 했어요. ELIZA는 치료사 일을 해요. 바꾸어 말하고, 질문하고, 탐험해요. PARRY는 편집증 환자 일을 해요. 불평하고, 비난하고, 불신을 드러내요. 두 프로그램은 각자의 구조(ELIZA 패턴 + PARRY 감정 모델) 조합이 우연히 맞물리는 답을 만들 덕분에 역할에 완벽히 머물러요. 기록 전체를 읽으면 우연한 정렬이 의도적 대화의 착각을 만드는 모습을 보여요.

제 저장소로 이 대화를 재현할 수 있어요.

bun run meeting

제 시뮬레이션은 두 봇 사이 25턴을 자동으로 돌려요. 시작 주제는 무작위(말, 조직 범죄, 감정...)이에요. ELIZA와 PARRY 둘 다 비결정적 요소(ELIZA 라운드 로빈, PARRY 무작위화)를 품고 있어서 실행마다 다른 교환이 나와요. 궤적을 비교하려고 명령을 여러 번 다시 실행하는 것이 저는 정말 좋아요.

ELIZA 대 PARRY에서 저를 때리는 것은 두 프로그램의 만남이에요. 하나는 내부 상태 없는 아키텍처, 다른 하나는 완전한 감정 모델인데 함께 작심한 것처럼 보이는 대화를 만들어내요. 1972년 기준으로 저는 결과가 기절초풍할 정도라고 생각해요.


ALICE (1995년): 대규모 패턴 매칭

ALICE(Artificial Linguistic Internet Computer Entity)는 Richard Wallace가 1995년에 만들었고, Loebner Prize를 세 번(2000년, 2001년, 2004년) 탔어요. ELIZA가 수백 개 규칙, PARRY가 수천 개였다면 ALICE는 66개 AIML 파일에 99,524개예요. 이 말뭉치를 처음 열었을 때 저는 당시의 "대규모"가 무슨 뜻이었는지 실감했어요.

AIML: 카테고리의 언어

AIML(Artificial Intelligence Markup Language)은 질문-답변 쌍을 정하는 XML 형식이에요.

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

하지만 ALICE의 힘은 와일드카드와 SRAI(Symbolic Reduction)에서 나와요.

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- <srai><star/></srai>와 같아요 -->
  </template>
</category>

SRAI로 ALICE는 입력을 다른 카테고리로 돌려서 축소 사슬을 만들어요.

입력: "WHAT'S UP?"
  → "WHAT IS UP" 패턴 → srai "HELLO"
    → "HELLO" 패턴 → "Hi there!" 템플릿

ALICE에게 유연함을 주는 구조가 바로 이거예요. 가능한 모든 표현마다 답장을 쓰는 대신 정석 답장 하나를 쓰고 변형을 그쪽으로 돌려요. 깊이 한도는 10이에요. 넘으면 무한 루프를 피하려고 ALICE가 포기해요 (카테고리 설계가 루프를 조심스레 피해요. 이 안전망은 여전히 필수예요). 아주 적은 규칙으로 수천 개 표현을 인수분해하는 이 설계 선택이 저는 우아하다고 생각해요.

ALICE가 패턴을 맞추는 방법

패턴은 구체성순으로 정렬돼요. 와일드카드가 가장 적은 것부터 먼저 시도해요. 와일드카드 *와 _는 어떤 단어 연속이든 잡아요. 엔진은 각 패턴을 정규식으로 컴파일한 뒤, 정렬된 카테고리를 맞는 것이 나올 때까지 돌아요.

// TypeScript 구현 -- 단순화했지만 충실해요
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

ALICE가 Loebner를 지배한 이유

99,524개 카테고리는 모든 것을 바꾸는 숫자예요. ELIZA는 몇 개 규칙이 특정 맥락(치료)에 잘 설계돼서 지능의 착각을 줬어요. ALICE는 워낙 많은 주제를 커버해서 진짜 교양이 있는 인상을 줘요. 과학, 정치, 유머, 스포츠, 감정, 전부 있어요. 이 접근에서 제가 간직하는 교훈은 지금도 적용해요. 넓고 잘 손질된 커버리지가 깊은 추론의 부재를 메워요.

➡ 소스 코드 보기


Jabberwacky (1997년)와 Cleverbot (2008년): 인식론적 단절

앞선 모든 봇은 하나의 가정을 공유해요. 답장을 써야 해요. ELIZA는 S-표현식 규칙, PARRY는 선택 패턴, ALICE는 AIML 카테고리. Rollo Carpenter는 정반대로 가면서 근본적인 질문을 던졌어요. 지난 대화들이 답장을 제공하게 하면 어떨까요? 규칙 수기 작성을 통째로 버리는 이 뒤집기가 저는 대담하다고 생각해요.

아이디어

Jabberwacky(1997년쯤 출시, 2008년 Cleverbot이 됨)는 평평한 기록에 저장된 전체 대화 히스토리에 온전히 기대고, 누군가 말을 걸면 히스토리에서 가장 비슷한 순간을 찾아서 그 뒤에 나왔던 말을 재사용해요.

사용자: "hello"
  ↓
찾기: 누군가 전에 "hello"라고 말한 적 있나요?
  ↓
네, 세션 3번 14번째 줄에서 누군가 "hello"라고 말했고 봇이 "hi there!"라고 답했어요
  ↓
답장: "hi there!"

여기에는 사람들이 서로 했던 말들의 거대한 기록만 있고, 때맞춰 재사용돼요. 저는 여기서 창발의 정의 그 자체를 봐요. 인간 흔적들의 축적에서 대화 행동이 태어나요.

TypeScript 구현

제 TypeScript 포트는 이 아키텍처를 정확히 재현해요.

flowchart TD
    A["사용자 입력:<br>'hello'"] --> B["TranscriptStore<br>시드 332줄 + 히스토리"]
    B --> C["withReplies()<br>쌍 추출<br>(줄 → 답장)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>이 줄 앞 문맥)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["정렬된 상위 K 후보"]
    I --> J{"pickReply()<br>룰렛 휠<br>선택"}
    J -->|"선택"| K["답장 = 승리 쌍의 reply.text"]
    J -->|"없음"| L["폴백: 'I have no idea<br>what to say to that yet.'"]
    K --> M["기록에 추가<br>save() → JSON"]
    L --> M

점수 매김의 핵심은 이렇고, Cleverbot의 공개 설명에서 영감을 받은 제 휴리스틱이에요.

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): 사용자 입력과 역사적 줄 사이 유사도
  • contextFit (0.25): 최근 대화와 역사적 줄 앞 상황 사이 유사도
  • recencyBonus (0.10): 최근 기억이 조금 더 중요해요 (봇 성격이 시간과 함께 흘러가요)

선택은 확률적(룰렛 휠 선택)이에요. 최고 후보가 더 자주 이기지만, 다양성을 주는 변동 몫이 있어요. 봇을 여러 번 테스트할 때 대화를 덜 예측 가능하게 만드는 선택이라 저는 좋아해요.

Cleverbot: 문서화된 두 가지 혁신

Cleverbot은 Jabberwacky 기본 개념에 두 가지 구조를 더해요.

  1. 다인 학습: 수백만 사용자가 같은 공유 기록에 기여해요. 히스토리에서 뽑은 답장이 진행 중인 대화와 완전히 다른 목소리에서 올 수 있어요. 그래서 Cleverbot이 갑자기 성격을 바꾸는 거예요.

  2. 지연 학습: 세션 중에 Cleverbot에게 말하는 것은 그 세션 동안 매칭에 쓰이지 않아요. 새 줄은 pending으로 표시되고, 세션 사이 "통합" 뒤에야 매칭 가능해져요. 그래서 Cleverbot에게 사실을 가르치고 같은 대화에서 재사용할 수 없는 거예요.

// Cleverbot: 통합될 때까지 최근 줄은 안 보여요
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate()는 세션 중이 아니라 시작할 때 불려요

제 TypeScript 포트는 두 동작을 구현해요. 줄에는 consolidated 플래그가 있고, 각 REPL 세션은 대기 중인 줄들의 통합으로 시작해요.

➡ 소스 코드 보기


TypeScript 포트 분석: 공통 아키텍처 설계하기

이 다섯 봇을 같은 언어로 만들면서 흥미로운 질문과 마주했어요. 이렇게 다른 아키텍처 사이에 코드를 인수분해할 수 있을까요?

전부 옮기고 난 뒤 제 답은요. 거의 없어요. 각 봇의 기본 루프가 달라요.

봇 주 루프 데이터 학습
ELIZA 키워드 스택 → 분해 → 재조립 S-표현식 .ela 스크립트 없음
PARRY 토큰화 → 선택 패턴과 플레어, 키워드, 추론 PDP-10 파일 58개 (사전, 믿음, 규칙) 없음
ALICE 정렬된 패턴 → 정규식 → AIML 템플릿 → 재귀 SRAI AIML XML 파일 66개 없음
Jabberwacky 유사도 → 문맥 → 최신성 → 가중 선택 기록 JSON (쓰면서 커져요) 계속
Cleverbot Jabberwacky와 같음 + pending과 consolidated + 페르소나 기록 JSON + 다중 페르소나 시드 지연 (세션 사이)

공유하는 것은 CLI 인터페이스와 TypeScript 인프라(biome 린트, tsx 실행)예요. 나머지는 각 아키텍처 고유예요.

공통 설계 선택

1. 원본 데이터 충실도. ELIZA, PARRY, ALICE에는 원본 파일을 써요. 2021년 Weizenbaum 기록에서 찾은 ELIZA 스크립트, PDP-10의 PARRY 원본 코드(58개 파일), AIML Free ALICE v1.6. 원래 형태로 보존해요. 봇들이 같은 데이터를 쓰니 원본처럼 행동해요.

2. 독점 부분은 클린룸. Jabberwacky와 Cleverbot은 경우가 달라요. 소스 코드가 공개된 적이 없어요 (Existor와 Rollo Carpenter가 독점으로 지켰어요). 그래서 제 포트는 행동에 대한 공개 설명만으로 만든 클린룸 재구현이에요. 코드 한 줄도 독점 데이터도 베끼지 않았어요.

3. 최소 의존성. 진짜 전제 조건은 TypeScript뿐이에요. ALICE는 AIML 파일(66개 파일, 99,524개 카테고리)의 XML을 파싱하려고 dom-js를 써요 (직접 만든 XML 파싱은 시간 낭비예요). 나머지는 순수 TypeScript예요. 저장소를 클론하면 각 봇을 읽고 실행하기 쉽게 하려고 이 선택을 했어요.


상징 챗봇에서 LLM으로: 개념적 도약

지금 소개한 다섯 봇은 기본 특성을 공유해요. 상징적이라는 거예요. 그 "지식"은 명시적 상징에만 기대요. 텍스트 패턴, 규칙 표, XML 카테고리, 기록 줄. 언어의 숫자 표현 없이 온전히 동작해요.

그래서 같은 유리 천장을 공유해요. 명시적으로 준비되거나 기록된 것에만 답해요. ELIZA는 치료 틀을 벗어나면 길을 잃어요. PARRY는 편집증 주제만 말해요. ALICE는 대화에서 아무것도 배우지 않아요. Jabberwacky는 이미 나온 말로만 답해요. 이 한계를 테스트하면 그 세대가 왜 정체됐는지 바로 이해돼요.

LLM(Large Language Model)은 패러다임을 근본적으로 바꿔서 이 천장을 뚫어요. 상징을 다루는 대신 언어를 숫자로 바꾸고, 숫자 사이의 통계적 관계를 배워요. 미리 쓴 답장이 아니라 가중치에 인코딩된 지식에서 확률을 계산해서 토큰마다 그때그때 만들어내요. 앞선 봇들의 논리를 완전히 뒤집는 이 전환이 저는 매혹적이라고 생각해요. 어떻게 동작하는지 간단히 봐요.

1. 토큰화

첫 단계는 텍스트를 토큰으로 나누는 거예요. 단어보다 작지만 문자보다 큰 단위요.

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

각 토큰은 어휘 목록(최근 모델은 보통 32,000개에서 128,000개 토큰)에 숫자 ID가 있어요. 이 조각내기 덕분에 모델이 본 적 없는 단어를 알려진 하위 단어로 쪼개서 다뤄요.

2. 임베딩

각 토큰 ID는 벡터로 바뀌어요. 부동소수점 숫자 배열(중간 크기 모델은 보통 4096차원)이요. 이 벡터는 토큰 의미를 수학 공간에 인코딩한 매장(임베딩)이고, 의미가 가까운 토큰은 벡터가 가까워요.

벡터("roi")  − 벡터("homme") + 벡터("femme")  ≈  벡터("reine")

이 속성은 저나 연구자들의 명시적 프로그래밍 없이 훈련에서 나와요. 저는 비슷한 문맥에 단어가 나타나는 방식의 직접 결과로 봐요.

3. 어텐션

어텐션 구조(2017년 논문 "Attention is All You Need"로 나왔어요)가 LLM을 가능하게 했어요. 각 토큰마다 어텐션은 문장에서 이것을 이해하는 데 중요한 다른 토큰들을 계산해요.

"La banque a refusé mon prêt."
       ↑
"banque" 토큰이 봐요: "refusé", "prêt" → 금융 기관임을 이해해요

"Je vais me promener sur la banque."
       ↑
"banque" 토큰이 봐요: "promener", "sur" → 강둑임을 이해해요

어텐션으로 모델이 문맥을 잡아요. 각 토큰은 자신을 둘러싼 것들에 따라 이해돼요. 온전히 관계적인 읽기예요. 고정 패턴을 LLM이 넘어서는 이유를 이해하는 데 이 문맥 민감성이 저를 가장 많이 도와줬어요.

4. 다음 토큰 예측

LLM 훈련은 속임수처럼 단순해요. 텍스트를 보여주고 마지막 토큰을 가린 뒤 예측하게 해요. 그리고 그 작업을 수십억 번 반복해요. 아주 단순한 학습 규칙이 아주 풍부한 능력을 만든다는 이 생각이 저는 좋아요.

입력:  "Je ne comprends"
가림:  "pas"
모델 예측: "pas" (확률 0.87), "rien" (0.05), "jamais" (0.02)...

목표는 각 위치에서 진짜 토큰의 확률을 키우는 거예요. 다음 토큰 예측이라고 불러요. 훈련 중에 모델은 수 테라바이트 텍스트에 대한 예측 오차를 줄이려고 수십억 개 매개변수를 조정해요.

추론할 때(여러분이 말을 걸 때) 모델은 루프로 한 번에 토큰 하나씩 만들어내요.

토큰 1: "Je"    (입력: "Parle-moi de toi.")
토큰 2: "suis"  (입력: "Parle-moi de toi. Je")
토큰 3: "un"    (입력: "Parle-moi de toi. Je suis")
토큰 4: "chatbot" (입력: "Parle-moi de toi. Je suis un")
...

각 토큰은 확률에 따라 샘플링돼요 (온도, top-k, top-p가 "창의성" 정도를 조종해요). 그게 전부예요. 이 작업을 수천 번 반복하는 수십억 개 매개변수. 이렇게 반복적인 구조가 이렇게 다양한 답장을 만든다는 것이 저는 아찔하다고 생각해요.

근본적으로 바뀌는 것

측면 상징 봇 (ELIZA, PARRY, ALICE) 현대 LLM
표현 명시적 단어와 규칙 숫자 벡터 (임베딩)
만들기 미리 쓴 답장에서 고르기 토큰마다 확률적 예측
지식 규칙 파일에 저장 네트워크 가중치에 인코딩
학습 수동 (규칙 쓰기) 자동 (말뭉치 훈련)
강건함 준비된 패턴 밖에서는 없음 본 적 없는 입력에 일반화
해석 가능성 완벽 (규칙을 읽을 수 있어요) 제한적 (블랙박스)

고전 챗봇은 투명하지만 깨지기 쉬워요. LLM은 강건하지만 불투명해요. 저는 오늘날 이들을 서로 다른 필요를 위한 보완 도구로 보고, 답장에 대해 어느 정도 통제를 유지하고 싶은지에 따라 하나를 골라요.

LLM 내부를 더 파고들고 싶으면 훌륭한 자료로 이 영상을 추천해요.

How LLMs Work - YouTube

Luna Protocol: 현대적 종합

아래 링크의 Luna Protocol 글들은 제가 방금 보여드린 모든 것의 가장 완성된 종합이라고 생각해요. 미 local LLM과 정교한 행동 시스템을 합친 현대 Discord 봇이고, 60년 대화형 AI의 교훈 위에 지은 거예요. 역사적 포트를 현재 실험과 잇게 해준 프로젝트예요.

Luna Protocol: 사람을 흉내 내는 자율 Discord 봇을 만들었어요

이 글은 LLM 기반 Discord 봇의 완전한 아키텍처를 자세히 다뤄요.

  • 우선순위 트리거 시스템 (멘션 > DM > 이름 > 키워드 > 후속 > 무작위)
  • 인간 행동: 오락가락 집중, 오타, 망설임 (15퍼센트), 깜빡함 (3퍼센트), 주제 피로
  • 수면 시간: 봇이 자고, 느려지고, 시간에 따라 무시해요
  • TTS 파이프라인: Piper와 ffmpeg로 음성 합성 → Discord 음성 메시지
  • 실시간 스트리밍: LLM이 타입화된 이벤트 버스에 토큰을 하나씩 내보내요

이 글을 역사적 챗봇과 잇는 것은 같은 탐구예요. 사람과 말한다고 믿게 만들기. ELIZA는 텍스트 거울을 썼어요. PARRY는 감정 모델을 썼어요. ALICE는 99k 카테고리를 썼어요. Luna Protocol은 미세 조정된 LLM과 인간의 불완벽을 흉내 내는 행동 시스템을 써요. 이 계보에서 저는 같은 요구를 다시 봐요. 화면 너머에 누군가 있다는 착각 만들기.

Luna Protocol: 왜 1.5B 모델을 미세 조정했나요

두 번째 글은 미세 조정과 퓨샷 프라이밍을 탐험해요. 제 핵심 발견은요. 퓨샷 예시로 제대로 시작하면 데이터를 적게(5만 샘플) 훈련한 더 작은 모델(1.5B)이 더 큰 모델(3B)을 능가해요. 측정했을 때 저는 이 결과에 정말 놀랐어요.

역사적 챗봇과 바로 울리는 교훈이에요.

  • ELIZA는 잘 설계된 몇 개 규칙으로 이해를 흉내 낼 수 있음을 보여줬어요
  • ALICE는 99k 카테고리로 교양을 흉내 낼 수 있음을 보여줬어요
  • Luna Protocol은 좋은 미세 조정과 퓨샷 예시 다섯 개로 작은 LLM이 사람을 흉내 낼 수 있음을 보여줘요

기법은 바뀌고, 원리는 그대로예요. 자료의 질과 시스템의 정밀도가 날것의 크기보다 중요해요. 제 실험에서 제가 지키는 확신이에요.


결론: 기억할 세 가지

1. 대화형 AI는 ChatGPT와 시작하지 않았어요. ELIZA는 60살이에요. PARRY는 1972년에 튜링 테스트를 통과했어요. ALICE는 Loebner를 세 번 탔어요. Jabberwacky는 Cleverbot이 대규모로 산업화한 기록 학습의 토대를 놓았어요. 각 접근이 퍼즐 조각 하나씩을 가져왔어요. 제 포트가 확인해 준 것이 그거예요.

2. 데이터가 많다고 더 똑똑한 것은 아니에요. Jabberwacky 기록에는 규칙이 없어요. ALICE의 99k 카테고리는 배우지 않아요. Luna Protocol의 5만 샘플 미세 조정은 3B 모델을 능가해요. 통념은 "클수록 좋다"고 말해요. 챗봇의 역사는 아키텍처와 설계가 크기만큼 중요함을 보여줘요. 제 기술 선택마다 지키는 생각이에요.

3. 문제는 60년째 같아요. 사람을 다른 사람과 말한다고 어떻게 믿게 할 것인가? ELIZA는 텍스트 거울로 답했어요. PARRY는 흉내 낸 분노로. ALICE는 사실로. Luna Protocol은 자고 오타를 내는 LLM으로. 해법은 바뀌고, 필요는 남아요. 계속 탐험하고 싶게 만드는 연속성이 바로 이거예요.

저장소는 오픈 소스예요. 클론해서 각 봇을 실행하고, 60년 대화형 AI가 TypeScript 저장소 하나에 어떻게 들어가는지 직접 보세요. 저만큼 말을 시키는 재미를 느끼시길 바라요.

자료 링크
GitHub 저장소 fox3000foxy/chatbots
Luna Protocol: 봇 아키텍처 글 읽기
Luna Protocol: 퓨샷 미세 조정 글 읽기
ELIZA 원본 스크립트 anthay/ELIZA
PARRY 원본 소스 코드 lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 원본 PARRY Encounters the DOCTOR
LLM 동작의 훌륭한 설명 https://www.youtube.com/watch?v=YmLp8qe87A0

ELIZA'dan LLMlere: TypeScript ile yeniden inşa edilmiş 60 yıllık konuşma yapay zekâsı

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot, aynı problemin kökten

ELIZA'dan LLMlere: TypeScript ile yeniden inşa edilmiş 60 yıllık konuşma yapay zekâsı

1966da Joseph Weizenbaum tarihin ilk chatbotunu yaratmak için IBM 7094 üzerinde 420 satır MAD-SLIP yazdı. Programın adı ELIZA idi ve temel kalıplar ve cümle permütasyonlarıyla Rogerci bir psikoterapisti simüle ediyordu. Altı on yıl sonra konuşma yapay zekâsı halk konusu oldu: ChatGPT, Claude, Gemini her sohbettedir. Orijinal kodu yeniden okuyunca araç ekonomisi beni çarptı.

Ama bu iki uç arasında PARRY (paranoyak chatbot, 1972), ALICE (99.000 kategorili AIML kralı, 1995), Jabberwacky (kuralsız öğrenen ilk, 1997) ve Cleverbot (endüstriyel halefi, 2008) vardı. Onları aynı probleme kökten farklı beş yanıt olarak görürüm: bir makineyi konuşturmak.

Bu beş botu orijinal verileriyle TypeScript taşıdım: ELIZA scriptleri, PARRY sözlükleri, ALICEnin AIML dosyaları. Her taşıma bağımsızdır, kullanıma hazırdır ve en ince ayrıntısına belgelenmiştir. Bu çalışmayla amacım basit çalıştırmayı aşar: nasıl çalıştıklarını, tarihe neden damga vurduklarını ve mimarilerinin dünün... ve bugünün yapay zekâsına dair neyi açığa çıkardığını anlamak istiyorum.

bun run eliza    # ELIZA ile konuş (1966)
bun run parry    # PARRY ile konuş (1972)
bun run alice    # ALICE ile konuş (1995)
bun run jabber   # Jabberwacky ile konuş
bun run cleverbot # Cleverbot ile konuş
bun run meeting  # otomatik ELIZA vs PARRY

Her botu seninle didikleyeceğim, kodlarına bakacağım, sonra Luna Protocol makaleleri üzerinden modern LLMlerle köprü kuracağım.


ELIZA (1966): anladığına inandırma sanatı

En eskisiyle başlarım ve gözümde sadeliğiyle en etkileyicisi. ELIZA modern anlamda hiç zekâya sahip değildir. Yalnızca metin kalıpları ve biraz permütasyonla çalışır.

İlke

DOCTOR scripti (psikoterapist sürümü) her biri ayrıştırma kalıpları ve yeniden birleştirme kurallarıyla ilişkili anahtar kelime tablosuyla çalışır. Tipik bir kural:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO anahtar kelimedir. 0, "sonrasındaki her şeyi yakala" diyen bir ayrıştırma kalıbıdır (joker gibi). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. yeniden birleştirme kuralıdır. Hepsi bu.

"Hello, I'm sad today" deyince ELIZA:

  1. Metni büyük harfe çevirir: HELLO I'M SAD TODAY
  2. Her kelimeyi anahtar kelime tablosuna karşı tarar
  3. HELLO bulur → anahtar kelime yığınına iter
  4. En yüksek öncelikli anahtar kelimeyi alır
  5. Her ayrıştırma kalıbını sırayla dener
  6. Eşleşirse sonraki yeniden birleştirme kuralını seçer (dönüşümlü)
  7. (1), (2) vs. yerine yakalanan kısımları koyar

Ama gerçekten akıllı kısım PRE kurallarıdır. Şuna bak:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

ELIZA MY eşleştirince cümlenin geri kalanını (0 ile yakalanan) PRE kuralıyla dönüştürür ve sonucu kullanıcı yeni bir anahtar kelime söylemiş gibi yeniden enjekte eder. Somut olarak:

Sen dersin: "My mother hates me"
  → PRE dönüştürür: "YOUR MOTHER HATES YOU"
  → yeni söylemişsin gibi yeniden enjekte edilir
  → muhtemelen "YOU" eşleşir → yeni yanıt

ELIZA bu yüzden "ben" ile "sen" farkını anlıyormuş izlenimi verir: orada anlayışı simüle eden mükemmel tasarlanmış mekanik bir dönüşüm görürüm.

Kullanıcı girdisinden yanıta tam akış:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>noktalamayı normalleştirir"]
    B --> C["splitUserInput()<br>kelimelere böler"]
    C --> D["Önceliğe göre sıralı<br>anahtar kelime yığını kur"]
    D --> E{"Yığın boş değil mi?"}
    E -->|"Evet"| F["En yüksek öncelikli anahtar kelimeyi al"]
    E -->|"Hayır"| G{"Bellekte hatırlama mı?"}
    G -->|"Evet"| H["Geçmiş kullanıcı ifadesini hatırlat"]
    G -->|"Hayır"| I["Yedek: zNONE kuralı"]
    I --> J["Yanıtı döndür"]
    H --> J
    F --> K["Ayrıştırma kalıplarını eşleştir"]
    K --> L{"Eşleşme bulundu mu?"}
    L -->|"Hayır"| M{"Bağlı anahtar kelime mi?"}
    M -->|"Evet"| N["Bağlı anahtar kelimeyi yığına it"]
    N --> E
    M -->|"Hayır"| O["NOMATCH döndür"]
    O --> J
    L -->|"Evet"| P["Sonraki yeniden birleştirmeyi seç (dönüşümlü)"]
    P --> Q{"Yeniden birleştirme türü?"}
    Q -->|"PRE"| R["Kelimeleri dönüştür (I→YOU)<br>bağlantı anahtar kelimesini it"]
    R --> N
    Q -->|"NEWKEY"| S["Sonraki anahtar kelimeye atla"]
    S --> E
    Q -->|"Standart"| T["(1), (2), (0) genişlet<br>final yanıta"]
    T --> J

Onu inandırıcı yapan şey

Weizenbaumun seçimini dahice bulurum: Rogerci psikoterapi. Bu yaklaşım yorumlamadan hastanın sözlerini yansıtmaktan ibarettir. "Üzgünüm" → "Üzgün olduğunu söylüyorsun". ELIZA tam bunu yapmayı bilir ve bu terapötik teknik tanınmış olduğu için kimse yanıtını tuhaf bulmaz. Botu test edince bu ayna etkisini doğrudan hissedersin.

TypeScript taşımasında

Taşımamda .ela scriptlerini (orijinal S-expression formatı) yüklüyorum, tamamen ayrıştırıyorum (60ların zincir formatı Hollerith kodlaması dahil) ve aynı döngüyü çalıştırıyorum: büyük harfleme → bölme → anahtar kelime yığını → ayrıştırma → yeniden birleştirme → PRE/dönüşümler. Davranışa ayrıntılarına kadar sadık kalmak istedim.

➡ Kaynak kodu gör


PARRY (1972): duygulu ilk chatbot

ELIZAdan altı yıl sonra Kenneth Colby (Stanfordda psikiyatr) PARRYyi yarattı: paranoid şizofreni hastasını simüle eden bir chatbot. ELIZA boş bir ayna gibi çalışırken PARRY gerçek bir iç duygusal model taşır. İkisini art arda taşırken beni en çok çarpan bu kontrast oldu.

Duygusal model

PARRY her konuşma turunda evrilen dört sürekli değişkene sahiptir:

Değişken Taban çizgisi Tur başına azalma Açıklama
ANGER 0 −1.0 Düşmanlık, tahriş
FEAR 0 −0.2 Paranoya (hezeyan başlayınca yavaş azalır)
MISTRUST 0 −0.05 Güvensizlik (düşmesi çok yavaş)
HURT 0 −0.5 Duygusal acı

Bu değerler çıkarım kurallarıyla tetiklenen duygusal sıçramalarla (ajump, fjump, hjump) artar ve her tur doğal taban çizgilerine doğru azalır.

İnanç ağı

PARRY bel dosyasında saklanan 200+ inanca sahiptir:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Her inancın kategorisi (HUM = hasta, HUM2 = diğerleri, DOC = doktor, INT = sorgulama, INN = niyetler) ve gücü (0-5) vardır. Çıkarım kuralları (TH2, EMOTE, IF) inançları birbirine yayar:

  • TH2: A inancı eşiği aşarsa güçlenir ve sonuçları artar
  • EMOTE: inanç eşiği aşarsa duygusal sıçrama tetikler (öfke/korku/acı)
  • IF: koşullu: A doğruysa B belli seviyede doğru olur

Hezeyan hiyerarşisi (flare sistemi)

PARRYnin en büyüleyici kısmı "flare" sistemidir: merkezi hezeyana yavaş yavaş götüren bir tırmanma zinciri:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Her anahtar kelime (kalıp eşleşmeyle) önceden yazılmış bir yanıt tetikler ve konuyu izlersen PARRY yavaş yavaş merkezi zulüm hezeyanına sürüklenir. Bir flare "tetiklenince" inaktif olur (deadFlares) ve PARRY sonrakine geçer, konusunu kazan bir muhatap gibi. Bu mekaniği çok severim çünkü çok az kodla anlatı ilerlemesi yaratır.

PARRY neden tarihseldir

1972de PARRY Turing testinin bir varyantını geçti: psikiyatrlar PARRY ile ve gerçek paranoyak hastalarla konuşma transkriptleri aldı ve bunları güvenilir ayırt edemedi. Bunu başaran ilk programdır, LLMlerden 40 yıl önce. Dönemin araçlarıyla bugünküler karşılaştırınca bu başarıyı kayda değer bulurum.

➡ Kaynak kodu gör


RFC 439: ELIZA PARRY ile karşılaşınca

18 Eylül 1972de iki program tarihte ilk kez kelimenin tam anlamıyla birbirleriyle konuştu: kaydedilmiş ilk yapay zekâlar arası konuşmadır. ELIZA (Boston BBN üzerinde döner) ve PARRY (Stanfordda döner) ARPANET üzerinden, birinin çıktısını diğerinin girdisine yazan insan operatörler aracılığıyla sohbet etti. Transkript RFC 439 ("PARRY Encounters the DOCTOR") olarak yayınlandı. Bu belgeyi keşfedince alanımız için kurucu bir anı işaretlediğini anladım.

Şuna benziyordu, alıntı:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

Bu değiş tokuşun tutarlılığı beni ilk okumada şaşırttı. ELIZA terapist işini yapar: yeniden formüle etmek, sorgulamak, keşfetmek. PARRY paranoyak hasta işini yapar: şikâyet etmek, suçlamak, güvensizlik ifade etmek. İki program kendi mekanizmalarının birleşimi (ELIZA kalıpları + PARRY duygusal modeli) şans eseri iç içe geçen yanıtlar ürettiği için rollerinde mükemmel kalır. Transkriptin tamamını okuyunca bu kazara hizalanmanın kasıtlı diyalog illüzyonunu nasıl yarattığını görürsün.

Depomla bu konuşmayı yeniden üretebilirsin:

bun run meeting

Simülasyonum rastgele başlangıç konusuyla (atlar, organize suç, duygular...) iki bot arasında 25 otomatik tur çalıştırır. ELIZA ve PARRY ikisi de deterministik olmayan öğeler taşıdığından (ELIZA dönüşümlü, PARRY rastgeleleme) her çalıştırma farklı değiş tokuş üretir. Yörüngeleri karşılaştırmak için komutu birkaç kez çalıştırmaya bayılırım.

ELIZA ile PARRYde beni çarpan şey, biri iç durumu olmayan mimarili, diğeri tam duygusal modelli iki programın birlikte kasıtlı bir şeye benzeyen bir konuşma üretmesidir. 1972 için sonucu şaşırtıcı bulurum.


ALICE (1995): büyük ölçekli kalıp eşleşme

ALICE (Artificial Linguistic Internet Computer Entity) 1995te Richard Wallace tarafından yaratıldı ve Loebner Ödülünü üç kez kazandı (2000, 2001, 2004). ELIZA birkaç yüz, PARRY birkaç bin kurala sahipken ALICE 66 AIML dosyasına dağılmış 99.524 kurala sahiptir. Bu külliyatı ilk açtığımda "büyük ölçek"in o dönemde ne anlama geldiğini ölçtüm.

AIML: kategori dili

AIML (Artificial Intelligence Markup Language), soru yanıt çiftleri tanımlayan bir XML formatıdır:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Ama ALICEnin gücü jokerler ve SRAIden (Symbolic Reduction) gelir:

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- <srai><star/></srai> eşdeğeri -->
  </template>
</category>

SRAI ALICEnin bir girdiyi başka kategoriye yönlendirmesine izin verir, indirgeme zinciri yaratır:

Input: "WHAT'S UP?"
  → kalıp "WHAT IS UP" → srai "HELLO"
    → kalıp "HELLO" → şablon "Hi there!"

ALICEa esnekliğini veren mekanizma budur: her olası formülasyon için yanıt yazmak yerine kanonik bir yanıt yazar ve varyasyonları ona yönlendirirsin. Derinlik limiti 10dur: ötesi ALICE sonsuz döngülerden kaçınmak için vazgeçer (kategori tasarımı onlardan dikkatle kaçınır ve bu güvenlik ağı esas kalır). Bu tasarım seçimini zarif bulurum çünkü çok az kuralla binlerce formülasyonu çarpanlarına ayırır.

ALICE kalıpları nasıl eşleştirir

Kalıplar özgüllüğe göre sıralanır: en az jokerliler önce denenir. * ve _ jokerleri herhangi bir kelime dizisini yakalar. Motor her kalıbı regexe derler, sonra eşleşme bulana kadar sıralı kategorileri yineler.

// TypeScript uygulamamız: basitleştirilmiş ama sadık
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

ALICE Loebnerleri neden domine etti

99.524 kategori her şeyi değiştiren sayıdır. ELIZA az sayıda kuralı belirli bir bağlam (terapi) için iyi tasarlandığı için zekâ illüzyonu veriyordu. ALICE o kadar çok konuyu kapsar ki gerçek genel kültür izlenimi verir: bilimler, politika, mizah, spor, duygular, hepsi vardır. Bu yaklaşımdan yine uyguladığım bir ders çıkarırım: geniş ve özenli kapsama derin muhakeme yokluğunu telafi eder.

➡ Kaynak kodu gör


Jabberwacky (1997) ve Cleverbot (2008): epistemolojik kırılma

Önceki tüm botlar bir hipotezi paylaşır: yanıtlar yazılmalıdır. ELIZAnın S-expression kuralları, PARRYnin seçici kalıpları, ALICEnin AIML kategorileri vardır. Rollo Carpenter radikal soruyla tam tersini aldı: ya geçmiş konuşmalar yanıtları sağlasa? Bu tersine çevirmeyi cesur bulurum çünkü tüm elle kural yazmayı terk eder.

Fikir

Jabberwacky (1997 civarı yayında, 2008de Cleverbot oldu) tamamen düz transkriptte saklanan tüm konuşma geçmişine dayanır ve biri onunla konuşunca tarihteki en benzer anı arar ve sonrasında söylenen şeyi yeniden kullanır:

Kullanıcı: "hello"
  ↓
Ara: biri daha önce "hello" dedi mi?
  ↓
Evet, 3. oturumda, 14. satırda biri "hello" dedi ve bot "hi there!" yanıtladı
  ↓
Yanıtla: "hi there!"

Burada yalnızca insanların birbirine söylediği şeylerin devasa arşivini uygun anda yeniden kullanılmış bulursun. Burada belirmenin ta tanımını görürüm: insan izlerinin birikiminden konuşma davranışı doğar.

TypeScript uygulaması

TypeScript taşımam tam bu mimariyi üretir:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 satır seed + geçmiş"]
    B --> C["withReplies()<br>çiftleri çıkarır<br>(satır → yanıt)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>bu satırdan önceki bağlam)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Sıralı en iyi K aday"]
    I --> J{"pickReply()<br>rulet tekerleği<br>seçimi"}
    J -->|"Seç"| K["Yanıt = kazanan çiftin reply.text değeri"]
    J -->|"Hiçbiri"| L["Yedek: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Transkripte ekle<br>save() → JSON"]
    L --> M

Skorlamanın kalbi, Cleverbotun herkese açık anlatımlarından ilhamla kendi sezgiselimle:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0,65): kullanıcı girdisi ile tarihsel satır arası benzerlik
  • contextFit (0,25): güncel konuşma ile tarihsel satırdan önceki şey arası benzerlik
  • recencyBonus (0,10): güncel anılar biraz daha sayılır (botun kişiliği zamanla sürüklenir)

Seçim olasılıklıdır (rulet tekerleği seçimi): en iyi aday daha sık kazanır, çeşitlilik veren değişkenlikle. Bu seçimi severim çünkü botu birkaç kez test edince konuşmaları daha az öngörülebilir yapar.

Cleverbot: belgelenmiş iki yenilik

Cleverbot Jabberwacky temel kavramına iki mekanizma ekler:

  1. Çok kişili öğrenme: milyonlarca kullanıcı aynı paylaşılan transkripte katkıda bulunur. Tarihten çekilen yanıt devam eden konuşmadan tamamen farklı bir sesten gelebilir: Cleverbotun birden kişilik değiştirmesini açıklayan budur.

  2. Gecikmeli öğrenme: oturum sırasında Cleverbota söylediğin şey aynı oturumda eşleşmeye AÇIK DEĞİLDİR. Yeni satırlar pending işaretlenir ve oturumlar arası "birleştirme"den sonra eşleşebilir olur: Cleverbota bir olguyu öğretip aynı konuşmada yeniden kullanamamanın sebebi budur.

// Cleverbot: güncel satırlar birleştirilene kadar görünmezdir
const line = store.append("human", text, null, sessionId, false); // bekleyen
// ...consolidate() başlangıçta çağrılır, oturum sırasında değil

TypeScript taşımam bu iki davranışı uygular: satırların consolidated bayrağı vardır ve her REPL oturumu bekleyen satırların birleştirmesiyle başlar.

➡ Kaynak kodu gör


TypeScript taşımasının analizi: ortak mimari tasarlamak

Bu beş botu aynı dilde inşa etmek beni ilginç bir soruyla karşı karşıya bıraktı: bu kadar farklı mimariler arasında kod çarpanlarına ayrılabilir mi?

Hepsini taşıdıktan sonraki yanıtım: çok az. Her botun farklı bir temel döngüsü vardır:

Bot Ana döngü Veri Öğrenme
ELIZA Anahtar kelime yığını → ayrıştırma → yeniden birleştirme S-expression .ela scriptleri Yok
PARRY Jetonleştirme → seçici kalıplar / flareler / anahtar kelimeler / çıkarımlar 58 PDP-10 dosyası (sözlükler, inançlar, kurallar) Yok
ALICE Sıralı kalıplar → regex → AIML şablonu → özyinelemeli SRAI 66 XML AIML dosyası Yok
Jabberwacky Benzerlik → bağlam → güncellik → ağırlıklı seçim JSON transkript (kullanımla büyür) Sürekli
Cleverbot Jabberwacky ile aynı + pending/consolidated + personalar JSON transkript + çok personalı seedler Gecikmeli (oturumlar arası)

Paylaştıkları şey CLI arayüzü ve TypeScript altyapısıdır (lint için biome, çalıştırma için tsx). Geri kalan her mimariye özeldir.

Ortak tasarım seçimleri

1. Orijinal verilere sadakat. ELIZA, PARRY ve ALICE için orijinal dosyaları kullanırım, 2021de Weizenbaum arşivlerinde bulunan ELIZA scriptleri, PDP-10 orijinal PARRY kodu (58 dosya), AIML Free ALICE v1.6. Onları orijinal formlarında korurum. Botlar aynı verileri kullandığı için orijinaller gibi davranır.

2. Sahipli kısımlar için clean-room. Jabberwacky ve Cleverbot farklı bir durum koyar: kaynak kodları hiç yayınlanmadı (Existor/Rollo Carpenter sahipli tuttu). Taşımalarım bu yüzden davranışın herkese açık anlatımlarından inşa edilmiş clean-room yeniden uygulamalarıdır. Tek satır kod ya da sahipli veri kopyalamadım.

3. Minimal bağımlılıklar. Tek gerçek önkoşul TypeScripttir. ALICE AIML dosyalarının XMLini ayrıştırmak için dom-js kullanır (66 dosya, 99.524 kategori, ev yapımı XML ayrıştırma zaman kaybı olurdu). Geri kalan vanilla TypeScripttir. Depoyu klonlayınca her botu okunur ve çalıştırılması kolay tutmak için bu seçimi yaptım.


Sembolik chatbotlardan LLMlere: kavramsal sıçrama

Az önce sunduğum beş bot temel bir özelliği paylaşır: semboliktirler. "Bilgileri" tamamen açık sembollere dayanır: metin kalıpları, kural tabloları, XML kategorileri, transkript satırları. Dilin sayısal temsili olmadan tamamen çalışırlar.

O yüzden aynı cam tavanı paylaşırlar: yalnızca açık öngörülmüş ya da kaydedilmiş şeye yanıt verirler. ELIZA terapötik çerçeveden çıkınca kaybolur. PARRY yalnızca paranoyak temalarından konuşur. ALICE konuşmalarından hiçbir şey öğrenmez. Jabberwacky yalnızca zaten söylenmiş repliklerle yanıtlar. Bu limitleri test edince bu neslin neden tavan yaptığını hızlı anlarsın.

LLMler (Large Language Models) paradigmayı kökten değiştirerek bu tavanı aşar: sembolleri manipüle etmek yerine dili sayılara dönüştürür ve bu sayılar arası istatistiksel ilişkileri öğrenir. Her jetonu olasılıklar hesaplayarak anında üretir, önceden yazılmış yanıtlardan çok ağırlıklarında kodlanmış bilgilerden. Bu kaymayı büyüleyici bulurum çünkü önceki botların mantığını tamamen tersine çevirir. Hızlıca nasıl çalıştığını görelim.

1. Jetonleştirme

İlk adım metni jetonlara bölmektir: kelimelerden küçük ama karakterlerden büyük birimler:

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Her jetonun sözlükte sayısal bir IDsi vardır (güncel modeller için tipik 32.000 ila 128.000 jeton). Bu parçalanma modelin hiç görmediği kelimeleri bilinen alt kelimelere bölerek yönetmesine izin verir.

2. Gömme

Her jeton IDsi vektöre dönüştürülür: kayan sayı dizisi (orta boy bir model için tipik 4096 boyut). Bu vektör jetonun anlamını, anlamsal yakın jetonların yakın vektörlere sahip olduğu matematiksel bir uzayda kodlayan bir gömmedir (embedding):

vektör("roi")  − vektör("homme") + vektör("femme")  ≈  vektör("reine")

Bu özellik benim ya da araştırmacıların açık programlaması olmadan eğitimden belirir. Burada kelimelerin benzer bağlamlarda görünme biçiminin doğrudan sonucu görürüm.

3. Dikkat

Dikkat mekanizması (2017de "Attention is All You Need" makalesiyle geldi) LLMleri mümkün yapandır. Her jeton için dikkat bunu anlamak için cümledeki hangi diğer jetonların önemli olduğunu hesaplar:

"La banque a refusé mon prêt."
      ↑
"banque" jetonu bakar: "refusé", "prêt" → finans kurumu olduğunu anlar

"Je vais me promener sur la banque."
      ↑
"banque" jetonu bakar: "promener", "sur" → kıyı olduğunu anlar

Dikkat modelin bağlamı yakalamasına izin verir: her jeton onu çevreleyenlere göre anlaşılır, tamamen ilişkisel okumada. LLMlerin sabit kalıpları aşmasını anlamama en çok yardımcı olan bu bağlam hassasiyetidir.

4. Sonraki jeton tahmini

LLM eğitimi aldatıcı bir sadeliğe dayanır: ona bir metin gösterirsin, son jetonu gizlersin ve tahmin etmesini istersin. Sonra işlemi milyarlarca kez tekrarlarsın. Bu fikri severim çünkü çok basit bir öğrenme kuralı çok zengin yetenekler üretir.

Girdi:  "Je ne comprends"
Gizli:  "pas"
Model tahmini: "pas" (olasılık 0.87), "rien" (0.05), "jamais" (0.02)...

Amaç her konumda gerçek jetonun olasılığını maksimize etmektir. Buna next-token prediction denir. Eğitimde model terabaytlarca metinde tahmin hatasını minimize etmek için milyarlarca parametresini ayarlar.

Çıkarım anında (onunla konuşunca) model döngüde birer jeton üretir:

Jeton 1: "Je"    (girdi: "Parle-moi de toi.")
Jeton 2: "suis"  (girdi: "Parle-moi de toi. Je")
Jeton 3: "un"    (girdi: "Parle-moi de toi. Je suis")
Jeton 4: "chatbot" (girdi: "Parle-moi de toi. Je suis un")
...

Her jeton olasılığına göre örneklenir (sıcaklık, top-k, top-p "yaratıcılık" derecesini kontrol eder). Ve hepsi bu. Bu işlemi binlerce kez tekrarlayan milyarlarca parametre. Bu kadar tekrarlayan mekanizmanın bu kadar çeşitli yanıtlar üretmesini baş döndürücü bulurum.

Temelde değişen şey

Yön Sembolik botlar (ELIZA, PARRY, ALICE) Modern LLMler
Temsil Açık kelimeler ve kurallar Sayısal vektörler (gömmeler)
Üretim Önceden yazılmış yanıtlardan seçim Jeton jeton olasılıklı tahmin
Bilgiler Kural dosyalarında saklanır Ağ ağırlıklarında kodlanır
Öğrenme Manuel (kural yazımı) Otomatik (külliyat eğitimi)
Sağlamlık Öngörülen kalıplar dışında yok Hiç görülmemiş girdilere geneller
Yorumlanabilirlik Mükemmel (kurallar okunabilir) Sınırlı (kara kutu)

Klasik chatbotlar şeffaf ama kırılgandır. Bir LLM sağlam ama opaktır. Bugün onları farklı ihtiyaçlar için tamamlayıcı araçlar görürüm ve yanıtlar üzerinde tutmak istediğin kontrol seviyesine göre birini ya da diğerini seçerim.

LLMlerin iç işleyişini derinleştirmek istersen bu videoyu mükemmel kaynak öneririm:

How LLMs Work: YouTube

Luna Protocol: modern sentez

Luna Protocol makaleleri (bağlantıları aşağıda) az önce gösterdiğim her şeyin gözümde en olgun sentezini temsil eder: yerel bir LLM ile sofistike davranış sistemini birleştiren modern bir Discord botu, hepsi 60 yıllık konuşma yapay zekâsı dersleri üstüne inşa edilmiş. Tarihsel taşımalarımı güncel deneylerime bağlamama izin veren proje budur.

Luna Protocol: insanı simüle eden özerk bir Discord botu yarattım

Bu makale LLM tabanlı bir Discord botunun tam mimarisini detaylandırır:

  • Öncelikli tetikleme sistemi (bahsetme > DM > ad > anahtar kelime > takip > rastgele)
  • İnsani davranışlar: değişken konsantrasyon, yazım hataları, tereddütler (%15), unutkanlıklar (%3), tematik yorgunluk
  • Uyku saatleri: bot saate göre uyur, yavaşlar ya da görmezden gelir
  • TTS pipelineı: Piper + ffmpeg ile ses sentezi → Discord sesli mesajları
  • Gerçek zamanlı akış: LLM jetonları tipli olay veriyolunda tek tek yayar

Bu makaleyi tarihsel chatbotlara bağlayan şey aynı arayıştır: bir insanla konuştuğuna inandırmak. ELIZA metin aynaları kullanıyordu. PARRY duygusal model kullanıyordu. ALICE 99 bin kategori kullanıyordu. Luna Protocol ince ayarlı bir LLM + insani kusurları simüle eden davranış sistemi kullanır. Bu soyda ekranın öbür tarafında bir mevcudiyet illüzyonu yaratma aynı gereğini bulurum.

Luna Protocol: 1,5B modeli neden ince ayarladım

İkinci makale ince ayar ve few-shot priming keşfeder. Merkezi keşfim: doğru few-shot örneklerle başlatılınca daha az veride (50 bin örnek) eğitilmiş daha küçük bir model (1,5B), daha büyük modeli (3B) geride bırakır. Bu sonucu ölçünce gerçekten şaşırdım.

Doğrudan tarihsel chatbotlarla yankılanan bir derstir:

  • ELIZA birkaç iyi tasarlanmış kuralla anlayışın simüle edilebileceğini gösteriyordu
  • ALICE 99 bin kategoriyle genel kültürün simüle edilebileceğini gösteriyordu
  • Luna Protocol iyi ince ayar ve 5 few-shot örnekle küçük bir LLMnin insanı simüle edebileceğini gösterir

Teknik değişir ve ilke aynı kalır: veri kalitesi ve sistem hassasiyeti ham boyuttan önemlidir. Kendi deneylerimde savunduğum kanaat budur.


Sonuç: akılda tutulacak üç şey

1. Konuşma yapay zekâsı ChatGPT ile başlamadı. ELIZA 60 yaşındadır. PARRY 1972de Turing testini geçti. ALICE Loebneri üç kez kazandı. Jabberwacky transkriptle öğrenmenin temellerini attı, Cleverbot onu büyük ölçekte endüstrileştirdi. Her yaklaşım yapbozun bir parçasını getirdi. Taşımalarım bana bunu doğruladı.

2. Daha fazla veri ≠ daha akıllı. Jabberwacky transkriptinin kuralı yoktur. ALICEnin 99 bin kategorisi öğrenmez. Luna Protocolün 50 bin örnekte ince ayarı 3B modeli geride bırakır. Yaygın bilgelik "ne kadar büyük o kadar iyi" der: chatbot tarihi mimari ve tasarımın boyut kadar önemli olduğunu gösterir. Bu fikri her teknik seçimimde savunurum.

3. Problem 60 yıldır aynıdır. Bir insanı başka bir insanla konuştuğuna nasıl inandırırsın? ELIZA metin aynalarıyla yanıtlıyordu. PARRY simüle öfkeyle. ALICE olgularla. Luna Protocol uyuyan ve yazım hatası yapan bir LLM ile. Çözüm değişir, ihtiyaç kalır. Beni keşfetmeye devam ettiren bu sürekliliktir.

Depo açık kaynaktır, klonlayabilir, her botu çalıştırabilir ve 60 yıllık konuşma yapay zekâsının tek TypeScript deposuna nasıl sığdığını kendin görebilirsin. Umarım onları konuştururken benim kadar keyif alırsın.

Kaynak Bağlantı
GitHub deposu fox3000foxy/chatbots
Luna Protocol: bot mimarisi Makaleyi oku
Luna Protocol: ince ayar few-shot Makaleyi oku
Orijinal ELIZA scriptleri anthay/ELIZA
Orijinal PARRY kaynak kodu lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
Orijinal RFC 439 PARRY Encounters the DOCTOR
LLMlerin çalışmasının mükemmel açıklaması https://www.youtube.com/watch?v=YmLp8qe87A0

Da ELIZA agli LLM: 60 anni di IA conversazionale, ricostruita in TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot: cinque architetture radicalmente diverse dello stesso problema, portate in TypeScript coi loro dati originali. Dal 1966 agli LLM moderni, ecco come l'IA conversazionale ha imparato a parlare, e cosa un repo di chatbot ci insegna su 60 anni di ricerca.

Da ELIZA agli LLM: 60 anni di IA conversazionale, ricostruita in TypeScript

Nel 1966, Joseph Weizenbaum scriveva 420 righe di MAD-SLIP su un IBM 7094 per creare il primo chatbot della storia. Il programma si chiamava ELIZA, e simulava una psicoterapeuta rogersiana con pattern di base e permutazioni di frasi. Sei decenni dopo, l'IA conversazionale è diventata un tema grand public: ChatGPT, Claude, Gemini sono in tutte le conversazioni. Quando ho riletto quel codice originale, sono stato colpito dalla sua economia di mezzi.

Ma tra questi due estremi, ci sono stati PARRY (il chatbot paranoico, 1972), ALICE (il re dell'AIML con 99 000 categorie, 1995), Jabberwacky (il primo a imparare senza regole, 1997), e Cleverbot (il suo successore industriale, 2008). Li vedo come cinque risposte radicalmente diverse allo stesso problema: far parlare una macchina.

Ho portato questi cinque bot in TypeScript coi loro dati originali: script ELIZA, dizionari PARRY, file AIML di ALICE. Ogni port è autonomo, pronto all'uso, e documentato nei minimi dettagli. Il mio obiettivo con questo lavoro va oltre la semplice esecuzione: voglio capire come funzionavano, perché hanno segnato la storia, e cosa le loro rispettive architetture rivelano sull'IA di ieri... e di oggi.

bun run eliza    # Parla con ELIZA (1966)
bun run parry    # Parla con PARRY (1972)
bun run alice    # Parla con ALICE (1995)
bun run jabber   # Parla con Jabberwacky
bun run cleverbot # Parla con Cleverbot
bun run meeting  # ELIZA contro PARRY automatico

Smonto ogni bot con te, guardo il loro codice, poi faccio il ponte coi LLM moderni attraverso gli articoli su Luna Protocol.


ELIZA (1966): l'arte di far credere di capire

Inizio dalla più antica, e ai miei occhi la più impressionante per semplicità. ELIZA non ha alcuna intelligenza in senso moderno. Funziona solo con pattern testuali e un po' di permutazione.

Il principio

Lo script DOCTOR (la versione psicoterapeuta) funziona con una tabella di keyword, ognuna associata a decomposition pattern e reassembly rule. Ecco una regola tipica:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO è la parola chiave. 0 è un pattern di scomposizione che dice "cattura tutto ciò che segue" (come un wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. è la regola di riassemblaggio. È tutto.

Quando dici "Hello, I'm sad today", ELIZA:

  1. Mette il testo in maiuscolo: HELLO I'M SAD TODAY
  2. Scansiona ogni parola contro la sua tabella di keyword
  3. Trova HELLO: lo spinge sulla pila di keyword
  4. Prende la keyword con la massima priorità
  5. Prova ogni pattern di scomposizione in ordine
  6. Se matcha, seleziona la prossima regola di riassemblaggio (round-robin)
  7. Sostituisce gli (1), (2) ecc. con le parti catturate

Ma la parte davvero intelligente, sono le PRE rule. Guarda qua:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Quando ELIZA matcha MY, trasforma il resto della frase (catturato da 0) via la PRE rule, e reinietta il risultato come se l'utente avesse appena detto una nuova parola chiave. In concreto:

Dici: "My mother hates me"
  → PRE trasforma: "YOUR MOTHER HATES YOU"
  → reiniettato come se l'avessi appena detto
  → matcha probabilmente "YOU" → nuova risposta

È per questo che ELIZA dà l'impressione di capire la differenza tra "io" e "tu": vedo lì una trasformazione meccanica perfettamente progettata che simula la comprensione.

Ecco il flusso completo, dalla digitazione utente alla risposta:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalizza la punteggiatura"]
    B --> C["splitUserInput()<br>taglia in parole"]
    C --> D["Build keyword stack<br>ordinato per priorità"]
    D --> E{"Stack non vuoto?"}
    E -->|"Sì"| F["Pop highest-priority keyword"]
    E -->|"No"| G{"Memory recall?"}
    G -->|"Sì"| H["Recall past user statement"]
    G -->|"No"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"No"| M{"Linked keyword?"}
    M -->|"Sì"| N["Push linked keyword to stack"]
    N --> E
    M -->|"No"| O["Return NOMATCH"]
    O --> J
    L -->|"Sì"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

Ciò che la rendeva credibile

Considero la scelta di Weizenbaum un colpo di genio: la psicoterapia rogersiana. Questo approccio consiste a riflettere le parole del paziente senza interpretare. "Sono triste" → "Lei dice di essere triste". È esattamente ciò che ELIZA sa fare, e dato che questa tecnica terapeutica è riconosciuta, nessuno trova strana la sua risposta. Quando provi il bot, senti direttamente questo effetto specchio.

Nel port TypeScript

Nel mio port, carico gli script .ela (formato S-expression originale), li parso interamente (compresa la codifica Hollerith, quel formato di stringa anni 60), ed eseguo lo stesso ciclo: uppercasing → split → keyword stack → scomposizione → riassemblaggio → PRE/transform. Ho voluto restare fedele al comportamento originale fin nei dettagli.

➡ Vedi il codice sorgente


PARRY (1972): il primo chatbot con emozioni

Sei anni dopo ELIZA, Kenneth Colby (psichiatra a Stanford) ha creato PARRY: un chatbot che simula un paziente affetto da schizofrenia paranoide. Là dove ELIZA funzionava come uno specchio vuoto, PARRY imbarca un vero modello emozionale interno. È questo contrasto che mi ha colpito di più portandoli uno dopo l'altro.

Il modello emozionale

PARRY ha quattro variabili continue che evolvono a ogni turno di conversazione:

Variabile Linea di base Decadimento/turno Descrizione
ANGER 0 −1.0 Ostilità, irritazione
FEAR 0 −0.2 Paranoia (decade lentamente dopo l'inizio del delirio)
MISTRUST 0 −0.05 Diffidenza (lentissima a scendere)
HURT 0 −0.5 Pena emozionale

Questi valori aumentano via salti emozionali (ajump, fjump, hjump) innescati da regole di inferenza, e decadono naturalmente verso le loro linee di base a ogni turno.

La rete di credenze

PARRY ha oltre 200 credenze salvate nel file bel:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Ogni credenza ha una categoria (HUM = il paziente, HUM2 = gli altri, DOC = il dottore, INT = l'interrogatorio, INN = le intenzioni) e una forza (0-5). Le regole di inferenza (TH2, EMOTE, IF) propagano le credenze tra loro:

  • TH2: se una credenza A supera una soglia, si rafforza e le sue conseguenze aumentano
  • EMOTE: se una credenza supera una soglia, innesca un salto emozionale (anger/fear/hurt)
  • IF: condizionale: se A è vera, allora B diventa vera a un certo livello

La gerarchia dei deliri (flare system)

La parte più affascinante di PARRY, è il suo sistema di "flare": una catena di escalation che porta progressivamente verso il delirio centrale:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Ogni parola chiave innesca una risposta pre-scritta (via pattern matching), e se segui il tema, PARRY deriva progressivamente verso il suo delirio centrale di persecuzione. Una volta che un flare è "innescato", diventa inattivo (deadFlares) e PARRY passa al successivo, come un interlocutore che scava il suo tema. Amo molto questa meccanica perché crea una progressione narrativa con pochissimo codice.

Perché PARRY è storico

Nel 1972, PARRY ha passato una variante del test di Turing: psichiatri hanno ricevuto trascrizioni di conversazioni con PARRY e con veri pazienti paranoici, e non hanno potuto distinguerli in modo affidabile. È il primo programma a riuscirci, 40 anni prima degli LLM. Trovo questa impresa notevole quando confronti i mezzi dell'epoca con quelli di oggi.

➡ Vedi il codice sorgente


RFC 439: quando ELIZA incontra PARRY

Il 18 settembre 1972, due programmi si sono parlati per la prima volta nella storia, letteralmente: è la prima conversazione inter-IA mai registrata. ELIZA (in esecuzione su BBN a Boston) e PARRY (in esecuzione a Stanford) hanno conversato via ARPANET, ritrasmessi da operatori umani che digitavano l'uscita dell'uno verso l'ingresso dell'altro. La trascrizione è stata pubblicata come RFC 439 ("PARRY Encounters the DOCTOR"). Quando ho scoperto questo documento, ho capito che segnava un momento fondatore per il nostro campo.

Ecco un estratto di com'era:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

La coerenza di questo scambio mi ha sorpreso alla prima lettura. ELIZA fa il suo lavoro di terapeuta: riformulare, interrogare, esplorare. PARRY fa il suo lavoro di paziente paranoico: lamentarsi, accusare, esprimere diffidenza. Entrambi i programmi restano perfettamente nel loro ruolo grazie alla combinazione dei loro rispettivi meccanismi (pattern ELIZA + modello emozionale PARRY) che produce risposte che si incastrano per caso. Quando leggi la trascrizione intera, vedi quanto questo allineamento accidentale crea l'illusione di un dialogo intenzionale.

Puoi riprodurre questa conversazione col mio repo:

bun run meeting

La mia simulazione lancia 25 turni automatici tra i due bot, con un tema di partenza casuale (cavalli, crimine organizzato, emozioni...). Dato che ELIZA e PARRY imbarcano entrambi elementi non deterministici (round-robin ELIZA, randomizzazione PARRY), ogni esecuzione produce uno scambio diverso. Adoro rilanciare il comando più volte per confrontare le traiettorie.

Ciò che mi colpisce con ELIZA contro PARRY, è l'incontro tra due programmi, l'uno con un'architettura senza stato interno, l'altro con un modello emozionale completo, che producono insieme una conversazione che assomiglia a qualcosa di deliberato. Per il 1972, trovo il risultato sbalorditivo.


ALICE (1995): il pattern matching su larga scala

ALICE (Artificial Linguistic Internet Computer Entity) è stata creata da Richard Wallace nel 1995, e ha vinto il Loebner Prize tre volte (2000, 2001, 2004). Là dove ELIZA aveva qualche centinaio di regole e PARRY qualche migliaio, ALICE ne ha 99 524, divise in 66 file AIML. Quando ho aperto questo corpus per la prima volta, ho misurato cosa "larga scala" volesse dire all'epoca.

AIML: il linguaggio delle categorie

AIML (Artificial Intelligence Markup Language) è un formato XML per definire coppie domanda-risposta:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Ma la potenza di ALICE viene dai wildcard e dallo SRAI (Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- equivale a <srai><star/></srai> -->
  </template>
</category>

Lo SRAI permette ad ALICE di reindirizzare un input verso un'altra categoria, creando una catena di riduzione:

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

È il meccanismo che dà ad ALICE la sua flessibilità: invece di scrivere una risposta per ogni formulazione possibile, scrivi una risposta canonica e reindirizzi le variazioni verso di lei. Il limite di profondità è 10: oltre, ALICE abbandona per evitare cicli infiniti (la progettazione delle categorie li evita con cura, e questa rete di sicurezza resta essenziale). Trovo questa scelta di progettazione elegante perché fattorizza migliaia di formulazioni con pochissime regole.

Come ALICE matcha i pattern

I pattern sono ordinati per specificità: quelli con meno wildcard sono provati per primi. I wildcard * e _ catturano qualsiasi sequenza di parole. Il motore compila ogni pattern in regex, poi itera le categorie ordinate finché trova un match.

// Nostra implementazione TypeScript: semplificata ma fedele
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Perché ALICE ha dominato i Loebner

99 524 categorie, è un numero che cambia tutto. ELIZA dava l'illusione dell'intelligenza perché le sue poche regole erano ben progettate per un contesto specifico (la terapia). ALICE copre così tanti temi da dare l'impressione di avere una vera cultura generale: scienze, politica, umorismo, sport, emozioni, c'è tutto. Trattengo da questo approccio una lezione che applico ancora: una copertura ampia e curata compensa l'assenza di ragionamento profondo.

➡ Vedi il codice sorgente


Jabberwacky (1997) e Cleverbot (2008): la rottura epistemologica

Tutti i bot precedenti condividono un'ipotesi: bisogna scrivere le risposte. ELIZA ha le sue regole S-expression, PARRY i suoi pattern selettivi, ALICE le sue categorie AIML. Rollo Carpenter ha preso la direzione opposta totale con una domanda radicale: e se lasciassimo le conversazioni passate fornire le risposte? Trovo questo rovesciamento audace perché abbandona ogni scrittura manuale di regole.

L'idea

Jabberwacky (lanciato verso il 1997, diventato Cleverbot nel 2008) poggia interamente su tutto lo storico delle conversazioni salvato in un transcript piatto, e quando qualcuno gli parla, cerca in questo storico il momento più simile e riusa ciò che è stato detto dopo:

Utente: "hello"
  ↓
Cerca: qualcuno ha già detto "hello" prima?
  ↓
Sì, nella sessione #3, riga 14, qualcuno ha detto "hello" e il bot ha risposto "hi there!"
  ↓
Rispondi: "hi there!"

Troverai qui solo un archivio gigante di cose che persone si sono dette, riusato al momento opportuno. Ci vedo la definizione stessa di emergenza: un comportamento conversazionale nasce da un accumulo di tracce umane.

L'implementazione TypeScript

Il mio port TypeScript riproduce questa architettura esatta:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 righe seed + storico"]
    B --> C["withReplies()<br>estrae le coppie<br>(riga → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>contesto prima di questa riga)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidati ordinati"]
    I --> J{"pickReply()<br>selezione roulette-wheel"}
    J -->|"Pick"| K["Reply = reply.text<br>della coppia vincente"]
    J -->|"Nessuno"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append al transcript<br>save() → JSON"]
    L --> M

Ecco il cuore dello scoring, con la mia euristica ispirata alle descrizioni pubbliche di Cleverbot:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): similarità tra l'input utente e la riga storica
  • contextFit (0.25): similarità tra la conversazione recente e ciò che precedeva la riga storica
  • recencyBonus (0.10): i ricordi recenti contano un po' di più (la personalità del bot deriva col tempo)

Il pick è probabilistico (selezione roulette-wheel): il miglior candidato vince più spesso, con una parte di variabilità che dà varietà. Apprezzo questa scelta perché rende le conversazioni meno prevedibili quando provi il bot più volte.

Cleverbot: le due innovazioni documentate

Cleverbot aggiunge due meccanismi al concetto base di Jabberwacky:

  1. Apprendimento multi-persona: milioni di utenti contribuiscono allo stesso transcript condiviso. Una risposta tirata dallo storico può venire da una voce completamente diversa da quella della conversazione in corso: il che spiega perché Cleverbot cambia improvvisamente personalità.

  2. Apprendimento differito: ciò che dici a Cleverbot durante una sessione NON è disponibile per il match durante quella stessa sessione. Le nuove righe sono marcate pending e diventano matchabili solo dopo una "consolidazione" tra le sessioni: il che spiega perché non puoi insegnare un fatto a Cleverbot e riusarlo nella stessa conversazione.

// Cleverbot: le righe recenti sono invisibili fino a consolidazione
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() è chiamata all'avvio, non durante la sessione

Il mio port TypeScript implementa entrambi i comportamenti: le righe hanno un flag consolidated, e ogni sessione di REPL inizia con una consolidazione delle righe in attesa.

➡ Vedi il codice sorgente


Analisi del port TypeScript: progettare un'architettura comune

Costruire questi cinque bot nello stesso linguaggio mi ha messo davanti a una domanda interessante: è possibile fattorizzare codice tra architetture così diverse?

La mia risposta dopo averli portati tutti: pochissimo. Ogni bot ha un ciclo fondamentale diverso:

Bot Ciclo principale Dati Apprendimento
ELIZA Keyword stack → scomposizione → riassemblaggio Script .ela in S-expression Nessuno
PARRY Tokenizzazione → pattern selettivi / flare / keyword / inferenze 58 file PDP-10 (dizionari, credenze, regole) Nessuno
ALICE Pattern ordinati → regex → template AIML → SRAI ricorsivo 66 file AIML XML Nessuno
Jabberwacky Similarità → contesto → recency → pick pesato Transcript JSON (cresce con l'uso) Continuo
Cleverbot Stesso di Jabberwacky + pending/consolidated + persona Transcript JSON + seed multi-persona Differito (tra sessioni)

Ciò che condividono, è l'interfaccia CLI e l'infrastruttura TypeScript (biome per il lint, tsx per l'esecuzione). Il resto è specifico di ogni architettura.

Scelte di progettazione comuni

1. Fedeltà ai dati originali. Per ELIZA, PARRY e ALICE, uso i file originali, script ELIZA ritrovati negli archivi Weizenbaum nel 2021, codice originale PARRY del PDP-10 (58 file), AIML Free ALICE v1.6. Li conservo nella loro forma originale. I bot si comportano come gli originali perché usano gli stessi dati.

2. Clean-room per le parti proprietarie. Jabberwacky e Cleverbot pongono un caso diverso: il loro codice sorgente non è mai stato pubblicato (Existor/Rollo Carpenter l'ha tenuto proprietario). I miei port sono quindi reimplementazioni clean-room, costruite solo da descrizioni pubbliche del comportamento. Non ho copiato alcuna riga di codice né dato proprietario.

3. Dipendenze minime. L'unico vero prerequisito, è TypeScript. ALICE usa dom-js per parsare l'XML dei file AIML (66 file, 99 524 categorie, il parsing XML casalingo sarebbe una perdita di tempo). Tutto il resto è in vanilla TypeScript. Ho fatto questa scelta per tenere ogni bot leggibile e facile da lanciare quando cloni il repo.


Dai chatbot simbolici agli LLM: il salto concettuale

I cinque bot che ti ho appena presentato condividono una caratteristica fondamentale: sono simbolici. Le loro "conoscenze" poggiano interamente su simboli espliciti: pattern testuali, tabelle di regole, categorie XML, righe di transcript. Funzionano interamente senza rappresentazione numerica del linguaggio.

Condividono quindi lo stesso tetto di vetro: rispondono solo a ciò che è stato esplicitamente previsto o registrato. ELIZA è persa se esci dalla cornice terapeutica. PARRY parla solo dei suoi temi paranoici. ALICE non impara niente dalle sue conversazioni. Jabberwacky risponde solo con battute già pronunciate. Quando provi questi limiti, capisci in fretta perché questa generazione ha toccato il tetto.

Gli LLM (Large Language Models) superano questo tetto cambiando radicalmente paradigma: invece di manipolare simboli, convertono il linguaggio in numeri e imparano relazioni statistiche tra questi numeri. Generano ogni token al volo calcolando probabilità, da conoscenze codificate nei loro pesi invece che da risposte pre-scritte. Trovo questo ribaltamento affascinante perché rovescia completamente la logica dei bot precedenti. Vediamo in fretta come funziona.

1. Tokenization

Il primo passo è tagliare il testo in token: unità più piccole delle parole ma più grandi dei caratteri:

"Non capisco"
  → ["Non", " cap", "isco"]

Ogni token ha un ID numerico in un vocabolario (tipicamente 32 000 a 128 000 token per i modelli recenti). Questa frammentazione permette al modello di gestire parole mai viste scomponendole in sotto-parole note.

2. Embedding

Ogni ID token è convertito in un vettore: una tabella di numeri in virgola mobile (tipicamente 4096 dimensioni per un modello di taglia media). Questo vettore è un embedding che codifica il senso del token in uno spazio matematico dove token semanticamente vicini hanno vettori vicini:

vettore("re")  − vettore("uomo") + vettore("donna")  ≈  vettore("regina")

Questa proprietà emerge dall'allenamento, senza programmazione esplicita mia o dei ricercatori. Ci vedo una conseguenza diretta del modo in cui le parole appaiono in contesti simili.

3. Attention

Il meccanismo di attention (introdotto dal paper "Attention is All You Need" nel 2017) è ciò che ha reso possibili gli LLM. Per ogni token, l'attention calcola quali altri token nella frase sono importanti per capire questo:

"La banca ha rifiutato il mio prestito."
       ↑
Token "banca" guarda: "rifiutato", "prestito" → capisce che è un'istituzione finanziaria

"Vado a passeggiare sulla banca."
       ↑
Token "banca" guarda: "passeggiare", "sulla" → capisce che è una riva

L'attention permette al modello di catturare il contesto: ogni token è capito in base a quelli che lo circondano, in una lettura pienamente relazionale. È questa sensibilità al contesto che più mi ha aiutato a capire perché gli LLM superano i pattern fissi.

4. Previsione del prossimo token

L'allenamento di un LLM poggia su una semplicità ingannevole: gli mostri un testo, nascondi l'ultimo token, e gli chiedi di prevederlo. Poi ripeti l'operazione miliardi di volte. Amo questa idea perché una regola di apprendimento molto semplice produce capacità molto ricche.

Input:  "Non cap"
Nascosto:  "isco"
Previsione del modello: "isco" (probabilità 0.87), "iente" (0.05), "mai" (0.02)...

L'obiettivo è massimizzare la probabilità del vero token a ogni posizione. È ciò che chiamiamo next-token prediction. Durante l'allenamento, il modello aggiusta i suoi miliardi di parametri per minimizzare l'errore di previsione su terabyte di testo.

Al momento dell'inferenza (quando gli parli), il modello genera un token alla volta in ciclo:

Token 1: "Io"    (input: "Parlami di te.")
Token 2: "sono"  (input: "Parlami di te. Io")
Token 3: "un"    (input: "Parlami di te. Io sono")
Token 4: "chatbot" (input: "Parlami di te. Io sono un")
...

Ogni token è campionato secondo la sua probabilità (temperatura, top-k, top-p controllano il grado di "creatività"). Ed è tutto. Miliardi di parametri che ripetono questa operazione migliaia di volte. Trovo vertiginoso che un meccanismo così ripetitivo produca risposte così varie.

Cosa cambia fondamentalmente

Aspetto Bot simbolici (ELIZA, PARRY, ALICE) LLM moderni
Rappresentazione Parole e regole esplicite Vettori numerici (embedding)
Generazione Selezione in risposte pre-scritte Previsione probabilistica token per token
Conoscenze Salvate in file di regole Codificate nei pesi della rete
Apprendimento Manuale (scrittura di regole) Automatico (allenamento su corpus)
Robustezza Nulla fuori dai pattern previsti Generalizza a input mai visti
Interpretabilità Perfetta (si possono leggere le regole) Limitata (scatola nera)

I chatbot classici sono trasparenti ma fragili. Un LLM è robusto ma opaco. Li vedo oggi come strumenti complementari per bisogni diversi, e scelgo l'uno o l'altro secondo il livello di controllo che vuoi tenere sulle risposte.

Se vuoi approfondire il funzionamento interno degli LLM, ti raccomando questo video come ottima risorsa:

How LLMs Work su YouTube

Luna Protocol: la sintesi moderna

Gli articoli su Luna Protocol (i cui link sono qui sotto) rappresentano ai miei occhi la sintesi più riuscita di tutto ciò che ti ho appena mostrato: un bot Discord moderno che combina un LLM locale con un sistema comportamentale sofisticato, il tutto costruito sulle lezioni di 60 anni di IA conversazionale. È il progetto che mi ha permesso di collegare i miei port storici alle mie sperimentazioni attuali.

Luna Protocol: ho creato un bot Discord autonomo che simula un essere umano

Questo articolo dettaglia l'architettura completa di un bot Discord basato su LLM:

  • Sistema di trigger a priorità (menzione > DM > nome > parola chiave > follow-up > casuale)
  • Comportamenti umani: concentrazione variabile, refusi, esitazioni (15%), dimenticanze (3%), fatica tematica
  • Orari di sonno: il bot dorme, rallenta, o ignora secondo l'ora
  • Pipeline TTS: sintesi vocale via Piper + ffmpeg → messaggi vocali Discord
  • Streaming in tempo reale: l'LLM emette i token uno a uno su un bus di eventi tipizzato

Ciò che collega questo articolo ai chatbot storici, è la stessa ricerca: far credere di parlare a una persona. ELIZA usava specchi testuali. PARRY usava un modello emozionale. ALICE usava 99k categorie. Luna Protocol usa un LLM fine-tunato + un sistema comportamentale che simula le imperfezioni umane. Ritrovo in questa filiazione la stessa esigenza: creare l'illusione di una presenza dall'altro lato dello schermo.

Luna Protocol: perché ho fine-tunato un modello da 1,5B

Il secondo articolo esplora il fine-tuning e il few-shot priming. La mia scoperta centrale: un modello più piccolo (1,5B) allenato su meno dati (50k campioni) supera un modello più grosso (3B) quando lo si innesca correttamente con esempi few-shot. Sono stato davvero sorpreso da questo risultato quando l'ho misurato.

È una lezione che risuona direttamente coi chatbot storici:

  • ELIZA mostrava che con poche regole ben progettate, si può simulare la comprensione
  • ALICE mostrava che con 99k categorie, si può simulare la cultura generale
  • Luna Protocol mostra che con un buon fine-tuning e 5 esempi few-shot, un piccolo LLM può simulare un essere umano

La tecnica cambia, e il principio resta lo stesso: la qualità dei dati e la precisione del sistema contano più della taglia grezza. È una convinzione che difendo nei miei esperimenti.


Conclusione: tre cose da trattenere

1. L'IA conversazionale non è iniziata con ChatGPT. ELIZA ha 60 anni. PARRY ha passato il test di Turing nel 1972. ALICE ha vinto il Loebner tre volte. Jabberwacky ha posto le basi dell'apprendimento per transcript, che Cleverbot ha industrializzato su larga scala. Ogni approccio ha portato un pezzo del puzzle. È ciò che i miei port mi hanno confermato.

2. Più dati non uguale più intelligente. Il transcript di Jabberwacky non ha regole. Le 99k categorie di ALICE non imparano. Il fine-tuning di Luna Protocol su 50k campioni supera il modello 3B. La saggezza convenzionale dice "più è grosso, meglio è": la storia dei chatbot mostra che architettura e progettazione contano quanto la taglia. Difendo questa idea in ognuna delle mie scelte tecniche.

3. Il problema è lo stesso da 60 anni. Come far credere a un umano che parla a un altro umano? ELIZA rispondeva con specchi testuali. PARRY con rabbia simulata. ALICE con fatti. Luna Protocol con un LLM che dorme e fa refusi. La soluzione cambia, il bisogno resta. È questa continuità che mi dà voglia di continuare a esplorare.

Il repo è open source, puoi clonarlo, lanciare ogni bot, e vedere da solo come 60 anni di IA conversazionale stanno in un solo deposito TypeScript. Spero prenderai tanto piacere quanto me a farli parlare.

Risorsa Link
Deposito GitHub fox3000foxy/chatbots
Luna Protocol: architettura bot Leggi l'articolo
Luna Protocol: fine-tuning few-shot Leggi l'articolo
Script ELIZA originali anthay/ELIZA
Codice sorgente PARRY originale lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 originale PARRY Encounters the DOCTOR
Ottima spiegazione del funzionamento degli LLM https://www.youtube.com/watch?v=YmLp8qe87A0

Von ELIZA zu LLM: 60 Jahre Konversations-KI, nachgebaut in TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot, fünf radikal unterschiedliche Architekturen desselben Problems, portiert nach TypeScript mit ihren Originaldaten. Von 1966 zu modernen LLM, so hat Konversations-KI sprechen gelernt, und was ein Chatbot-Repo über 60 Jahre Forschung lehrt.

Von ELIZA zu LLM: 60 Jahre Konversations-KI, nachgebaut in TypeScript

1966 schrieb Joseph Weizenbaum 420 Zeilen MAD-SLIP auf einer IBM 7094, um den ersten Chatbot der Geschichte zu erschaffen. Das Programm hieß ELIZA, und es simulierte eine rogerianische Psychotherapeutin mit Basismustern und Satzpermutationen. Sechs Jahrzehnte später ist Konversations-KI ein Mainstream-Thema geworden, ChatGPT, Claude, Gemini sind in allen Gesprächen. Als ich diesen Originalcode neu las, traf mich seine Mittelsparsamkeit.

Aber zwischen diesen zwei Extremen gab es PARRY (den paranoiden Chatbot, 1972), ALICE (der AIML-König mit 99.000 Kategorien, 1995), Jabberwacky (der erste, der ohne Regeln lernte, 1997) und Cleverbot (sein industrieller Nachfolger, 2008). Ich sehe sie als fünf radikal unterschiedliche Antworten auf dasselbe Problem: Eine Maschine zum Sprechen bringen.

Ich habe diese fünf Bots nach TypeScript portiert mit ihren Originaldaten, ELIZA-Skripte, PARRY-Wörterbücher, ALICEs AIML-Dateien. Jeder Port ist autonom, einsatzbereit und bis ins Detail dokumentiert. Mein Ziel mit dieser Arbeit geht über simples Ausführen hinaus: Ich will verstehen, wie sie funktionierten, warum sie Geschichte schrieben und was ihre jeweiligen Architekturen über die KI von gestern... und heute verraten.

bun run eliza    # Mit ELIZA reden (1966)
bun run parry    # Mit PARRY reden (1972)
bun run alice    # Mit ALICE reden (1995)
bun run jabber   # Mit Jabberwacky reden
bun run cleverbot # Mit Cleverbot reden
bun run meeting  # Automatisches ELIZA vs PARRY

Ich werde jeden Bot mit dir zerlegen, ihren Code anschauen und dann die Brücke zu modernen LLM schlagen via die Artikel über Luna Protocol.


ELIZA (1966): die Kunst glauben zu machen, man verstünde

Ich beginne mit der Ältesten und in meinen Augen durch ihre Einfachheit Beeindruckendsten. ELIZA hat keinerlei Intelligenz im modernen Sinne. Sie funktioniert nur mit Textmustern und etwas Satzpermutation.

Das Prinzip

Das DOCTOR-Skript (die Psychotherapeuten-Version) funktioniert mit einer Tabelle von Keywords, jedes assoziiert mit Dekompositions-Patterns und Reassembly-Rules. Hier eine typische Regel:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO ist das Keyword. 0 ist ein Dekompositions-Pattern, das sagt "fange alles Folgende ein" (wie ein Wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. ist die Reassembly-Regel. Das ist alles.

Wenn du "Hello, I'm sad today" sagst, ELIZA:

  1. Text in Großbuchstaben: HELLO I'M SAD TODAY
  2. Scannt jedes Wort gegen seine Keyword-Tabelle
  3. Findet HELLO → pusht es auf den Keyword-Stack
  4. Nimmt das Keyword mit höchster Priorität
  5. Probiert jedes Dekompositions-Pattern der Reihe nach
  6. Wenn es matcht, wählt die nächste Reassembly-Regel (Round-Robin)
  7. Ersetzt (1), (2) usw. durch die eingefangenen Teile

Aber der wirklich intelligente Teil sind die PRE-Rules. Schau dir das an:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Wenn ELIZA MY matcht, transformiert sie den Rest des Satzes (eingefangen von 0) via PRE-Rule und injiziert das Ergebnis erneut, als hätte der Nutzer gerade ein neues Keyword gesagt. Konkret:

Du sagst: "My mother hates me"
  → PRE transformiert: "YOUR MOTHER HATES YOU"
  → erneut injiziert als hättest du es gerade gesagt
  → matcht wahrscheinlich "YOU" → neue Antwort

Darum gibt ELIZA den Eindruck, den Unterschied zwischen "ich" und "du" zu verstehen: Ich sehe darin eine perfekt designte mechanische Transformation, die Verständnis simuliert.

Hier der komplette Fluss, vom Nutzertippen bis zur Antwort:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalisiert Interpunktion"]
    B --> C["splitUserInput()<br>zerlegt in Wörter"]
    C --> D["Baut Keyword-Stack<br>sortiert nach Priorität"]
    D --> E{"Stack nicht leer?"}
    E -->|"Ja"| F["Popt höchstpriores Keyword"]
    E -->|"Nein"| G{"Memory Recall?"}
    G -->|"Ja"| H["Ruft vergangene Nutzeraussage ab"]
    G -->|"Nein"| I["Fallback: zNONE-Regel"]
    I --> J["Gibt Antwort zurück"]
    H --> J
    F --> K["Matcht Dekompositions-Patterns"]
    K --> L{"Match gefunden?"}
    L -->|"Nein"| M{"Verlinktes Keyword?"}
    M -->|"Ja"| N["Pusht verlinktes Keyword auf Stack"]
    N --> E
    M -->|"Nein"| O["Gibt NOMATCH zurück"]
    O --> J
    L -->|"Ja"| P["Wählt nächstes Reassembly (Round-Robin)"]
    P --> Q{"Reassembly-Typ?"}
    Q -->|"PRE"| R["Transformiert Wörter (I→YOU)<br>pusht Link-Keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Springt zum nächsten Keyword"]
    S --> E
    Q -->|"Standard"| T["Expandiert (1), (2), (0)<br>in finale Antwort"]
    T --> J

Was sie glaubwürdig machte

Ich betrachte Weizenbaums Wahl als Geniestreich: Die rogerianische Psychotherapie. Dieser Ansatz besteht darin, die Patientenaussagen zu spiegeln ohne zu interpretieren. "Ich bin traurig" → "Du sagst, du bist traurig". Genau das kann ELIZA, und weil diese Therapietechnik anerkannt ist, findet niemand ihre Antwort seltsam. Wenn du den Bot testest, spürst du diesen Spiegeleffekt direkt.

Im TypeScript-Port

In meinem Port lade ich die .ela-Skripte (Original-S-Expression-Format), parse sie vollständig (inklusive Hollerith-Kodierung, diesem Stringformat der 60er) und führe denselben Zyklus aus: Uppercasing → Split → Keyword-Stack → Dekomposition → Reassembly → PRE/Transforms. Ich wollte originalgetreu bleiben bis ins Detail.

➡ Zum Quellcode


PARRY (1972): der erste Chatbot mit Emotionen

Sechs Jahre nach ELIZA schuf Kenneth Colby (Psychiater in Stanford) PARRY: Einen Chatbot, der einen Patienten mit paranoider Schizophrenie simuliert. Wo ELIZA wie ein leerer Spiegel funktionierte, bringt PARRY ein echtes internes Emotionsmodell mit. Dieser Kontrast hat mich beim nacheinander Portieren am meisten geprägt.

Das Emotionsmodell

PARRY hat vier kontinuierliche Variablen, die sich bei jedem Gesprächstakt entwickeln:

Variable Baseline Zerfall/Takt Beschreibung
ANGER 0 −1.0 Hostilität, Irritation
FEAR 0 −0.2 Paranoia (klingt langsam ab nach Delirbeginn)
MISTRUST 0 −0.05 Misstrauen (sehr langsam abklingend)
HURT 0 −0.5 Emotionaler Schmerz

Diese Werte steigen via emotionalen Sprüngen (ajump, fjump, hjump), getriggert von Inferenzregeln, und klingen natürlich zu ihren Baselines bei jedem Takt ab.

Das Glaubensnetz

PARRY hat 200+ Glaubenssätze in der bel-Datei gespeichert:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Jeder Glaube hat eine Kategorie (HUM = der Patient, HUM2 = die anderen, DOC = der Doktor, INT = das Verhör, INN = die Intentionen) und eine Stärke (0-5). Die Inferenzregeln (TH2, EMOTE, IF) propagieren Glauben untereinander:

  • TH2: Wenn ein Glaube A eine Schwelle übersteigt, verstärkt er sich und seine Konsequenzen steigen
  • EMOTE: Wenn ein Glaube eine Schwelle übersteigt, triggert er einen emotionalen Sprung (Anger/Fear/Hurt)
  • IF: Konditional, wenn A wahr ist, dann wird B auf einem gewissen Level wahr

Die Delir-Hierarchie (Flare-System)

Der faszinierendste Teil von PARRY ist sein "Flare"-System, eine Eskalationskette, die progressiv zum zentralen Delir führt:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Jedes Keyword triggert eine vorgeschriebene Antwort (via Pattern Matching), und wenn du dem Thema folgst, driftet PARRY progressiv zu seinem zentralen Verfolgungsdelir. Sobald ein Flare "getriggert" ist, wird er inaktiv (deadFlares) und PARRY geht zum nächsten, wie ein Gesprächspartner, der sein Thema vertieft. Ich mag diese Mechanik sehr, weil sie mit sehr wenig Code eine narrative Progression erzeugt.

Warum PARRY historisch ist

1972 bestand PARRY eine Turing-Test-Variante: Psychiater erhielten Transkripte von Gesprächen mit PARRY und mit echten paranoiden Patienten und konnten sie nicht verlässlich unterscheiden. Das erste Programm, dem das gelang, 40 Jahre vor LLM. Ich finde diesen Exploit bemerkenswert, wenn man die damaligen Mittel mit den heutigen vergleicht.

➡ Zum Quellcode


RFC 439: als ELIZA PARRY trifft

Am 18. September 1972 redeten zwei Programme zum ersten Mal in der Geschichte miteinander, buchstäblich: Es ist die erste Inter-KI-Konversation, je aufgezeichnet. ELIZA (laufend auf BBN in Boston) und PARRY (laufend in Stanford) konversierten via ARPANET, gerelayt von menschlichen Operatoren, die den Output des einen in den Input des anderen tippten. Die Transkription wurde als RFC 439 ("PARRY Encounters the DOCTOR") publiziert. Als ich dieses Dokument entdeckte, verstand ich, dass es einen Gründungsmoment für unser Feld markierte.

Hier ein Auszug, wie das aussah:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

Die Kohärenz dieses Austauschs überraschte mich bei der ersten Lektüre. ELIZA macht ihren Therapeutenjob: Umformulieren, Fragen, Erkunden. PARRY macht seinen paranoiden Patientenjob: Sich beschweren, anklagen, Misstrauen ausdrücken. Beide Programme bleiben dank der Kombination ihrer jeweiligen Mechanismen (ELIZA-Patterns + PARRY-Emotionsmodell), die zufällig ineinandergreifende Antworten produziert, perfekt in ihrer Rolle. Wenn du die Transkription ganz liest, siehst du, wie sehr diese zufällige Ausrichtung die Illusion eines intentionalen Dialogs erzeugt.

Du kannst diese Konversation mit meinem Repo reproduzieren:

bun run meeting

Meine Simulation startet 25 automatische Takte zwischen beiden Bots, mit zufälligem Startthema (Pferde, organisiertes Verbrechen, Emotionen...). Da ELIZA und PARRY beide nicht-deterministische Elemente mitbringen (ELIZA-Round-Robin, PARRY-Randomisierung), produziert jede Ausführung einen anderen Austausch. Ich liebe es, den Command mehrmals neu zu starten, um Trajektorien zu vergleichen.

Was mich an ELIZA vs PARRY trifft, ist die Begegnung zweier Programme, eines mit zustandsloser Architektur, das andere mit komplettem Emotionsmodell, die zusammen eine Konversation produzieren, die aussieht wie etwas Deliberates. Für 1972 finde ich das Ergebnis umwerfend.


ALICE (1995): Pattern Matching in großem Maßstab

ALICE (Artificial Linguistic Internet Computer Entity) wurde 1995 von Richard Wallace erschaffen und gewann dreimal den Loebner Prize (2000, 2001, 2004). Wo ELIZA ein paar hundert Regeln und PARRY ein paar tausend hatte, hat ALICE 99.524, verteilt in 66 AIML-Dateien. Als ich diesen Korpus zum ersten Mal öffnete, maß ich, was "großer Maßstab" damals hieß.

AIML: die Kategoriensprache

AIML (Artificial Intelligence Markup Language) ist ein XML-Format zum Definieren von Frage-Antwort-Paaren:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Aber ALICEs Power kommt aus Wildcards und SRAI (Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- äquivalent zu <srai><star/></srai> -->
  </template>
</category>

SRAI erlaubt ALICE, einen Input auf eine andere Kategorie umzuleiten und eine Reduktionskette zu erzeugen:

Input: "WHAT'S UP?"
  → Pattern "WHAT IS UP" → srai "HELLO"
    → Pattern "HELLO" → Template "Hi there!"

Das ist der Mechanismus, der ALICE ihre Flexibilität gibt: Statt eine Antwort für jede mögliche Formulierung zu schreiben, schreibst du eine kanonische Antwort und leitest die Variationen darauf um. Das Tiefenlimit ist 10: Darüber gibt ALICE auf, um Endlosschleifen zu vermeiden (das Kategoriendesign vermeidet sie sorgfältig, und dieses Sicherheitsnetz bleibt essenziell). Ich finde diese Designentscheidung elegant, weil sie Tausende Formulierungen mit sehr wenigen Regeln faktorisiert.

Wie ALICE Patterns matcht

Patterns werden nach Spezifität sortiert: Die mit den wenigsten Wildcards werden zuerst probiert. Die *- und _-Wildcards fangen beliebige Wortsequenzen ein. Die Engine kompiliert jedes Pattern zu Regex und iteriert die sortierten Kategorien bis zum Match.

// Unsere TypeScript-Implementierung, vereinfacht aber treu
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Warum ALICE die Loebner dominierte

99.524 Kategorien, das ist eine Zahl, die alles ändert. ELIZA gab die Illusion von Intelligenz, weil ihre wenigen Regeln gut für einen spezifischen Kontext (Therapie) designed waren. ALICE deckt so viele Themen ab, dass sie den Eindruck echter Allgemeinbildung gibt: Wissenschaft, Politik, Humor, Sport, Emotionen, alles dabei. Ich behalte aus diesem Ansatz eine Lektion, die ich noch anwende: Breite sorgfältige Abdeckung kompensiert fehlendes tiefes Reasoning.

➡ Zum Quellcode


Jabberwacky (1997) und Cleverbot (2008): der epistemologische Bruch

Alle vorherigen Bots teilen eine Hypothese: Man muss die Antworten schreiben. ELIZA hat ihre S-Expression-Regeln, PARRY seine selektiven Patterns, ALICE ihre AIML-Kategorien. Rollo Carpenter ging mit einer radikalen Frage total dagegen: Was, wenn vergangene Gespräche die Antworten liefern ? Ich finde diese Umkehr mutig, weil sie jedes manuelle Regelschreiben aufgibt.

Die Idee

Jabberwacky (um 1997 gestartet, 2008 zu Cleverbot geworden) beruht vollständig auf der ganzen gespeicherten Gesprächshistorie in einem flachen Transkript, und wenn jemand mit ihm redet, sucht es in dieser Historie den ähnlichsten Moment und wiederverwendet, was danach gesagt wurde:

Nutzer: "hello"
  ↓
Suchen: Hat schon mal jemand "hello" gesagt ?
  ↓
Ja, in Session #3, Zeile 14, hat jemand "hello" gesagt und der Bot antwortete "hi there!"
  ↓
Antworten: "hi there!"

Du findest hier nur ein riesiges Archiv von Dingen, die Leute sich sagten, im passenden Moment wiederverwendet. Ich sehe darin die Definition von Emergenz: Konversationsverhalten entsteht aus einer Anhäufung menschlicher Spuren.

Die TypeScript-Implementierung

Mein TypeScript-Port reproduziert exakt diese Architektur:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 Seed-Zeilen + Historie"]
    B --> C["withReplies()<br>extrahiert Paare<br>(Zeile → Reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>Kontext vor dieser Zeile)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K Kandidaten sortiert"]
    I --> J{"pickReply()<br>Roulette-Wheel<br>Selection"}
    J -->|"Pick"| K["Reply = reply.text<br>des Gewinnerpaars"]
    J -->|"Keiner"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Anhängen ans Transkript<br>save() → JSON"]
    L --> M

Hier das Scoring-Herz, mit meiner eigenen Heuristik inspiriert von Cleverbots öffentlichen Beschreibungen:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): Ähnlichkeit zwischen Nutzereingabe und historischer Zeile
  • contextFit (0.25): Ähnlichkeit zwischen aktuellem Gespräch und dem, was der historischen Zeile vorausging
  • recencyBonus (0.10): Aktuelle Erinnerungen zählen etwas mehr (die Bot-Persönlichkeit driftet mit der Zeit)

Der Pick ist probabilistisch (Roulette-Wheel-Selection): Der beste Kandidat gewinnt öfter, mit einem Variabilitätsanteil, der Vielfalt gibt. Ich schätze diese Wahl, weil sie Gespräche weniger vorhersehbar macht, wenn du den Bot mehrmals testest.

Cleverbot: die zwei dokumentierten Innovationen

Cleverbot fügt zwei Mechanismen zum Jabberwacky-Basiskonzept hinzu:

  1. Multi-Personen-Lernen: Millionen Nutzer tragen zum selben geteilten Transkript bei. Eine aus der Historie gezogene Antwort kann von einer völlig anderen Stimme kommen als die der laufenden Konversation, was erklärt, warum Cleverbot plötzlich die Persönlichkeit wechselt.

  2. Verzögertes Lernen: Was du Cleverbot während einer Session sagst, ist während derselben Session NICHT matchbar. Neue Zeilen werden pending markiert und werden erst nach einer "Konsolidierung" zwischen Sessions matchbar, was erklärt, warum du Cleverbot keinen Fakt beibringen und in derselben Konversation wiederverwenden kannst.

// Cleverbot : Aktuelle Zeilen sind bis zur Konsolidierung unsichtbar
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() wird beim Start aufgerufen, nicht während der Session

Mein TypeScript-Port implementiert beide Verhaltensweisen: Zeilen haben ein consolidated-Flag, und jede REPL-Session beginnt mit einer Konsolidierung wartender Zeilen.

➡ Zum Quellcode


Analyse des TypeScript-Ports: eine gemeinsame Architektur designen

Diese fünf Bots in derselben Sprache zu bauen konfrontierte mich mit einer interessanten Frage: Ist es möglich, Code zwischen so unterschiedlichen Architekturen zu faktorisieren ?

Meine Antwort nach dem Portieren aller: Sehr wenig. Jeder Bot hat eine unterschiedliche fundamentale Schleife:

Bot Hauptschleife Daten Lernen
ELIZA Keyword-Stack → Dekomposition → Reassembly .ela-Skripte in S-Expressions Keines
PARRY Tokenisierung → selektive Patterns / Flares / Keywords / Inferenzen 58 PDP-10-Dateien (Wörterbücher, Glauben, Regeln) Keines
ALICE Sortierte Patterns → Regex → AIML-Template → rekursives SRAI 66 XML-AIML-Dateien Keines
Jabberwacky Ähnlichkeit → Kontext → Recency → gewichteter Pick JSON-Transkript (wächst mit Nutzung) Kontinuierlich
Cleverbot Wie Jabberwacky + pending/consolidated + Personas JSON-Transkript + Multi-Persona-Seeds Verzögert (zwischen Sessions)

Was sie teilen, ist das CLI-Interface und die TypeScript-Infrastruktur (Biome für Lint, tsx für Ausführung). Der Rest ist architekturspezifisch.

Gemeinsame Designentscheidungen

1. Treue zu den Originaldaten. Für ELIZA, PARRY und ALICE nutze ich die Originaldateien, ELIZA-Skripte 2021 in den Weizenbaum-Archiven wiedergefunden, originaler PARRY-PDP-10-Code (58 Dateien), AIML Free ALICE v1.6. Ich bewahre sie in ihrer Originalform. Die Bots verhalten sich wie die Originale, weil sie dieselben Daten nutzen.

2. Clean-Room für proprietäre Teile. Jabberwacky und Cleverbot stellen einen anderen Fall: Ihr Quellcode wurde nie publiziert (Existor/Rollo Carpenter hielt ihn proprietär). Meine Ports sind also Clean-Room-Reimplementierungen, nur aus öffentlichen Verhaltensbeschreibungen gebaut. Ich habe keine Codezeile und keine proprietären Daten kopiert.

3. Minimale Abhängigkeiten. Die einzige echte Voraussetzung ist TypeScript. ALICE nutzt dom-js, um das XML der AIML-Dateien zu parsen (66 Dateien, 99.524 Kategorien, hausgemachtes XML-Parsing wäre Zeitverschwendung). Alles andere ist Vanilla-TypeScript. Ich traf diese Wahl, um jeden Bot lesbar und leicht startbar zu halten, wenn du das Repo klonst.


Von symbolischen Chatbots zu LLM: der konzeptuelle Sprung

Die fünf Bots, die ich dir gerade vorgestellt habe, teilen ein fundamentales Merkmal: Sie sind symbolisch. Ihr "Wissen" beruht vollständig auf expliziten Symbolen: Textmuster, Regeltabellen, XML-Kategorien, Transkriptzeilen. Sie funktionieren ganz ohne numerische Sprachrepräsentation.

Sie teilen also dieselbe Glasdecke: Sie antworten nur auf explizit Vorgesehenes oder Aufgezeichnetes. ELIZA ist verloren, wenn du den Therapierahmen verlässt. PARRY redet nur über seine paranoiden Themen. ALICE lernt nichts aus ihren Gesprächen. Jabberwacky antwortet nur mit schon ausgesprochenen Repliken. Wenn du diese Grenzen testest, verstehst du schnell, warum diese Generation cappte.

LLM (Large Language Models) durchbrechen diese Decke mit radikalem Paradigmenwechsel: Statt Symbole zu manipulieren, konvertieren sie Sprache zu Zahlen und lernen statistische Beziehungen zwischen diesen Zahlen. Sie generieren jeden Token on the fly durch Wahrscheinlichkeitsberechnung, aus in ihren Gewichten kodiertem Wissen statt vorgeschriebener Antworten. Ich finde diesen Wechsel faszinierend, weil er die Logik der vorherigen Bots komplett umkehrt. Schauen wir kurz, wie das funktioniert.

1. Tokenisierung

Der erste Schritt ist, Text in Tokens zu zerschneiden, Einheiten kleiner als Wörter, aber größer als Zeichen:

"Ich verstehe nicht"
  → ["Ich", " verstehe", " nicht"]

Jeder Token hat eine numerische ID in einem Vokabular (typischerweise 32.000 bis 128.000 Tokens für aktuelle Modelle). Diese Fragmentierung erlaubt dem Modell, nie gesehene Wörter zu handhaben, indem es sie in bekannte Subwörter zerlegt.

2. Embeddings

Jede Token-ID wird in einen Vektor konvertiert, ein Float-Array (typischerweise 4096 Dimensionen für ein mittelgroßes Modell). Dieser Vektor ist ein Embedding, das die Tokenbedeutung in einem mathematischen Raum kodiert, wo semantisch nahe Tokens nahe Vektoren haben:

Vektor("König")  − Vektor("Mann") + Vektor("Frau")  ≈  Vektor("Königin")

Diese Eigenschaft emergiert aus dem Training, ohne explizite Programmierung von mir oder den Forschern. Ich sehe darin eine direkte Folge davon, wie Wörter in ähnlichen Kontexten erscheinen.

3. Attention

Der Attention-Mechanismus (2017 vom Paper "Attention is All You Need" eingeführt) machte LLM erst möglich. Für jeden Token berechnet Attention, welche anderen Tokens im Satz wichtig sind, um diesen zu verstehen:

"Die Bank hat meinen Kredit abgelehnt."
       ↑
Token "Bank" schaut: "abgelehnt", "Kredit" → versteht dass es ein Finanzinstitut ist

"Ich gehe auf der Bank spazieren."
       ↑
Token "Bank" schaut: "spazieren", "auf" → versteht dass es eine Sitzbank ist

Attention erlaubt dem Modell, Kontext zu erfassen: Jeder Token wird je nach umgebenden verstanden, in voll relationaler Lesart. Diese Kontextsensibilität half mir am meisten zu verstehen, warum LLM fixe Patterns übertreffen.

4. Next-Token-Prediction

LLM-Training beruht auf trügerischer Einfachheit: Du zeigst ihm einen Text, versteckst den letzten Token und bittest ihn, ihn vorherzusagen. Dann wiederholst du das milliardenfach. Ich mag diese Idee, weil eine sehr simple Lernregel sehr reiche Fähigkeiten produziert.

Input:  "Ich verstehe"
Versteckt:  "nicht"
Modellvorhersage: "nicht" (Wahrscheinlichkeit 0.87), "nichts" (0.05), "nie" (0.02)...

Das Ziel ist, die Wahrscheinlichkeit des wahren Tokens an jeder Position zu maximieren. Das nennt man Next-Token-Prediction. Während des Trainings justiert das Modell seine Milliarden Parameter, um den Vorhersagefehler über Terabytes Text zu minimieren.

Zum Inference-Zeitpunkt (wenn du mit ihm redest) generiert das Modell einen Token nach dem anderen in Schleife:

Token 1: "Ich"    (Input: "Erzähl mir von dir.")
Token 2: "bin"  (Input: "Erzähl mir von dir. Ich")
Token 3: "ein"    (Input: "Erzähl mir von dir. Ich bin")
Token 4: "Chatbot" (Input: "Erzähl mir von dir. Ich bin ein")
...

Jeder Token wird nach seiner Wahrscheinlichkeit gesampelt (Temperatur, Top-k, Top-p kontrollieren den "Kreativitäts"-Grad). Und das ist alles. Milliarden Parameter, die diese Operation tausendfach wiederholen. Ich finde es schwindelerregend, dass ein so repetitiver Mechanismus so variierte Antworten produziert.

Was sich fundamental ändert

Aspekt Symbolische Bots (ELIZA, PARRY, ALICE) Moderne LLM
Repräsentation Explizite Wörter und Regeln Numerische Vektoren (Embeddings)
Generierung Selektion aus vorgeschriebenen Antworten Probabilistische Token-für-Token-Prediction
Wissen In Regeldateien gespeichert In Netzgewichten kodiert
Lernen Manuell (Regelschreiben) Automatisch (Korpus-Training)
Robustheit Null außerhalb vorgesehener Muster Generalisiert auf nie gesehene Inputs
Interpretierbarkeit Perfekt (Regeln lesbar) Begrenzt (Blackbox)

Klassische Chatbots sind transparent aber fragil. Ein LLM ist robust aber opak. Ich sehe sie heute als komplementäre Tools für verschiedene Bedürfnisse, und ich wähle das eine oder andere je nach Kontrolllevel, das du über Antworten behalten willst.

Wenn du die LLM-Innereien vertiefen willst, empfehle ich dir dieses Video als exzellente Ressource:

How LLMs Work, YouTube

Luna Protocol: die moderne Synthese

Die Artikel über Luna Protocol (Links unten) repräsentieren in meinen Augen die vollendetste Synthese all dessen, was ich dir gerade gezeigt habe: Ein moderner Discord-Bot, der ein lokales LLM mit sophistiziertem Verhaltenssystem kombiniert, gebaut auf den Lehren von 60 Jahren Konversations-KI. Das Projekt erlaubte mir, meine historischen Ports mit meinen aktuellen Experimenten zu verbinden.

Luna Protocol: Ich habe einen autonomen Discord-Bot gebaut

Dieser Artikel detailliert die komplette Architektur eines LLM-basierten Discord-Bots:

  • Prioritäres Triggersystem (Mention > DM > Name > Keyword > Follow-up > Random)
  • Menschliche Verhaltensweisen: Variable Konzentration, Tippfehler, Zögern (15%), Vergessen (3%), Themenmüdigkeit
  • Schlafzeiten: Der Bot schläft, verlangsamt oder ignoriert je nach Uhrzeit
  • TTS-Pipeline: Sprachsynthese via Piper + ffmpeg → Discord-Sprachnachrichten
  • Echtzeit-Streaming: Das LLM emittiert Tokens eines nach dem anderen auf einem typisierten Eventbus

Was diesen Artikel mit den historischen Chatbots verbindet, ist dieselbe Suche: Glauben machen, man rede mit einer Person. ELIZA nutzte Textspiegel. PARRY nutzte ein Emotionsmodell. ALICE nutzte 99k Kategorien. Luna Protocol nutzt ein feingetuntes LLM + ein Verhaltenssystem, das menschliche Imperfektionen simuliert. Ich finde in dieser Filiation dieselbe Anforderung wieder: Die Illusion einer Präsenz auf der anderen Bildschirmseite erzeugen.

Luna Protocol: warum ich ein 1,5B-Modell feingetunt habe

Der zweite Artikel erkundet Fine-Tuning und Few-Shot-Priming. Meine zentrale Entdeckung: Ein kleineres Modell (1,5B) trainiert auf weniger Daten (50k Samples) übertrifft ein größeres Modell (3B), wenn man es mit Few-Shot-Beispielen korrekt anstößt. Ich war wirklich überrascht von diesem Ergebnis, als ich es maß.

Eine Lektion, die direkt mit den historischen Chatbots resoniert:

  • ELIZA zeigte, dass man mit wenigen gut designten Regeln Verständnis simulieren kann
  • ALICE zeigte, dass man mit 99k Kategorien Allgemeinbildung simulieren kann
  • Luna Protocol zeigt, dass ein kleines LLM mit gutem Fine-Tuning und 5 Few-Shot-Beispielen einen Menschen simulieren kann

Die Technik ändert sich, das Prinzip bleibt: Datenqualität und Systempräzision zählen mehr als rohe Größe. Eine Überzeugung, die ich in meinen eigenen Experimenten verteidige.


Fazit: drei Dinge zum Mitnehmen

1. Konversations-KI begann nicht mit ChatGPT. ELIZA ist 60 Jahre alt. PARRY bestand den Turing-Test 1972. ALICE gewann Loebner dreimal. Jabberwacky legte die Transkript-Lernbasics, die Cleverbot in großem Maßstab industrialisierte. Jeder Ansatz brachte ein Puzzleteil. Das bestätigten mir meine Ports.

2. Mehr Daten ≠ intelligenter. Jabberwackys Transkript hat keine Regeln. ALICEs 99k Kategorien lernen nicht. Luna Protocols Fine-Tuning auf 50k Samples übertrifft das 3B-Modell. Die konventionelle Weisheit sagt "größer ist besser", die Chatbot-Geschichte zeigt, dass Architektur und Design so viel zählen wie Größe. Ich verteidige diese Idee in jeder meiner technischen Entscheidungen.

3. Das Problem ist seit 60 Jahren dasselbe. Wie lässt man einen Menschen glauben, er rede mit einem anderen Menschen? ELIZA antwortete mit Textspiegeln. PARRY mit simulierter Wut. ALICE mit Fakten. Luna Protocol mit einem LLM, das schläft und Tippfehler macht. Die Lösung ändert sich, das Bedürfnis bleibt. Diese Kontinuität gibt mir Lust weiter zu erkunden.

Das Repo ist Open Source, du kannst es klonen, jeden Bot starten und selbst sehen, wie 60 Jahre Konversations-KI in ein einziges TypeScript-Repo passen. Ich hoffe, du hast so viel Spaß wie ich, sie zum Sprechen zu bringen.

Ressource Link
GitHub-Repo fox3000foxy/chatbots
Luna Protocol, Bot-Architektur Artikel lesen
Luna Protocol, Fine-Tuning Few-Shot Artikel lesen
Originale ELIZA-Skripte anthay/ELIZA
Originaler PARRY-Quellcode lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
Originale RFC 439 PARRY Encounters the DOCTOR
Exzellente Erklärung wie LLM funktionieren https://www.youtube.com/watch?v=YmLp8qe87A0

От ELIZA до LLM : 60 лет разговорного ИИ, пересобранного на TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- пять радикально разных архитектур одной задачи, портированных на TypeScript с оригинальными данными. От 1966 до современных LLM : как разговорный ИИ научился говорить и чему репозиторий чат-ботов учит про 60 лет исследований.

От ELIZA до LLM : 60 лет разговорного ИИ, пересобранного на TypeScript

В 1966 Джозеф Вейценбаум писал 420 строк MAD-SLIP на IBM 7094, чтобы создать первого чат-бота истории. Программа называлась ELIZA, и она симулировала роджерианского психотерапевта базовыми паттернами и перестановками фраз. Шесть десятилетий спустя разговорный ИИ стал мейнстримом -- ChatGPT, Claude, Gemini во всех разговорах. Когда я перечитал этот исходный код, меня поразила его экономия средств.

Но между этими крайностями были PARRY (параноидальный чат-бот, 1972), ALICE (король AIML на 99 000 категорий, 1995), Jabberwacky (первый, учащийся без правил, 1997), и Cleverbot (его индустриальный преемник, 2008). Я вижу их как пять радикально разных ответов на одну задачу : заставить машину говорить.

Я портировал этих пятерых ботов на TypeScript с их оригинальными данными -- скрипты ELIZA, словари PARRY, AIML-файлы ALICE. Каждый порт автономен, готов к использованию и задокументирован в малейших деталях. Моя цель с этой работой шире простого запуска : хочу понять, как они работали, почему вошли в историю и что их архитектуры говорят об ИИ вчерашнем... и сегодняшнем.

bun run eliza    # Поговори с ELIZA (1966)
bun run parry    # Поговори с PARRY (1972)
bun run alice    # Поговори с ALICE (1995)
bun run jabber   # Поговори с Jabberwacky
bun run cleverbot # Поговори с Cleverbot
bun run meeting  # ELIZA против PARRY автоматически

Я разберу каждого бота с тобой, посмотрю их код, а затем наведу мост к современным LLM через статьи про Luna Protocol.


ELIZA (1966) : искусство заставить поверить, что понимаешь

Начну с самой древней, а на мой взгляд самой впечатляющей простотой. У ELIZA нет интеллекта в современном смысле. Она работает только с текстовыми паттернами и небольшой перестановкой.

Принцип

Скрипт DOCTOR (психотерапевтическая версия) работает с таблицей ключевых слов, каждому сопоставлены паттерны декомпозиции и правила сборки. Вот типичное правило :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO это ключевое слово. 0 это паттерн декомпозиции, говорящий "захвати всё, что следует" (как wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. это правило сборки. Всё.

Когда говоришь "Hello, I'm sad today", ELIZA :

  1. Переводит текст в верхний регистр : HELLO I'M SAD TODAY
  2. Сканирует каждое слово по таблице ключевых слов
  3. Находит HELLO → кладёт на стек ключевых слов
  4. Берёт ключевое слово с высшим приоритетом
  5. Пробует каждый паттерн декомпозиции по порядку
  6. Если совпало, выбирает следующее правило сборки (round-robin)
  7. Заменяет (1), (2) и т.д. захваченными частями

Но по-настоящему умная часть это PRE-правила. Смотри :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Когда ELIZA совпадает на MY, она трансформирует остаток фразы (захваченный через 0) через PRE-правило и реинжектит результат, будто пользователь только что сказал новое ключевое слово. Конкретно :

Ты говоришь : "My mother hates me"
  → PRE трансформирует : "YOUR MOTHER HATES YOU"
  → реинжектится, будто ты только что это сказал
  → вероятно совпадает "YOU" → новый ответ

Поэтому ELIZA создаёт впечатление понимания разницы между "я" и "ты" : я вижу здесь идеально сконструированную механическую трансформацию, симулирующую понимание.

Вот полный поток, от нажатия пользователя до ответа :

flowchart TD
    A["Ввод пользователя :<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>нормализует пунктуацию"]
    B --> C["splitUserInput()<br>режет на слова"]
    C --> D["Строит стек ключевых слов<br>упорядоченный по приоритету"]
    D --> E{"Стек непуст?"}
    E -->|"Да"| F["Берёт ключевое слово высшего приоритета"]
    E -->|"Нет"| G{"Вспомнить из памяти?"}
    G -->|"Да"| H["Вспомнить прошлое высказывание пользователя"]
    G -->|"Нет"| I["Фолбэк : правило zNONE"]
    I --> J["Вернуть ответ"]
    H --> J
    F --> K["Совпадение паттернов декомпозиции"]
    K --> L{"Совпадение найдено?"}
    L -->|"Нет"| M{"Связанное ключевое слово?"}
    M -->|"Да"| N["Кладёт связанное ключевое слово на стек"]
    N --> E
    M -->|"Нет"| O["Вернуть NOMATCH"]
    O --> J
    L -->|"Да"| P["Выбирает следующую сборку (round-robin)"]
    P --> Q{"Тип сборки?"}
    Q -->|"PRE"| R["Трансформирует слова (I→YOU)<br>кладёт связное ключевое слово"]
    R --> N
    Q -->|"NEWKEY"| S["Переходит к следующему ключевому слову"]
    S --> E
    Q -->|"Standard"| T["Разворачивает (1), (2), (0)<br>в финальный ответ"]
    T --> J

Что делало её убедительной

Считаю выбор Вейценбаума гениальным ходом : роджерианская психотерапия. Этот подход состоит в отражении слов пациента без интерпретации. "Мне грустно" → "Вы говорите, что вам грустно". Это ровно то, что ELIZA умеет, а раз эта терапевтическая техника признана, никого не удивляет её ответ. Когда тестируешь бота, прямо чувствуешь этот зеркальный эффект.

В порте TypeScript

В моём порте я гружу скрипты .ela (оригинальный S-expression формат), полностью их парсю (включая кодировку Hollerith, строковый формат 60-х) и исполняю тот же цикл : uppercasing → split → стек ключевых слов → декомпозиция → сборка → PRE/трансформы. Я хотел остаться верным оригинальному поведению вплоть до деталей.

➡ Смотри исходник


PARRY (1972) : первый чат-бот с эмоциями

Шесть лет спустя после ELIZA Кеннет Колби (психиатр в Стэнфорде) создал PARRY : чат-бот, симулирующий пациента с параноидальной шизофренией. Там, где ELIZA работала пустым зеркалом, PARRY везёт настоящую внутреннюю эмоциональную модель. Этот контраст поразил меня сильнее всего, когда портировал их одного за другим.

Эмоциональная модель

У PARRY четыре непрерывные переменные, эволюционирующие каждый ход разговора :

Переменная Базовая линия Спад/ход Описание
ANGER 0 −1.0 Враждебность, раздражение
FEAR 0 −0.2 Паранойя (медленно спадает после начала бреда)
MISTRUST 0 −0.05 Недоверчивость (очень медленно спадает)
HURT 0 −0.5 Эмоциональная боль

Эти значения растут через эмоциональные прыжки (ajump, fjump, hjump), триггеримые правилами вывода, и естественно спадают к своим базовым линиям каждый ход.

Сеть убеждений

У PARRY 200+ убеждений, хранящихся в файле bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

У каждого убеждения есть категория (HUM = пациент, HUM2 = остальные, DOC = доктор, INT = допрос, INN = намерения) и сила (0-5). Правила вывода (TH2, EMOTE, IF) распространяют убеждения между ними :

  • TH2 : если убеждение A превышает порог, оно усиливается, а его следствия растут
  • EMOTE : если убеждение превышает порог, оно триггерит эмоциональный прыжок (anger/fear/hurt)
  • IF : условное -- если A истинно, то B становится истинным на определённом уровне

Иерархия бредов (flare-система)

Самая захватывающая часть PARRY это его система "flares" -- цепочка эскалации, постепенно ведущая к центральному бреду :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Каждое ключевое слово триггерит предписанный ответ (через паттерн-матчинг), а если следуешь теме, PARRY постепенно дрейфует к своему центральному бреду преследования. Как только flare "сработал", он становится неактивным (deadFlares), и PARRY переходит к следующему, как собеседник, копающий свою тему. Очень люблю эту механику, потому что она создаёт нарративную прогрессию совсем малым кодом.

Почему PARRY историчен

В 1972 PARRY прошёл вариант теста Тьюринга : психиатры получили транскрипты разговоров с PARRY и с настоящими параноидальными пациентами и не смогли их надёжно различить. Это первая программа, которой это удалось, за 40 лет до LLM. Считаю этот подвиг замечательным, если сравнить средства эпохи с сегодняшними.

➡ Смотри исходник


RFC 439 : когда ELIZA встречает PARRY

18 сентября 1972 две программы впервые в истории поговорили друг с другом, буквально : это первый меж-ИИ разговор, когда-либо записанный. ELIZA (крутившаяся на BBN в Бостоне) и PARRY (крутившийся в Стэнфорде) общались через ARPANET, ретранслируемые людьми-операторами, вбивавшими вывод одного на вход другого. Транскрипт опубликован как RFC 439 ("PARRY Encounters the DOCTOR"). Когда я открыл этот документ, я понял, что он маркирует учредительный момент нашей области.

Вот выдержка, как это выглядело :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

Связность этого обмена удивила меня при первом чтении. ELIZA делает свою терапевтическую работу : переформулировать, спрашивать, исследовать. PARRY делает свою работу параноидального пациента : жаловаться, обвинять, выражать недоверчивость. Обе программы идеально держатся в ролях благодаря комбинации их механизмов (паттерны ELIZA + эмоциональная модель PARRY), дающей случайно стыкующиеся ответы. Когда читаешь транскрипт целиком, видишь, насколько это случайное выравнивание создаёт иллюзию намеренного диалога.

Можешь воспроизвести этот разговор с моим репозиторием :

bun run meeting

Моя симуляция запускает 25 автоматических ходов между двумя ботами со случайной стартовой темой (лошади, оргпреступность, эмоции...). Раз ELIZA и PARRY оба везут недетерминированные элементы (round-robin ELIZA, рандомизация PARRY), каждый запуск даёт разный обмен. Обожаю перезапускать команду несколько раз, чтобы сравнить траектории.

Что поражает меня в ELIZA против PARRY, так это встреча двух программ, одной с архитектурой без внутреннего состояния, другой с полной эмоциональной моделью, вместе дающих разговор, похожий на что-то осмысленное. Для 1972 считаю результат ошеломляющим.


ALICE (1995) : паттерн-матчинг в большом масштабе

ALICE (Artificial Linguistic Internet Computer Entity) создана Ричардом Уоллесом в 1995 и выиграла премию Лёбнера трижды (2000, 2001, 2004). Там, где у ELIZA было несколько сотен правил, а у PARRY несколько тысяч, у ALICE их 99 524, разбитых на 66 AIML-файлов. Когда я впервые открыл этот корпус, я измерил, что "большой масштаб" значил в ту эпоху.

AIML : язык категорий

AIML (Artificial Intelligence Markup Language) это XML-формат для определения пар вопрос-ответ :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Но мощь ALICE идёт из wildcard и SRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- эквивалент <srai><star/></srai> -->
  </template>
</category>

SRAI позволяет ALICE перенаправлять вход в другую категорию, создавая цепочку редукции :

Вход : "WHAT'S UP?"
  → паттерн "WHAT IS UP" → srai "HELLO"
    → паттерн "HELLO" → template "Hi there!"

Это механизм, дающий ALICE гибкость : вместо написания ответа на каждую возможную формулировку пишешь канонический ответ и перенаправляешь вариации на него. Лимит глубины 10 : дальше ALICE сдаётся, чтобы избегать бесконечных петель (дизайн категорий их тщательно избегает, и эта страховка остаётся существенной). Считаю этот дизайнерский выбор элегантным, потому что он факторизует тысячи формулировок совсем малым числом правил.

Как ALICE матчит паттерны

Паттерны сортируются по специфичности : с наименьшим числом wildcard пробуются первыми. Wildcard * и _ захватывают любую последовательность слов. Движок компилирует каждый паттерн в regex, затем итерирует отсортированные категории, пока не найдёт совпадение.

// Наша реализация TypeScript -- упрощённая, но верная
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Почему ALICE доминировала в Лёбнерах

99 524 категории это число, меняющее всё. ELIZA давала иллюзию интеллекта, потому что её немногие правила были хорошо спроектированы под конкретный контекст (терапия). ALICE покрывает столько тем, что кажется обладающей настоящей общей культурой : науки, политика, юмор, спорт, эмоции, всё здесь. Выношу из этого подхода урок, применяемый до сих пор : широкое и тщательное покрытие компенсирует отсутствие глубокого рассуждения.

➡ Смотри исходник


Jabberwacky (1997) и Cleverbot (2008) : эпистемологический разрыв

Все предыдущие боты разделяют гипотезу : ответы надо писать. У ELIZA свои S-expression правила, у PARRY свои селективные паттерны, у ALICE свои AIML-категории. Ролло Карпентер пошёл в полный контрпивот с радикальным вопросом : а если прошлые разговоры сами дадут ответы? Считаю этот разворот дерзким, потому что он бросает всякое ручное написание правил.

Идея

Jabberwacky (запущен около 1997, ставший Cleverbot в 2008) целиком опирается на всю историю разговоров, хранящуюся в плоском транскрипте, и когда кто-то с ним говорит, он ищет в этой истории самый похожий момент и переиспользует сказанное после :

Пользователь : "hello"
  ↓
Ищем : говорил ли кто-то уже "hello" раньше?
  ↓
Да, в сессии #3, строка 14, кто-то сказал "hello", а бот ответил "hi there!"
  ↓
Отвечаем : "hi there!"

Ты найдёшь здесь лишь гигантский архив вещей, сказанных людьми друг другу, переиспользованный в подходящий момент. Вижу здесь само определение эмерджентности : разговорное поведение рождается из накопления человеческих следов.

Реализация TypeScript

Мой порт TypeScript воспроизводит ровно эту архитектуру :

flowchart TD
    A["Ввод пользователя :<br>'hello'"] --> B["TranscriptStore<br>332 сид-строки + история"]
    B --> C["withReplies()<br>извлекает пары<br>(строка → ответ)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>контекст до этой строки)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Топ K отсортированных кандидатов"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>выигравшей пары"]
    J -->|"Нет"| L["Фолбэк : 'I have no idea<br>what to say to that yet.'"]
    K --> M["Дописать в транскрипт<br>save() → JSON"]
    L --> M

Вот сердце скоринга, с моей эвристикой, вдохновлённой публичными описаниями Cleverbot :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : схожесть между вводом пользователя и исторической строкой
  • contextFit (0.25) : схожесть между недавним разговором и тем, что предшествовало исторической строке
  • recencyBonus (0.10) : свежие воспоминания считаются чуть больше (личность бота дрейфует со временем)

Выбор вероятностный (roulette-wheel selection) : лучший кандидат выигрывает чаще, с долей вариативности, дающей разнообразие. Ценю этот выбор, потому что он делает разговоры менее предсказуемыми, когда тестируешь бота несколько раз.

Cleverbot : две задокументированные инновации

Cleverbot добавляет два механизма к базовой концепции Jabberwacky :

  1. Мультиперсонное обучение : миллионы пользователей вносят вклад в общий расшаренный транскрипт. Ответ, вытащенный из истории, может прийти от голоса совершенно отличного от голоса текущего разговора -- что объясняет, почему Cleverbot вдруг меняет личность.

  2. Отложенное обучение : сказанное тобой Cleverbot во время сессии НЕ доступно для совпадения в той же сессии. Новые строки помечаются pending и становятся matchable только после "консолидации" между сессиями -- что объясняет, почему нельзя научить Cleverbot факту и переиспользовать в том же разговоре.

// Cleverbot : недавние строки невидимы до консолидации
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() зовётся на старте, а не во время сессии

Мой порт TypeScript реализует оба поведения : у строк есть флаг consolidated, а каждая REPL-сессия начинается с консолидации ожидающих строк.

➡ Смотри исходник


Разбор порта TypeScript : проектируем общую архитектуру

Постройка этих пяти ботов на одном языке столкнула меня с интересным вопросом : можно ли факторизовать код между столь разными архитектурами?

Мой ответ после портирования всех : очень мало. У каждого бота свой фундаментальный цикл :

Бот Главный цикл Данные Обучение
ELIZA Стек ключевых слов → декомпозиция → сборка Скрипты .ela в S-expressions Никакого
PARRY Токенизация → селективные паттерны / flares / keywords / выводы 58 файлов PDP-10 (словари, убеждения, правила) Никакого
ALICE Отсортированные паттерны → regex → шаблон AIML → рекурсивный SRAI 66 AIML XML-файлов Никакого
Jabberwacky Схожесть → контекст → recency → взвешенный выбор Транскрипт JSON (растёт с использованием) Непрерывное
Cleverbot То же, что Jabberwacky + pending/consolidated + персоны Транскрипт JSON + мультиперсонные сиды Отложенное (между сессиями)

Что они разделяют, так это CLI-интерфейс и TypeScript-инфраструктуру (biome для линта, tsx для запуска). Остальное специфично каждой архитектуре.

Общие дизайнерские выборы

1. Верность оригинальным данным. Для ELIZA, PARRY и ALICE я использую оригинальные файлы, скрипты ELIZA, найденные в архивах Вейценбаума в 2021, оригинальный код PARRY с PDP-10 (58 файлов), AIML Free ALICE v1.6. Сохраняю их в оригинальной форме. Боты ведут себя как оригиналы, потому что используют те же данные.

2. Clean-room для проприетарных частей. Jabberwacky и Cleverbot ставят другой случай : их исходник никогда не публиковался (Existor/Rollo Carpenter держал проприетарным). Мои порты, стало быть, clean-room реимплементации, построенные только на публичных описаниях поведения. Я не скопировал ни строки кода, ни проприетарных данных.

3. Минимальные зависимости. Единственный настоящий пререквизит это TypeScript. ALICE использует dom-js для парсинга XML AIML-файлов (66 файлов, 99 524 категории, самопальный XML-парсинг был бы потерей времени). Всё остальное на ванильном TypeScript. Я сделал этот выбор, чтобы каждый бот оставался читаемым и легко запускаемым при клоне репозитория.


От символьных чат-ботов к LLM : концептуальный прыжок

Пять ботов, что я только представил, разделяют фундаментальную характеристику : они символьные. Их "знания" целиком опираются на явные символы : текстовые паттерны, таблицы правил, XML-категории, строки транскрипта. Они работают полностью без числового представления языка.

Они разделяют и тот же стеклянный потолок : они отвечают только на то, что явно предусмотрено или записано. ELIZA теряется, выйдя из терапевтических рамок. PARRY говорит только о своих параноидальных темах. ALICE ничему не учится из своих разговоров. Jabberwacky отвечает только уже произнесёнными репликами. Когда тестируешь эти лимиты, быстро понимаешь, почему это поколение упёрлось в потолок.

LLM (Large Language Models) пробивают этот потолок, радикально меняя парадигму : вместо манипуляции символами они конвертируют язык в числа и учат статистические отношения между этими числами. Они генерируют каждый токен на лету, вычисляя вероятности, из знаний, закодированных в их весах, а не из предписанных ответов. Считаю этот сдвиг захватывающим, потому что он полностью инвертирует логику предыдущих ботов. Быстро смотрим, как это работает.

1. Токенизация

Первый шаг нарезать текст на токены -- единицы мельче слов, но крупнее символов :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

У каждого токена есть числовой ID в словаре (типично 32 000-128 000 токенов для свежих моделей). Эта фрагментация позволяет модели управлять словами, никогда не виденными, раскладывая их на известные подслова.

2. Эмбеддинги

Каждый ID токена конвертируется в вектор -- массив флоатов (типично 4096 измерений для средней модели). Этот вектор это погружение (embedding), кодирующее смысл токена в математическом пространстве, где семантически близкие токены имеют близкие векторы :

вектор("roi")  − вектор("homme") + вектор("femme")  ≈  вектор("reine")

Это свойство emerge из обучения без явного программирования с моей стороны или со стороны исследователей. Вижу в этом прямое следствие того, как слова появляются в похожих контекстах.

3. Внимание

Механизм внимания (введённый статьёй "Attention is All You Need" в 2017) это то, что сделало LLM возможными. Для каждого токена внимание вычисляет, какие другие токены во фразе важны для понимания этого :

"La banque a refusé mon prêt."
       ↑
Токен "banque" смотрит на : "refusé", "prêt" → понимает, что речь о финансовом институте

"Je vais me promener sur la banque."
       ↑
Токен "banque" смотрит на : "promener", "sur" → понимает, что речь о береге

Внимание позволяет модели захватывать контекст : каждый токен понимается в зависимости от окружающих, в полностью реляционном чтении. Именно эта чувствительность к контексту сильнее всего помогла мне понять, почему LLM превосходят фиксированные паттерны.

4. Предсказание следующего токена

Обучение LLM опирается на обманчивую простоту : показываешь ему текст, прячешь последний токен и просишь предсказать. Затем повторяешь миллиарды раз. Люблю эту идею, потому что очень простое правило обучения даёт очень богатые способности.

Вход :  "Je ne comprends"
Скрыт :  "pas"
Предсказание модели : "pas" (вероятность 0.87), "rien" (0.05), "jamais" (0.02)...

Цель максимизировать вероятность истинного токена на каждой позиции. Это называется next-token prediction. Во время обучения модель подстраивает свои миллиарды параметров, чтобы минимизировать ошибку предсказания на терабайтах текста.

В момент инференса (когда с ним говоришь) модель генерирует по токену за раз в цикле :

Токен 1 : "Je"    (вход : "Parle-moi de toi.")
Токен 2 : "suis"  (вход : "Parle-moi de toi. Je")
Токен 3 : "un"    (вход : "Parle-moi de toi. Je suis")
Токен 4 : "chatbot" (вход : "Parle-moi de toi. Je suis un")
...

Каждый токен семплируется по своей вероятности (температура, top-k, top-p контролируют степень "креативности"). И всё. Миллиарды параметров, повторяющие эту операцию тысячи раз. Считаю головокружительным, что столь повторяющийся механизм даёт столь разнообразные ответы.

Что фундаментально меняется

Аспект Символьные боты (ELIZA, PARRY, ALICE) Современные LLM
Представление Явные слова и правила Числовые векторы (embeddings)
Генерация Выбор среди предписанных ответов Вероятностное предсказание токен за токеном
Знания Хранятся в файлах правил Закодированы в весах сети
Обучение Ручное (написание правил) Автоматическое (обучение на корпусе)
Робастность Нулевая вне предусмотренных мотивов Обобщает на невиданные входы
Интерпретируемость Идеальная (правила можно читать) Ограничена (чёрный ящик)

Классические чат-боты прозрачны, но хрупки. LLM робастен, но непрозрачен. Вижу их сегодня как взаимодополняющие инструменты под разные нужды, и выбираю тот или другой по уровню контроля, который хочешь сохранить над ответами.

Если хочешь углубить внутренности LLM, рекомендую это видео как отличный ресурс :

How LLMs Work : YouTube

Luna Protocol : современный синтез

Статьи про Luna Protocol (ссылки ниже) представляют на мой взгляд самая удачная синтеза всего показанного : современный Discord-бот, комбинирующий локальный LLM со сложной поведенческой системой, всё построенное на уроках 60 лет разговорного ИИ. Это проект, позволивший мне связать исторические порты с текущими экспериментами.

Luna Protocol : я создал автономного Discord-бота, симулирующего человека

Эта статья деталирует полную архитектуру LLM-based Discord-бота :

  • Приоритетная система триггеров (упоминание > ЛС > имя > ключевое слово > follow-up > случайный)
  • Человеческие поведения : переменная концентрация, опечатки, колебания (15 %), забывания (3 %), тематическая усталость
  • Расписание сна : бот спит, тормозит или игнорирует по времени
  • TTS-пайплайн : синтез речи через Piper + ffmpeg → голосовые сообщения Discord
  • Стриминг реального времени : LLM испускает токены один за другим на типизированную шину событий

Что связывает эту статью с историческими чат-ботами, так это тот же квест : заставить поверить, что говоришь с человеком. ELIZA использовала текстовые зеркала. PARRY использовал эмоциональную модель. ALICE использовала 99k категорий. Luna Protocol использует дообученный LLM + поведенческую систему, симулирующую человеческие несовершенства. Нахожу в этой филиации то же требование : создать иллюзию присутствия по ту сторону экрана.

Luna Protocol : зачем я дообучил модель 1,5B

Вторая статья исследует дообучение и few-shot priming. Моё центральное открытие : меньшая модель (1,5B), обученная на меньшем количестве данных (50k образцов), превосходит большую модель (3B), если правильно затравливать few-shot примерами. Я был реально удивлён этим результатом, когда измерил.

Это урок, прямо резонирующий с историческими чат-ботами :

  • ELIZA показывала, что несколькими хорошо спроектированными правилами можно симулировать понимание
  • ALICE показывала, что 99k категориями можно симулировать общую культуру
  • Luna Protocol показывает, что хорошим дообучением и 5 few-shot примерами маленький LLM может симулировать человека

Техника меняется, а принцип тот же : качество данных и точность системы важнее сырого размера. Это убеждение, защищаемое в собственных экспериментах.


Заключение : три вещи запомнить

1. Разговорный ИИ начался не с ChatGPT. ELIZA 60 лет. PARRY прошёл тест Тьюринга в 1972. ALICE выиграла Лёбнера трижды. Jabberwacky заложил основы обучения на транскриптах, которые Cleverbot индустриализировал в большом масштабе. Каждый подход принёс кусок пазла. Это и подтвердили мои порты.

2. Больше данных ≠ умнее. Транскрипт Jabberwacky не имеет правил. 99k категорий ALICE не учатся. Дообучение Luna Protocol на 50k образцах превосходит модель 3B. Конвенциональная мудрость говорит "чем больше, тем лучше" -- история чат-ботов показывает, что архитектура и дизайн считаются не меньше размера. Защищаю эту идею в каждом техническом выборе.

3. Задача та же 60 лет. Как заставить человека поверить, что он говорит с другим человеком? ELIZA отвечала текстовыми зеркалами. PARRY симулированным гневом. ALICE фактами. Luna Protocol с LLM, спящим и делающим опечатки. Решение меняется, нужда остаётся. Именно эта непрерывность даёт мне желание продолжать исследовать.

Репозиторий открытый, можешь клонировать, запускать каждого бота и сам увидеть, как 60 лет разговорного ИИ умещаются в один TypeScript-репозиторий. Надеюсь, получишь столько же удовольствия, сколько я, заставляя их говорить.

Ресурс Ссылка
Репозиторий GitHub fox3000foxy/chatbots
Luna Protocol -- архитектура бота Читать статью
Luna Protocol -- дообучение few-shot Читать статью
Оригинальные скрипты ELIZA anthay/ELIZA
Оригинальный исходник PARRY lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
Оригинальный RFC 439 PARRY Encounters the DOCTOR
Отличное объяснение работы LLM https://www.youtube.com/watch?v=YmLp8qe87A0

De ELIZA a los LLM: 60 años de IA conversacional, reconstruida en TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot: cinco arquitecturas radicalmente distintas del mismo problema, llevadas a TypeScript con sus datos de origen. De 1966 a los LLM modernos, así aprendió a hablar la IA conversacional, y lo que un repo de chatbots nos enseña sobre 60 años de investigación.

De ELIZA a los LLM: 60 años de IA conversacional, reconstruida en TypeScript

En 1966, Joseph Weizenbaum escribía 420 líneas de MAD-SLIP en un IBM 7094 para crear el primer chatbot de la historia. El programa se llamaba ELIZA, y simulaba una psicoterapeuta rogeriana con patrones básicos y permutaciones de frases. Seis décadas después, la IA conversacional se volvió un tema gran público: ChatGPT, Claude, Gemini están en todas las conversaciones. Cuando releí ese código de origen, me impactó su economía de medios.

Pero entre esos dos extremos, hubo PARRY (el chatbot paranoico, 1972), ALICE (el rey del AIML con 99 000 categorías, 1995), Jabberwacky (el primero en aprender sin reglas, 1997), y Cleverbot (su sucesor industrial, 2008). Los veo como cinco respuestas radicalmente distintas al mismo problema: hacer hablar una máquina.

Llevé esos cinco bots a TypeScript con sus datos de origen: scripts ELIZA, diccionarios PARRY, archivos AIML de ALICE. Cada port es autónomo, listo para usar, y documentado hasta el detalle. Mi objetivo con ese trabajo va más allá de la simple ejecución: quiero entender cómo funcionaban, por qué marcaron la historia, y lo que sus arquitecturas respectivas revelan sobre la IA de ayer... y de hoy.

bun run eliza    # Habla con ELIZA (1966)
bun run parry    # Habla con PARRY (1972)
bun run alice    # Habla con ALICE (1995)
bun run jabber   # Habla con Jabberwacky
bun run cleverbot # Habla con Cleverbot
bun run meeting  # ELIZA vs PARRY automático

Voy a diseccionar cada bot contigo, mirar su código, y luego tender el puente con los LLM modernos a través de los artículos sobre Luna Protocol.


ELIZA (1966): el arte de hacer creer que se entiende

Empiezo por la más antigua, y a mis ojos la más impresionante por su simplicidad. ELIZA no tiene ninguna inteligencia en el sentido moderno del término. Funciona solo con patrones textuales y un poco de permutación.

El principio

El script DOCTOR (la versión psicoterapeuta) funciona con una tabla de keywords, cada una asociada a patrones de descomposición y reglas de reensamblaje. Aquí una regla típica:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO es la palabra clave. 0 es un patrón de descomposición que dice "captura todo lo que sigue" (como un wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. es la regla de reensamblaje. Eso es todo.

Cuando dices "Hello, I'm sad today", ELIZA:

  1. Pone el texto en mayúsculas: HELLO I'M SAD TODAY
  2. Escanea cada palabra contra su tabla de keywords
  3. Encuentra HELLO → lo mete en la pila de keywords
  4. Toma la keyword con la prioridad más alta
  5. Prueba cada patrón de descomposición en orden
  6. Si coincide, selecciona la próxima regla de reensamblaje (round-robin)
  7. Reemplaza los (1), (2) etc. por las partes capturadas

Pero la parte realmente inteligente son las reglas PRE. Mira esto:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Cuando ELIZA coincide con MY, transforma el resto de la frase (capturado por 0) vía la regla PRE, y reinyecta el resultado como si el usuario acabara de decir una palabra clave nueva. En concreto:

Dices: "My mother hates me"
  → PRE transforma: "YOUR MOTHER HATES YOU"
  → reinyectado como si lo acabaras de decir
  → probablemente coincide con "YOU" → nueva respuesta

Por eso ELIZA da la impresión de entender la diferencia entre "yo" y "tú": veo ahí una transformación mecánica perfectamente diseñada que simula la comprensión.

Aquí el flujo completo, desde la tecla del usuario hasta la respuesta:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normaliza la puntuación"]
    B --> C["splitUserInput()<br>trocea en palabras"]
    C --> D["Build keyword stack<br>ordenado por prioridad"]
    D --> E{"Stack no vacío?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

Lo que la hacía creíble

Considero la elección de Weizenbaum un golpe de genio: la psicoterapia rogeriana. Ese enfoque consiste en reflejar lo dicho por el paciente sin interpretar. "Estoy triste" → "Dices que estás triste". Es justo lo que ELIZA sabe hacer, y como esa técnica terapéutica está reconocida, a nadie le extraña su respuesta. Cuando pruebas el bot, sientes directamente ese efecto espejo.

En el port TypeScript

En mi port, cargo los scripts .ela (formato S-expression de origen), los parseo enteros (incluida la codificación Hollerith, ese formato de cadena de los 60), y ejecuto el mismo ciclo: uppercasing → split → keyword stack → descomposición → reensamblaje → PRE/transforms. Quise mantenerme fiel al comportamiento de origen hasta en los detalles.

➡ Ver el código fuente


PARRY (1972): el primer chatbot con emociones

Seis años después de ELIZA, Kenneth Colby (psiquiatra en Stanford) creó PARRY: un chatbot que simula un paciente con esquizofrenia paranoide. Donde ELIZA funcionaba como un espejo vacío, PARRY incluye un verdadero modelo emocional interno. Es ese contraste lo que más me marcó al llevarlos uno tras otro.

El modelo emocional

PARRY tiene cuatro variables continuas que evolucionan en cada turno de conversación:

Variable Línea base Decaimiento/turno Descripción
ANGER 0 −1.0 Hostilidad, irritación
FEAR 0 −0.2 Paranoia (decae despacio tras el inicio del delirio)
MISTRUST 0 −0.05 Desconfianza (muy lenta en bajar)
HURT 0 −0.5 Pena emocional

Esos valores suben vía saltos emocionales (ajump, fjump, hjump) disparados por reglas de inferencia, y decaen naturalmente hacia sus líneas base en cada turno.

La red de creencias

PARRY tiene 200+ creencias guardadas en el archivo bel:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Cada creencia tiene una categoría (HUM = el paciente, HUM2 = los demás, DOC = el doctor, INT = el interrogatorio, INN = las intenciones) y una fuerza (0-5). Las reglas de inferencia (TH2, EMOTE, IF) propagan las creencias entre ellas:

  • TH2: si una creencia A supera un umbral, se refuerza y sus consecuencias aumentan
  • EMOTE: si una creencia supera un umbral, dispara un salto emocional (anger/fear/hurt)
  • IF: condicional: si A es verdad, entonces B se vuelve verdad a cierto nivel

La jerarquía de los delirios (flare system)

La parte más fascinante de PARRY es su sistema de "flares": una cadena de escalada que lleva progresivamente hacia el delirio central:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Cada palabra clave dispara una respuesta preescrita (vía el pattern matching), y si sigues el tema, PARRY deriva progresivamente hacia su delirio central de persecución. Una vez que un flare se "dispara", queda inactivo (deadFlares) y PARRY pasa al siguiente, como un interlocutor que ahonda en su tema. Me encanta esa mecánica porque crea una progresión narrativa con muy poco código.

Por qué PARRY es histórico

En 1972, PARRY pasó una variante del test de Turing: unos psiquiatras recibieron transcripciones de conversaciones con PARRY y con verdaderos pacientes paranoicos, y no pudieron distinguirlos de forma fiable. Es el primer programa en lograrlo, 40 años antes que los LLM. Encuentro esa hazaña notable cuando comparas los medios de la época con los de hoy.

➡ Ver el código fuente


RFC 439: cuando ELIZA se encuentra con PARRY

El 18 de septiembre de 1972, dos programas hablaron por primera vez en la historia, literalmente: es la primera conversación inter-IA jamás registrada. ELIZA (corriendo en BBN en Boston) y PARRY (corriendo en Stanford) conversaron vía ARPANET, relevados por operadores humanos que escribían la salida de uno hacia la entrada del otro. La transcripción se publicó como RFC 439 ("PARRY Encounters the DOCTOR"). Cuando descubrí ese documento, entendí que marcaba un momento fundador para nuestro campo.

Aquí un extracto de cómo era:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

La coherencia de ese intercambio me sorprendió en la primera lectura. ELIZA hace su trabajo de terapeuta: reformular, cuestionar, explorar. PARRY hace su trabajo de paciente paranoico: quejarse, acusar, expresar desconfianza. Ambos programas se mantienen perfectamente en su rol gracias a la combinación de sus mecanismos respectivos (patrones ELIZA + modelo emocional PARRY) que produce respuestas que encajan por azar. Cuando lees la transcripción entera, ves hasta qué punto esa alineación accidental crea la ilusión de un diálogo intencional.

Puedes reproducir esa conversación con mi repo:

bun run meeting

Mi simulación lanza 25 turnos automáticos entre ambos bots, con un tema de partida aleatorio (caballos, crimen organizado, emociones...). Como ELIZA y PARRY incluyen ambos elementos no deterministas (round-robin ELIZA, aleatorización PARRY), cada ejecución produce un intercambio distinto. Me encanta relanzar el comando varias veces para comparar las trayectorias.

Lo que me impacta con ELIZA vs PARRY es el encuentro entre dos programas, uno con una arquitectura sin estado interno, otro con un modelo emocional completo, que producen juntos una conversación que parece algo deliberado. Para 1972, encuentro el resultado alucinante.


ALICE (1995): el pattern matching a gran escala

ALICE (Artificial Linguistic Internet Computer Entity) la creó Richard Wallace en 1995, y ganó el Loebner Prize tres veces (2000, 2001, 2004). Donde ELIZA tenía unos cientos de reglas y PARRY unos miles, ALICE tiene 99 524, repartidas en 66 archivos AIML. Cuando abrí ese corpus por primera vez, medí lo que "gran escala" quería decir en la época.

AIML: el lenguaje de las categorías

AIML (Artificial Intelligence Markup Language) es un formato XML para definir pares pregunta-respuesta:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Pero la potencia de ALICE viene de los wildcards y del SRAI (Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- equivale a <srai><star/></srai> -->
  </template>
</category>

El SRAI permite a ALICE redirigir una entrada hacia otra categoría, creando una cadena de reducción:

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

Es el mecanismo que da a ALICE su flexibilidad: en vez de escribir una respuesta para cada formulación posible, escribes una respuesta canónica y rediriges las variaciones hacia ella. El límite de profundidad es 10: más allá, ALICE abandona para evitar bucles infinitos (el diseño de las categorías los evita cuidadosamente, y esa red de seguridad sigue siendo esencial). Encuentro elegante esa elección de diseño porque factoriza miles de formulaciones con muy pocas reglas.

Cómo ALICE coincide los patrones

Los patrones se ordenan por especificidad: los que tienen menos wildcards se prueban primero. Los wildcards * y _ capturan cualquier secuencia de palabras. El motor compila cada patrón en regex, y luego itera las categorías ordenadas hasta encontrar una coincidencia.

// Nuestra implementación TypeScript: simplificada pero fiel
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Por qué ALICE dominó los Loebner

99 524 categorías es un número que lo cambia todo. ELIZA daba la ilusión de la inteligencia porque sus pocas reglas estaban bien diseñadas para un contexto específico (la terapia). ALICE cubre tantos temas que da la impresión de tener una verdadera cultura general: ciencias, política, humor, deportes, emociones, todo está. De ese enfoque retengo una lección que aún aplico: una cobertura amplia y cuidada compensa la ausencia de razonamiento profundo.

➡ Ver el código fuente


Jabberwacky (1997) y Cleverbot (2008): la ruptura epistemológica

Todos los bots anteriores comparten una hipótesis: hay que escribir las respuestas. ELIZA tiene sus reglas S-expressions, PARRY sus patrones selectivos, ALICE sus categorías AIML. Rollo Carpenter tomó el contrapié total con una pregunta radical: ¿y si dejamos que las conversaciones pasadas aporten las respuestas? Encuentro atrevido ese vuelco porque abandona toda escritura manual de reglas.

La idea

Jabberwacky (lanzado hacia 1997, convertido en Cleverbot en 2008) se apoya por entero en todo el historial de las conversaciones guardado en un transcript plano, y cuando alguien le habla, busca en ese historial el momento más similar y reutiliza lo que se dijo después:

Usuario: "hello"
  ↓
Buscar: ¿alguien ya dijo "hello" antes?
  ↓
Sí, en la sesión #3, línea 14, alguien dijo "hello" y el bot respondió "hi there!"
  ↓
Responder: "hi there!"

Aquí encontrarás solo un archivo gigante de cosas que la gente se dijo, reutilizado en el momento oportuno. Veo ahí la definición misma de la emergencia: un comportamiento conversacional nace de una acumulación de huellas humanas.

La implementación TypeScript

Mi port TypeScript reproduce esa arquitectura exacta:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 líneas seed + historial"]
    B --> C["withReplies()<br>extrae los pares<br>(línea → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>contexto antes de esa línea)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidatos ordenados"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>del par ganador"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append al transcript<br>save() → JSON"]
    L --> M

Aquí el corazón del scoring, con mi propia heurística inspirada en las descripciones públicas de Cleverbot:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): similitud entre la entrada de usuario y la línea histórica
  • contextFit (0.25): similitud entre la conversación reciente y lo que precedía la línea histórica
  • recencyBonus (0.10): los recuerdos recientes cuentan un poco más (la personalidad del bot deriva con el tiempo)

El pick es probabilístico (roulette-wheel selection): el mejor candidato gana más a menudo, con una parte de variabilidad que da variedad. Aprecio esa elección porque hace las conversaciones menos previsibles cuando pruebas el bot varias veces.

Cleverbot: las dos innovaciones documentadas

Cleverbot añade dos mecanismos al concepto base de Jabberwacky:

  1. Aprendizaje multipersona: millones de usuarios contribuyen al mismo transcript compartido. Una respuesta sacada del historial puede venir de una voz por completo distinta de la de la conversación en curso, lo que explica por qué Cleverbot cambia de pronto de personalidad.

  2. Aprendizaje diferido: lo que le dices a Cleverbot durante una sesión NO está disponible para coincidir durante esa misma sesión. Las líneas nuevas se marcan pending y solo se vuelven coincidentes tras una "consolidación" entre sesiones, lo que explica por qué no puedes enseñar un hecho a Cleverbot y reutilizarlo en la misma conversación.

// Cleverbot: las líneas recientes son invisibles hasta la consolidación
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() se llama al arrancar, no durante la sesión

Mi port TypeScript implementa esos dos comportamientos: las líneas tienen un flag consolidated, y cada sesión de REPL empieza con una consolidación de las líneas en espera.

➡ Ver el código fuente


Análisis del port TypeScript: diseñar una arquitectura común

Construir esos cinco bots en el mismo lenguaje me enfrentó a una pregunta interesante: ¿es posible factorizar código entre arquitecturas tan distintas?

Mi respuesta tras llevarlos todos: muy poco. Cada bot tiene un bucle fundamental distinto:

Bot Bucle principal Datos Aprendizaje
ELIZA Keyword stack → descomposición → reensamblaje Scripts .ela en S-expressions Ninguno
PARRY Tokenización → patrones selectivos / flares / keywords / inferencias 58 archivos PDP-10 (diccionarios, creencias, reglas) Ninguno
ALICE Patrones ordenados → regex → template AIML → SRAI recursivo 66 archivos AIML XML Ninguno
Jabberwacky Similitud → contexto → recency → pick ponderado Transcript JSON (crece con el uso) Continuo
Cleverbot Igual que Jabberwacky + pending/consolidated + personas Transcript JSON + semillas multipersona Diferido (entre sesiones)

Lo que comparten es la interfaz CLI y la infraestructura TypeScript (biome para el lint, tsx para la ejecución). El resto es específico de cada arquitectura.

Decisiones de diseño comunes

1. Fidelidad a los datos originales. Para ELIZA, PARRY y ALICE, uso los archivos de origen, scripts ELIZA recuperados en los archivos Weizenbaum en 2021, código original PARRY del PDP-10 (58 archivos), AIML Free ALICE v1.6. Los conservo en su forma de origen. Los bots se comportan como los originales porque usan los mismos datos.

2. Clean-room para las partes propietarias. Jabberwacky y Cleverbot plantean un caso distinto: su código fuente nunca se publicó (Existor/Rollo Carpenter lo mantuvo propietario). Mis ports son por tanto clean-room reimplementations, construidas solo a partir de descripciones públicas del comportamiento. No copié ninguna línea de código ni dato propietario.

3. Dependencias mínimas. El único verdadero prerrequisito es TypeScript. ALICE usa dom-js para parsear el XML de los archivos AIML (66 archivos, 99 524 categorías, el parsing XML casero sería una pérdida de tiempo). Todo lo demás es en vanilla TypeScript. Tomé esa decisión para mantener cada bot legible y fácil de lanzar cuando clonas el repo.


De los chatbots simbólicos a los LLM: el salto conceptual

Los cinco bots que acabo de presentarte comparten una característica fundamental: son simbólicos. Sus "conocimientos" se apoyan por entero en símbolos explícitos: patrones textuales, tablas de reglas, categorías XML, líneas de transcript. Funcionan por entero sin representación numérica del lenguaje.

Comparten por tanto el mismo techo de cristal: solo responden a lo previsto o registrado explícitamente. ELIZA se pierde si sales del marco terapéutico. PARRY solo habla de sus temas paranoicos. ALICE no aprende nada de sus conversaciones. Jabberwacky solo responde con réplicas ya pronunciadas. Cuando pruebas esos límites, entiendes rápido por qué esa generación tocó techo.

Los LLM (Large Language Models) franquean ese techo cambiando radicalmente de paradigma: en vez de manipular símbolos, convierten el lenguaje en números y aprenden relaciones estadísticas entre esos números. Generan cada token al vuelo calculando probabilidades, a partir de conocimientos codificados en sus pesos más que de respuestas preescritas. Encuentro alucinante ese vuelco porque invierte por completo la lógica de los bots anteriores. Veamos rápido cómo funciona.

1. Tokenization

La primera etapa es trocear el texto en tokens: unidades más pequeñas que palabras pero más grandes que caracteres:

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Cada token tiene un ID numérico en un vocabulario (típicamente 32 000 a 128 000 tokens para los modelos recientes). Esa fragmentación permite al modelo gestionar palabras que nunca vio descomponiéndolas en subpalabras conocidas.

2. Embeddings

Cada token ID se convierte en un vector: una tabla de números flotantes (típicamente 4096 dimensiones para un modelo de tamaño medio). Ese vector es un plongement (embedding) que codifica el sentido del token en un espacio matemático donde tokens semánticamente cercanos tienen vectores cercanos:

vector("roi")  − vector("homme") + vector("femme")  ≈ vector("reine")

Esa propiedad emerge del entrenamiento, sin programación explícita por mi parte ni por la de los investigadores. Veo ahí una consecuencia directa de la forma en que las palabras aparecen en contextos similares.

3. Attention

El mecanismo de atención (introducido por el paper "Attention is All You Need" en 2017) es lo que hizo posibles los LLM. Para cada token, la atención calcula qué otros tokens en la frase son importantes para entender este:

"La banque a refusé mon prêt."
      ↑
Token "banque" mira: "refusé", "prêt" → entiende que se trata de una institución financiera

"Je vais me promener sur la banque."
      ↑
Token "banque" mira: "promener", "sur" → entiende que se trata de una orilla

La atención permite al modelo capturar el contexto: cada token se entiende según los que lo rodean, en una lectura plenamente relacional. Es esa sensibilidad al contexto lo que más me ayudó a entender por qué los LLM superan los patrones fijos.

4. Predicción del próximo token

El entrenamiento de un LLM se apoya en una simplicidad engañosa: le muestras un texto, ocultas el último token, y le pides predecirlo. Luego repites la operación miles de millones de veces. Me gusta esa idea porque una regla de aprendizaje muy simple produce capacidades muy ricas.

Input:  "Je ne comprends"
Oculto:  "pas"
Predicción del modelo: "pas" (probabilidad 0.87), "rien" (0.05), "jamais" (0.02)...

El objetivo es maximizar la probabilidad del verdadero token en cada posición. Eso se llama la next-token prediction. Durante el entrenamiento, el modelo ajusta sus miles de millones de parámetros para minimizar el error de predicción sobre terabytes de texto.

En el momento de la inferencia (cuando le hablas), el modelo genera un token a la vez en bucle:

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

Cada token se muestrea según su probabilidad (temperatura, top-k, top-p controlan el grado de "creatividad"). Y eso es todo. Miles de millones de parámetros que repiten esa operación miles de veces. Encuentro vertiginoso que un mecanismo tan repetitivo produzca respuestas tan variadas.

Lo que cambia fundamentalmente

Aspecto Bots simbólicos (ELIZA, PARRY, ALICE) LLM modernos
Representación Palabras y reglas explícitas Vectores numéricos (embeddings)
Generación Selección en respuestas preescritas Predicción probabilística token por token
Conocimientos Guardados en archivos de reglas Codificados en los pesos de la red
Aprendizaje Manual (redacción de reglas) Automático (entrenamiento sobre corpus)
Robustez Nula fuera de los patrones previstos Generaliza a entradas jamás vistas
Interpretabilidad Perfecta (se pueden leer las reglas) Limitada (caja negra)

Los chatbots clásicos son transparentes pero frágiles. Un LLM es robusto pero opaco. Hoy los veo como herramientas complementarias para necesidades distintas, y elijo uno u otro según el nivel de control que quieras mantener sobre las respuestas.

Si quieres profundizar en el funcionamiento interno de los LLM, te recomiendo este vídeo como excelente recurso:

How LLMs Work — YouTube

Luna Protocol: la síntesis moderna

Los artículos sobre Luna Protocol (cuyos enlaces están abajo) representan a mis ojos la síntesis más lograda de todo lo que acabo de mostrarte: un bot Discord moderno que combina un LLM local con un sistema conductual sofisticado, todo construido sobre las lecciones de 60 años de IA conversacional. Es el proyecto que me permitió enlazar mis ports históricos con mis experimentos actuales.

Luna Protocol: creé un bot Discord autónomo que simula un ser humano

Este artículo detalla la arquitectura completa de un bot Discord LLM-based:

  • Sistema de disparo por prioridades (mención > DM > nombre > palabra clave > follow-up > aleatorio)
  • Comportamientos humanos: concentración variable, erratas, vacilaciones (15%), olvidos (3%), fatiga temática
  • Horarios de sueño: el bot duerme, ralentiza, o ignora según la hora
  • Pipeline TTS: síntesis vocal vía Piper + ffmpeg → mensajes vocales Discord
  • Streaming en tiempo real: el LLM emite los tokens uno por uno sobre un bus de eventos tipado

Lo que enlaza este artículo con los chatbots históricos es la misma búsqueda: hacer creer que hablas con una persona. ELIZA usaba espejos textuales. PARRY usaba un modelo emocional. ALICE usaba 99k categorías. Luna Protocol usa un LLM fine-tuneado + un sistema conductual que simula las imperfecciones humanas. Reencuentro en esa filiación la misma exigencia: crear la ilusión de una presencia al otro lado de la pantalla.

Luna Protocol: por qué fine-tuneé un modelo de 1,5B

El segundo artículo explora el fine-tuning y el few-shot priming. Mi descubrimiento central: un modelo más pequeño (1,5B) entrenado sobre menos datos (50k muestras) supera a un modelo más grande (3B) cuando se lo inicia correctamente con ejemplos few-shot. Me sorprendió de verdad ese resultado cuando lo medí.

Es una lección que resuena directamente con los chatbots históricos:

  • ELIZA mostraba que con pocas reglas bien diseñadas, se puede simular la comprensión
  • ALICE mostraba que con 99k categorías, se puede simular la cultura general
  • Luna Protocol muestra que con un buen fine-tuning y 5 ejemplos few-shot, un pequeño LLM puede simular un ser humano

La técnica cambia, y el principio sigue siendo el mismo: la calidad de los datos y la precisión del sistema cuentan más que el tamaño bruto. Es una convicción que defiendo en mis propios experimentos.


Conclusión: tres cosas a recordar

1. La IA conversacional no empezó con ChatGPT. ELIZA tiene 60 años. PARRY pasó el test de Turing en 1972. ALICE ganó el Loebner tres veces. Jabberwacky sentó las bases del aprendizaje por transcript, que Cleverbot industrializó a gran escala. Cada enfoque aportó una pieza del puzle. Eso me confirmaron mis ports.

2. Más datos ≠ más inteligente. El transcript de Jabberwacky no tiene reglas. Las 99k categorías de ALICE no aprenden. El fine-tuning de Luna Protocol sobre 50k muestras supera el modelo 3B. La sabiduría convencional dice "cuanto más grande, mejor": la historia de los chatbots muestra que la arquitectura y el diseño cuentan tanto como el tamaño. Defiendo esa idea en cada una de mis decisiones técnicas.

3. El problema es el mismo desde hace 60 años. ¿Cómo hacer creer a un humano que habla con otro humano? ELIZA respondía con espejos textuales. PARRY con ira simulada. ALICE con hechos. Luna Protocol con un LLM que duerme y mete erratas. La solución cambia, la necesidad queda. Es esa continuidad la que me da ganas de seguir explorando.

El repo es open source, puedes clonarlo, lanzar cada bot, y ver por ti mismo cómo 60 años de IA conversacional caben en un solo repo TypeScript. Espero que disfrutes tanto como yo haciéndolos hablar.

Recurso Enlace
Repo GitHub fox3000foxy/chatbots
Luna Protocol: arquitectura bot Leer el artículo
Luna Protocol: fine-tuning few-shot Leer el artículo
ELIZA scripts originales anthay/ELIZA
Código fuente PARRY original lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 original PARRY Encounters the DOCTOR
Excelente explicación del funcionamiento de los LLM https://www.youtube.com/watch?v=YmLp8qe87A0

De ELIZA aos LLM: 60 anos de IA conversacional, reconstruída em TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- cinco arquiteturas radicalmente diferentes para o mesmo problema, portadas para TypeScript com seus dados originais. De 1966 aos LLM modernos, eis como a IA conversacional aprendeu a falar, e o que um repositório de chatbots nos ensina sobre 60 anos de pesquisa.

De ELIZA aos LLM: 60 anos de IA conversacional, reconstruída em TypeScript

Em 1966, Joseph Weizenbaum escreveu 420 linhas de MAD-SLIP num IBM 7094 para criar o primeiro chatbot da história. O programa chamava-se ELIZA, e simulava uma psicoterapeuta rogeriana com padrões básicos e permutações de frases. Seis décadas depois, a IA conversacional tornou-se um assunto mainstream -- ChatGPT, Claude, Gemini estão em todas as conversas.

Mas entre estes dois extremos, houve PARRY (o chatbot paranoico, 1972), ALICE (o rei do AIML com 99 000 categorias, 1995), Jabberwacky (o primeiro a aprender sem regras, 1997), e Cleverbot (o seu sucessor industrial, 2008). Cinco programas, cinco arquiteturas, um só problema: fazer uma máquina falar.

Este repo contém estes cinco bots, portados para TypeScript com os seus dados originais -- scripts ELIZA, dicionários PARRY, ficheiros AIML da ALICE. Cada port é autónomo, pronto a usar e documentado ao pormenor. O objetivo não é apenas fazê-los funcionar: é compreender como funcionavam, porque marcaram a história, e o que as suas respetivas arquiteturas nos ensinam sobre a IA de ontem... e de hoje.

bun run eliza    # Fala com ELIZA (1966)
bun run parry    # Fala com PARRY (1972)
bun run alice    # Fala com ALICE (1995)
bun run jabber   # Fala com Jabberwacky
bun run cleverbot # Fala com Cleverbot
bun run meeting  # ELIZA vs PARRY automático

Vamos dissecar cada bot, olhar para o seu código, e depois fazer a ponte com os LLM modernos através dos artigos sobre Luna Protocol.


ELIZA (1966): a arte de fazer crer que se compreende

Comecemos pela mais antiga, e provavelmente a mais impressionante na sua simplicidade. A ELIZA não tem nenhuma inteligência no sentido moderno. Sem rede neuronal, sem estatísticas, sem aprendizagem. Apenas padrões de texto e um pouco de permutação.

O princípio

O script DOCTOR (a versão psicoterapeuta) funciona com uma tabela de keywords, cada uma associada a padrões de decomposição e regras de remontagem. Eis uma regra típica:

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO é a palavra-chave. 0 é um padrão de decomposição que diz "captura tudo o que se segue" (como um wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. é a regra de remontagem. É tudo.

Quando dizes "Hello, I'm sad today", a ELIZA:

  1. Coloca o texto em maiúsculas: HELLO I'M SAD TODAY
  2. Percorre cada palavra contra a sua tabela de keywords
  3. Encontra HELLO → coloca-a na pilha de keywords
  4. Pega na keyword com a prioridade mais alta
  5. Tenta cada padrão de decomposição por ordem
  6. Se houver correspondência, seleciona a próxima regra de remontagem (round-robin)
  7. Substitui (1), (2) etc. pelas partes capturadas

Mas a parte verdadeiramente inteligente são as PRE rules. Vê isto:

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Quando a ELIZA corresponde a MY, transforma o resto da frase (capturado por 0) através da PRE rule, e re-injeta o resultado como se o utilizador tivesse acabado de dizer uma nova palavra-chave. Concretamente:

Tu dizes: "My mother hates me"
  → PRE transforma: "YOUR MOTHER HATES YOU"
  → re-injetado como se o tivesses acabado de dizer
  → provavelmente corresponde a "YOU" → nova resposta

É por isso que a ELIZA parece compreender a diferença entre "eu" e "tu" -- não é compreensão, é uma transformação mecânica perfeitamente concebida.

Aqui está o fluxo completo, desde o input do utilizador até à resposta:

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normaliza a pontuação"]
    B --> C["splitUserInput()<br>divide em palavras"]
    C --> D["Build keyword stack<br>ordenado por prioridade"]
    D --> E{"Stack não vazio?"}
    E -->|"Sim"| F["Pop keyword de maior prioridade"]
    E -->|"Não"| G{"Memory recall?"}
    G -->|"Sim"| H["Recall declaração anterior do utilizador"]
    G -->|"Não"| I["Fallback: regra zNONE"]
    I --> J["Devolver resposta"]
    H --> J
    F --> K["Match padrões de decomposição"]
    K --> L{"Match encontrado?"}
    L -->|"Não"| M{"Keyword ligada?"}
    M -->|"Sim"| N["Push keyword ligada ao stack"]
    N --> E
    M -->|"Não"| O["Devolver NOMATCH"]
    O --> J
    L -->|"Sim"| P["Selecionar próxima remontagem (round-robin)"]
    P --> Q{"Tipo de remontagem?"}
    Q -->|"PRE"| R["Transformar palavras (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Saltar para a próxima keyword"]
    S --> E
    Q -->|"Standard"| T["Expandir (1), (2), (0)<br>em resposta final"]
    T --> J

O que a tornava credível

Weizenbaum fez uma escolha genial: a psicoterapia rogeriana. Esta abordagem consiste em refletir as palavras do paciente sem interpretar. "Estou triste" → "Dizes que estás triste". É exatamente o que a ELIZA sabe fazer -- e como é uma técnica terapêutica reconhecida, ninguém acha estranho.

No port TypeScript

O port carrega os scripts .ela (formato S-expression original), analisa-os completamente (incluindo a codificação Hollerith -- um formato de string dos anos 60), e executa o mesmo ciclo: uppercasing → split → keyword stack → decomposição → remontagem → PRE/transforms.

➡ Ver código fonte


PARRY (1972): o primeiro chatbot com emoções

Seis anos após a ELIZA, Kenneth Colby (psiquiatra em Stanford) criou o PARRY: um chatbot que simula um paciente com esquizofrenia paranoide. Onde a ELIZA era um espelho vazio, o PARRY tem um verdadeiro modelo emocional interno.

O modelo emocional

O PARRY tem quatro variáveis contínuas que evoluem a cada turno de conversa:

Variável Linha de base Decaimento/turno Descrição
ANGER 0 −1.0 Hostilidade, irritação
FEAR 0 −0.2 Paranoia (decai lentamente após o início do delírio)
MISTRUST 0 −0.05 Desconfiança (muito lenta a diminuir)
HURT 0 −0.5 Dor emocional

Estes valores aumentam através de saltos emocionais (ajump, fjump, hjump) desencadeados por regras de inferência, e decaem naturalmente para as suas linhas de base a cada turno.

A rede de crenças

O PARRY tem mais de 200 crenças armazenadas no ficheiro bel:

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Cada crença tem uma categoria (HUM = o paciente, HUM2 = os outros, DOC = o médico, INT = o interrogatório, INN = as intenções) e uma força (0-5). As regras de inferência (TH2, EMOTE, IF) propagam as crenças entre si:

  • TH2: se uma crença A ultrapassa um limiar, reforça-se e as suas consequências aumentam
  • EMOTE: se uma crença ultrapassa um limiar, desencadeia um salto emocional (raiva/medo/dor)
  • IF: condicional -- se A é verdadeira, então B torna-se verdadeira a um certo nível

A hierarquia dos delírios (flare system)

A parte mais fascinante do PARRY é o seu sistema de "flares" -- uma cadeia de escalada que leva progressivamente ao delírio central:

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Cada palavra-chave desencadeia uma resposta pré-escrita (via pattern matching), e se o interlocutor seguir o tema, o PARRY deriva progressivamente para o seu delírio central de perseguição. Assim que um flare é "desencadeado", torna-se inativo (deadFlares) -- o PARRY passa ao seguinte, simulando um interlocutor que aprofunda o assunto.

Porque o PARRY é histórico

Em 1972, o PARRY passou numa variante do teste de Turing: psiquiatras receberam transcrições de conversas com o PARRY e com verdadeiros pacientes paranoicos, e não conseguiram distingui-los de forma fiável. É o primeiro programa a consegui-lo, 40 anos antes dos LLM.

➡ Ver código fonte


RFC 439: quando a ELIZA encontra o PARRY

A 18 de setembro de 1972, dois programas falaram um com o outro pela primeira vez na história -- literalmente: é a primeira conversa inter-IA jamais registada. A ELIZA (a correr na BBN em Boston) e o PARRY (a correr em Stanford) conversaram através da ARPANET, retransmitidos por operadores humanos que digitavam a saída de um na entrada do outro. A transcrição foi publicada como RFC 439 ("PARRY Encounters the DOCTOR").

Aqui está um excerto do aspeto que tinha:

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

É surpreendentemente coerente. A ELIZA faz o seu trabalho de terapeuta: reformular, perguntar, explorar. O PARRY faz o seu trabalho de paciente paranoico: queixar-se, acusar, expressar desconfiança. Ambos os programas estão perfeitamente no seu papel -- não porque "compreendem" a situação, mas porque os seus respetivos mecanismos (padrões ELIZA + modelo emocional PARRY) produzem respostas que se encaixam por acaso.

O repo pode reproduzir esta conversa com:

bun run meeting

A simulação executa 25 turnos automáticos entre os dois bots, com um tema de partida aleatório (cavalos, crime organizado, emoções...). Como tanto a ELIZA como o PARRY têm elementos não-determinísticos (round-robin da ELIZA, randomização do PARRY), cada execução produz um intercâmbio diferente.

O que é impressionante na ELIZA vs PARRY é que temos dois programas -- um sem estado interno, o outro com um modelo emocional completo -- que juntos produzem uma conversa que se parece com algo deliberado. Para 1972, era de ficar boquiaberto.


ALICE (1995): o pattern matching em grande escala

A ALICE (Artificial Linguistic Internet Computer Entity) foi criada por Richard Wallace em 1995, e ganhou o Loebner Prize três vezes (2000, 2001, 2004). Onde a ELIZA tinha umas centenas de regras e o PARRY uns milhares, a ALICE tem 99 524 -- distribuídas por 66 ficheiros AIML.

AIML: a linguagem das categorias

AIML (Artificial Intelligence Markup Language) é um formato XML para definir pares pergunta-resposta:

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Mas o poder da ALICE vem dos wildcards e do SRAI (Symbolic Reduction):

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- equivalente a <srai><star/></srai> -->
  </template>
</category>

O SRAI permite à ALICE redirecionar um input para outra categoria, criando uma cadeia de redução:

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

Este é o mecanismo que dá à ALICE a sua flexibilidade: em vez de escrever uma resposta para cada formulação possível, escreve-se uma resposta canónica e redirecionam-se as variações para ela. O limite de profundidade é 10 -- para além disso, a ALICE desiste para evitar ciclos infinitos (cuidadosamente evitados na conceção das categorias, mas uma rede de segurança continua a ser essencial).

Como a ALICE corresponde aos padrões

Os padrões são ordenados por especificidade: os com menos wildcards são tentados primeiro. Os wildcards * e _ capturam qualquer sequência de palavras. O motor compila cada padrão numa regex, depois itera as categorias ordenadas até encontrar uma correspondência.

// A nossa implementação TypeScript -- simplificada mas fiel
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Porque a ALICE dominou os Loebner

99 524 categorias é um número que muda tudo. A ELIZA parecia inteligente porque as suas poucas regras estavam bem concebidas para um contexto específico (a terapia). A ALICE cobre tantos temas que dá a impressão de ter uma verdadeira cultura geral: ciências, política, humor, desporto, emoções, está tudo lá.

➡ Ver código fonte


Jabberwacky (1997) & Cleverbot (2008): a rutura epistemológica

Todos os bots anteriores partilham um pressuposto: é preciso escrever as respostas. A ELIZA tem as suas regras S-expression, o PARRY os seus padrões seletivos, a ALICE as suas categorias AIML. Rollo Carpenter tomou a posição completamente oposta: e se não escrevêssemos nada?

A ideia

O Jabberwacky (lançado por volta de 1997, tornado Cleverbot em 2008) não armazena nenhuma regra. Armazena todo o histórico de conversas num transcript simples, e quando alguém lhe fala, procura nesse histórico o momento mais semelhante e reutiliza o que foi dito a seguir:

Utilizador: "hello"
  ↓
Procurar: alguém já disse "hello" antes?
  ↓
Sim, na sessão #3, linha 14, alguém disse "hello" e o bot respondeu "hi there!"
  ↓
Responder: "hi there!"

Sem padrão. Sem gramática. Sem XML. Apenas um arquivo gigante de coisas que pessoas disseram umas às outras, reutilizado no momento certo. É a própria definição de emergência.

A implementação TypeScript

O port TypeScript reproduz esta arquitetura exata:

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 linhas seed + histórico"]
    B --> C["withReplies()<br>extrai pares<br>(linha → resposta)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>contexto antes desta linha)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidatos ordenados"]
    I --> J{"pickReply()<br>roleta<br>selection"}
    J -->|"Escolhido"| K["Resposta = reply.text<br>do par vencedor"]
    J -->|"Nenhum"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append ao transcript<br>save() → JSON"]
    L --> M

Aqui está o núcleo da pontuação -- a nossa própria heurística inspirada em descrições públicas do Cleverbot:

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65): semelhança entre o input do utilizador e a linha histórica
  • contextFit (0.25): semelhança entre a conversa recente e o contexto antes da linha histórica
  • recencyBonus (0.10): as memórias recentes contam um pouco mais (a personalidade do bot deriva com o tempo)

A seleção é probabilística (seleção por roleta): o melhor candidato ganha mais vezes, mas nem sempre -- o que dá variedade.

Cleverbot: as duas inovações documentadas

O Cleverbot acrescenta dois mecanismos ao conceito base do Jabberwacky:

  1. Aprendizagem multi-pessoa: milhões de utilizadores contribuem para o mesmo transcript partilhado. Uma resposta retirada do histórico pode vir de uma voz completamente diferente da da conversa atual -- o que explica porque é que o Cleverbot muda subitamente de personalidade.

  2. Aprendizagem diferida: o que dizes ao Cleverbot durante uma sessão NÃO está disponível para correspondência durante essa mesma sessão. As novas linhas são marcadas como pending e só se tornam correspondíveis após uma "consolidação" entre sessões -- o que explica porque não podes ensinar um facto ao Cleverbot e reutilizá-lo na mesma conversa.

// Cleverbot: as linhas recentes são invisíveis até à consolidação
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() é chamada no arranque, não durante a sessão

O port TypeScript implementa ambos os comportamentos: as linhas têm um flag consolidated, e cada sessão REPL começa por consolidar as linhas pendentes.

➡ Ver código fonte


Análise do port TypeScript: conceber uma arquitetura comum

Construir estes cinco bots na mesma linguagem confronta-nos com uma questão interessante: é possível fatorizar código entre arquiteturas tão diferentes?

A resposta é: muito pouco. Cada bot tem um ciclo fundamental diferente:

Bot Ciclo principal Dados Aprendizagem
ELIZA Keyword stack → decomposição → remontagem Scripts .ela em S-expressions Nenhuma
PARRY Tokenização → padrões seletivos / flares / keywords / inferências 58 ficheiros PDP-10 (dicionários, crenças, regras) Nenhuma
ALICE Padrões ordenados → regex → template AIML → SRAI recursivo 66 ficheiros AIML XML Nenhuma
Jabberwacky Similaridade → contexto → recência → seleção ponderada Transcript JSON (cresce com o uso) Contínua
Cleverbot Igual ao Jabberwacky + pending/consolidated + personas Transcript JSON + sementes multi-persona Diferida (entre sessões)

O que partilham é a interface CLI e a infraestrutura TypeScript (biome para lint, tsx para execução). O resto é específico de cada arquitetura.

Escolhas de conceção comuns

1. Fidelidade aos dados originais. Para a ELIZA, o PARRY e a ALICE, usamos os ficheiros originais -- scripts ELIZA recuperados dos arquivos Weizenbaum em 2021, código original PARRY do PDP-10 (58 ficheiros), AIML Free ALICE v1.6. Sem tradução, sem reescrita. Os bots comportam-se como os originais porque usam os mesmos dados.

2. Clean-room para as partes proprietárias. O Jabberwacky e o Cleverbot são diferentes: o seu código fonte nunca foi publicado (a Existor/Rollo Carpenter mantiveram-no proprietário). Os ports são portanto clean-room reimplementations -- construídas apenas a partir de descrições públicas do comportamento. Nenhuma linha de código ou dado proprietário é copiada.

3. Dependências mínimas. O único verdadeiro pré-requisito é TypeScript. A ALICE usa dom-js para analisar o XML dos ficheiros AIML (66 ficheiros, 99 524 categorias, analisar XML manualmente seria uma perda de tempo). Todo o resto é TypeScript vanilla.


Dos chatbots simbólicos aos LLM: o salto conceptual

Os cinco bots que acabámos de ver partilham todos uma característica fundamental: são simbólicos. O seu "conhecimento" é armazenado como símbolos explícitos -- padrões de texto, tabelas de regras, categorias XML, linhas de transcript. Não há nenhuma representação numérica da linguagem em nenhum destes sistemas.

O que também significa que todos têm o mesmo teto de vidro: só podem responder ao que foi explicitamente previsto ou registado. A ELIZA perde-se se saíres do contexto terapêutico. O PARRY não pode falar do tempo. A ALICE não aprende nada das suas conversas. O Jabberwacky só pode responder com réplicas já pronunciadas.

Os LLM (Large Language Models) ultrapassam este teto mudando radicalmente de paradigma: em vez de manipular símbolos, convertem a linguagem em números e aprendem relações estatísticas entre esses números. Não armazenam respostas pré-escritas -- geram cada token em tempo real calculando probabilidades. Vejamos rapidamente como funciona.

1. Tokenização

O primeiro passo é dividir o texto em tokens -- unidades mais pequenas que palavras mas maiores que caracteres:

"Não compreendo"
  → ["Não", " com", "pre", "endo"]

Cada token tem um ID numérico num vocabulário (tipicamente 32 000 a 128 000 tokens para modelos recentes). Esta fragmentação permite ao modelo lidar com palavras que nunca viu, decompondo-as em subpalavras conhecidas.

2. Embeddings

Cada ID de token é convertido num vetor -- um array de números de vírgula flutuante (tipicamente 4096 dimensões para um modelo de tamanho médio). Este vetor é um embedding que codifica o significado do token num espaço matemático onde tokens semanticamente próximos têm vetores próximos:

vetor("rei") − vetor("homem") + vetor("mulher") ≈ vetor("rainha")

Esta propriedade emerge do treino -- ninguém a programou explicitamente. É uma consequência da forma como as palavras são usadas em contextos semelhantes.

3. Attention

O mecanismo de attention (introduzido pelo artigo "Attention is All You Need" em 2017) é o que tornou os LLM possíveis. Para cada token, a attention calcula que outros tokens na frase são importantes para o compreender:

"O banco recusou o meu empréstimo."
     ↑
Token "banco" olha para: "recusou", "empréstimo" → compreende que é uma instituição financeira

"Vou sentar-me no banco do jardim."
     ↑
Token "banco" olha para: "sentar", "jardim" → compreende que é um assento

A attention permite ao modelo capturar o contexto -- cada token é compreendido com base nos que o rodeiam, não isoladamente.

4. Predição do próximo token

O treino de um LLM é enganadoramente simples: mostra-se-lhe texto, esconde-se o último token, e pede-se-lhe que o prediga. Depois repete-se milhares de milhões de vezes.

Input:  "Não compr"
Escondido: "eendo"
Predição do modelo: "eendo" (probabilidade 0,87), "reendo" (0,05)...

O objetivo é maximizar a probabilidade do token real em cada posição. Isto chama-se next-token prediction. Durante o treino, o modelo ajusta os seus milhares de milhões de parâmetros para minimizar o erro de predição em terabytes de texto.

Durante a inferência (quando lhe falamos), o modelo gera um token de cada vez num ciclo:

Token 1: "Sou"     (input: "Fala-me de ti.")
Token 2: "um"      (input: "Fala-me de ti. Sou")
Token 3: "chatbot" (input: "Fala-me de ti. Sou um")
...

Cada token é amostrado de acordo com a sua probabilidade (temperatura, top-k, top-p controlam o grau de "criatividade"). E é tudo. Milhares de milhões de parâmetros a fazer isto milhares de vezes.

O que muda fundamentalmente

Aspeto Bots simbólicos (ELIZA, PARRY, ALICE) LLM modernos
Representação Palavras e regras explícitas Vetores numéricos (embeddings)
Geração Seleção em respostas pré-escritas Predição probabilística token a token
Conhecimento Armazenado em ficheiros de regras Codificado nos pesos da rede
Aprendizagem Manual (redação de regras) Automática (treino em corpus)
Robustez Nula fora dos padrões previstos Generaliza a inputs nunca vistos
Interpretabilidade Perfeita (podem ler-se as regras) Limitada (caixa negra)

Os chatbots clássicos são transparentes mas frágeis. Um LLM é robusto mas opaco. Ambas as abordagens existem ainda hoje -- não como concorrentes, mas como ferramentas para necessidades diferentes.

Se quiser aprofundar o funcionamento interno dos LLMs, este vídeo é um excelente recurso:

Se quiser aprofundar o funcionamento interno dos LLMs, este vídeo é um excelente recurso:

How LLMs Work — YouTube

Luna Protocol: a síntese moderna

Os artigos sobre Luna Protocol (cujos links estão abaixo) representam a síntese mais conseguida de tudo o que acabámos de ver: um bot Discord moderno que combina um LLM local com um sistema comportamental sofisticado, tudo construído sobre as lições de 60 anos de IA conversacional.

Luna Protocol: criei um bot Discord autónomo que simula um ser humano

Este artigo detalha a arquitetura completa de um bot Discord baseado em LLM:

  • Sistema de acionamento prioritário (menção > MD > nome > palavra-chave > follow-up > aleatório)
  • Comportamentos humanos: concentração variável, erros de digitação, hesitações (15%), esquecimentos (3%), fadiga temática
  • Horários de sono: o bot dorme, abranda ou ignora consoante a hora
  • Pipeline TTS: síntese de voz via Piper + ffmpeg → mensagens de voz Discord
  • Streaming em tempo real: o LLM emite os tokens um a um num bus de eventos tipado

O que liga este artigo aos chatbots históricos é a mesma busca: fazer crer que se fala com uma pessoa. A ELIZA fazia-o com espelhos textuais. O PARRY com um modelo emocional. A ALICE com 99k categorias. O Luna Protocol fá-lo com um LLM fine-tunado + um sistema comportamental que simula as imperfeições humanas.

Luna Protocol: porque é que fiz fine-tuning de um modelo de 1,5B

O segundo artigo explora o fine-tuning e o few-shot priming. A descoberta central: um modelo mais pequeno (1,5B) treinado em menos dados (50k amostras) supera um modelo maior (3B) quando é devidamente preparado com exemplos few-shot.

É uma lição que ressoa diretamente com os chatbots históricos:

  • A ELIZA mostrava que com poucas regras bem concebidas, se pode simular compreensão
  • A ALICE mostrava que com 99k categorias, se pode simular cultura geral
  • O Luna Protocol mostra que com um bom fine-tuning e 5 exemplos few-shot, um LLM pequeno pode simular um ser humano

A técnica é diferente, mas o princípio é o mesmo: a qualidade dos dados e a precisão do sistema importam mais do que o tamanho bruto.


Conclusão: três coisas para reter

1. A IA conversacional não começou com o ChatGPT. A ELIZA tem 60 anos. O PARRY passou o teste de Turing em 1972. A ALICE ganhou o Loebner três vezes. O Jabberwacky lançou as bases da aprendizagem por transcript, que o Cleverbot industrializou em grande escala. Cada abordagem trouxe uma peça do puzzle.

2. Mais dados ≠ mais inteligente. O transcript do Jabberwacky não tem regras. As 99k categorias da ALICE não aprendem. O fine-tuning do Luna Protocol em 50k amostras supera o modelo 3B. A sabedoria convencional diz "quanto maior, melhor" -- a história dos chatbots mostra que a arquitetura e o design contam tanto como o tamanho.

3. O problema é o mesmo há 60 anos. Como fazer um humano acreditar que está a falar com outro humano? A ELIZA respondia com espelhos textuais. O PARRY com raiva simulada. A ALICE com factos. O Luna Protocol com um LLM que dorme e comete erros de digitação. A solução muda, a necessidade permanece.

O repo é open source -- podes cloná-lo, executar cada bot, e ver por ti mesmo como 60 anos de IA conversacional cabem num único repositório TypeScript.

Recurso Link
Repositório GitHub fox3000foxy/chatbots
Luna Protocol -- arquitetura do bot Ler o artigo
Luna Protocol -- few-shot fine-tuning Ler o artigo
Scripts ELIZA originais anthay/ELIZA
Código fonte PARRY original lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 original PARRY Encounters the DOCTOR
Excelente explicação de como LLMs funcionam https://www.youtube.com/watch?v=YmLp8qe87A0

Dari ELIZA ke LLM : 60 tahun AI percakapan, dibangun ulang dalam TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- lima arsitektur yang sangat berbeda untuk masalah yang sama, di porting ke TypeScript dengan data aslinya. Dari 1966 hingga LLM modern, beginilah cara AI percakapan belajar bicara, dan apa yang diajarkan repo chatbot tentang 60 tahun riset.

Dari ELIZA ke LLM : 60 tahun AI percakapan, dibangun ulang dalam TypeScript

Pada 1966, Joseph Weizenbaum menulis 420 baris MAD-SLIP di IBM 7094 untuk menciptakan chatbot pertama dalam sejarah. Program itu bernama ELIZA, dan ia mensimulasikan psikoterapis rogerian dengan motif dasar dan permutasi kalimat. Enam dekade kemudian, AI percakapan menjadi topik publik -- ChatGPT, Claude, Gemini ada di semua percakapan. Ketika saya membaca ulang kode aslinya, saya terkejut oleh kehematan sarananya.

Tapi antara dua ekstrem itu, ada PARRY (chatbot paranoid, 1972), ALICE (raja AIML dengan 99 000 kategori, 1995), Jabberwacky (yang pertama belajar tanpa aturan, 1997), dan Cleverbot (penerus industrinya, 2008). Saya melihat mereka sebagai lima jawaban yang sangat berbeda untuk masalah yang sama : membuat mesin bicara.

Saya mem porting kelima bot ini ke TypeScript dengan data aslinya -- skrip ELIZA, kamus PARRY, berkas AIML ALICE. Setiap port mandiri, siap pakai, dan didokumentasikan hingga detail terkecil. Tujuan saya dengan pekerjaan ini melampaui sekadar eksekusi : saya ingin memahami cara mereka bekerja, kenapa mereka menandai sejarah, dan apa yang diungkapkan arsitektur masing-masing tentang AI kemarin... dan hari ini.

bun run eliza    # Bicara dengan ELIZA (1966)
bun run parry    # Bicara dengan PARRY (1972)
bun run alice    # Bicara dengan ALICE (1995)
bun run jabber   # Bicara dengan Jabberwacky
bun run cleverbot # Bicara dengan Cleverbot
bun run meeting  # ELIZA vs PARRY otomatis

Saya akan membedah setiap bot bersamamu, melihat kodenya, lalu menjembatani ke LLM modern lewat artikel tentang Luna Protocol.


ELIZA (1966) : seni membuat orang percaya kita memahami

Saya mulai dari yang tertua, dan menurut saya paling mengesankan karena kesederhanaannya. ELIZA tidak punya kecerdasan apa pun dalam arti modern. Ia bekerja hanya dengan motif tekstual dan sedikit permutasi.

Prinsipnya

Skrip DOCTOR (versi psikoterapis) bekerja dengan tabel keyword, masing-masing terkait dengan pola dekomposisi dan aturan reassembly. Berikut aturan tipikal :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO adalah kata kunci. 0 adalah pola dekomposisi yang berarti "tangkap semua yang mengikuti" (seperti wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. adalah aturan reassembly. Itu saja.

Ketika kamu bilang "Hello, I'm sad today", ELIZA :

  1. Mengubah teks menjadi huruf besar : HELLO I'M SAD TODAY
  2. Memindai setiap kata melawan tabel keyword-nya
  3. Menemukan HELLO → mendorongnya ke tumpukan keyword
  4. Mengambil keyword dengan prioritas tertinggi
  5. Mencoba setiap pola dekomposisi berurutan
  6. Kalau cocok, memilih aturan reassembly berikutnya (round-robin)
  7. Mengganti (1), (2) dll. dengan bagian yang ditangkap

Tapi bagian yang benar-benar cerdas adalah aturan PRE. Lihat ini :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Ketika ELIZA mencocokkan MY, ia mengubah sisa kalimat (yang ditangkap oleh 0) lewat aturan PRE, dan menyuntikkan ulang hasilnya seolah pengguna baru saja mengucapkan kata kunci baru. Konkretnya :

Kamu bilang: "My mother hates me"
  → PRE mengubah: "YOUR MOTHER HATES YOU"
  → disuntikkan ulang seolah kamu baru mengucapkannya
  → mungkin cocok dengan "YOU" → respons baru

Itulah kenapa ELIZA memberi kesan memahami beda "aku" dan "kamu" : saya melihat di sana transformasi mekanis yang dirancang sempurna yang mensimulasikan pemahaman.

Berikut alur lengkapnya, dari ketikan pengguna hingga respons :

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalise la ponctuation"]
    B --> C["splitUserInput()<br>découpe en mots"]
    C --> D["Build keyword stack<br>ordonné par priorité"]
    D --> E{"Stack non-vide?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

Apa yang membuatnya kredibel

Saya menganggap pilihan Weizenbaum sebagai pukulan jenius : psikoterapi rogerian. Pendekatan ini consiste merefleksikan ucapan pasien tanpa menafsirkan. "Saya sedih" → "Kamu bilang kamu sedih". Tepat itulah yang bisa dilakukan ELIZA, dan karena teknik terapeutik ini diakui, tidak ada yang menganggap responsnya aneh. Ketika kamu menguji bot, kamu langsung merasakan efek cermin ini.

Dalam port TypeScript

Dalam port saya, saya memuat skrip .ela (format S-expression aslinya), mem parsingnya utuh (termasuk enkode Hollerith, format string tahun 60-an itu), dan menjalankan siklus yang sama : uppercasing → split → keyword stack → dekomposisi → reassembly → PRE/transform. Saya ingin tetap setia pada perilaku aslinya hingga detailnya.

➡ Lihat kode sumber


PARRY (1972) : chatbot pertama beremosi

Enam tahun setelah ELIZA, Kenneth Colby (psikiater di Stanford) menciptakan PARRY : chatbot yang mensimulasikan pasien penderita skizofrenia paranoid. Di mana ELIZA berfungsi seperti cermin kosong, PARRY membawa model emosional internal yang sungguhan. Kontras inilah yang paling menandai saya saat mem porting keduanya berurutan.

Model emosional

PARRY punya empat variabel kontinu yang berkembang di setiap putaran percakapan :

Variabel Garis dasar Peluruhan/putaran Deskripsi
ANGER 0 −1.0 Hostilitas, iritasi
FEAR 0 −0.2 Paranoia (meluruh lambat setelah awal deliri)
MISTRUST 0 −0.05 Ketidakpercayaan (sangat lambat turun)
HURT 0 −0.5 Luka emosional

Nilai ini naik lewat lompatan emosional (ajump, fjump, hjump) yang dipicu aturan inferensi, dan meluruh alami menuju garis dasarnya di setiap putaran.

Jaringan keyakinan

PARRY punya 200+ keyakinan yang disimpan dalam berkas bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Setiap keyakinan punya kategori (HUM = pasien, HUM2 = orang lain, DOC = dokter, INT = interogasi, INN = niat) dan kekuatan (0-5). Aturan inferensi (TH2, EMOTE, IF) menyebarkan keyakinan di antara mereka :

  • TH2 : kalau keyakinan A melampaui ambang, ia menguat dan konsekuensinya meningkat
  • EMOTE : kalau keyakinan melampaui ambang, ia memicu lompatan emosional (anger/fear/hurt)
  • IF : kondisional -- kalau A benar, maka B menjadi benar pada tingkat tertentu

Hierarki deliri (sistem flare)

Bagian paling menarik dari PARRY adalah sistem "flare"-nya -- rantai eskalasi yang berangsur menuju deliri pusat :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Setiap kata kunci memicu respons yang sudah ditulis (lewat pencocokan pola), dan kalau kamu mengikuti topiknya, PARRY berangsur menuju deliri pusat persekusinya. Begitu flare "terpicu", ia menjadi tidak aktif (deadFlares) dan PARRY lanjut ke berikutnya, seperti lawan bicara yang menggali topiknya. Saya sangat suka mekanisme ini karena ia menciptakan progresi naratif dengan sangat sedikit kode.

Kenapa PARRY bersejarah

Pada 1972, PARRY lolos varian tes Turing : psikiater menerima transkrip percakapan dengan PARRY dan dengan pasien paranoid sungguhan, dan tidak bisa membedakannya secara andal. Ini program pertama yang berhasil, 40 tahun sebelum LLM. Saya menganggap pencapaian ini luar biasa kalau kamu bandingkan sarana zaman itu dengan hari ini.

➡ Lihat kode sumber


RFC 439 : ketika ELIZA bertemu PARRY

Pada 18 September 1972, dua program saling bicara untuk pertama kalinya dalam sejarah, secara harfiah : ini percakapan antar-AI pertama yang pernah direkam. ELIZA (berjalan di BBN di Boston) dan PARRY (berjalan di Stanford) bercakap lewat ARPANET, disampaikan operator manusia yang mengetikkan keluaran satu ke masukan lainnya. Transkripnya diterbitkan sebagai RFC 439 ("PARRY Encounters the DOCTOR"). Ketika saya menemukan dokumen ini, saya paham ia menandai momen pendiri bagi bidang kami.

Berikut kutipan dari tampilannya :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

Koherensi pertukaran ini mengejutkan saya pada bacaan pertama. ELIZA melakukan tugas terapisnya : memformulasikan ulang, bertanya, menjelajah. PARRY melakukan tugas pasien paranoidnya : mengeluh, menuduh, mengungkapkan ketidakpercayaan. Kedua program tetap sempurna dalam perannya berkat kombinasi mekanisme masing-masing (pola ELIZA + model emosional PARRY) yang menghasilkan respons yang saling mengunci secara kebetulan. Ketika kamu membaca transkrip utuhnya, kamu melihat sejauh mana keselarasan kebetulan ini menciptakan ilusi dialog yang disengaja.

Kamu bisa mereproduksi percakapan ini dengan repo saya :

bun run meeting

Simulasi saya menjalankan 25 putaran otomatis antara kedua bot, dengan topik awal acak (kuda, kejahatan terorganisasi, emosi...). Karena ELIZA dan PARRY sama-sama membawa elemen non-deterministik (round-robin ELIZA, randomisasi PARRY), setiap eksekusi menghasilkan pertukaran berbeda. Saya suka menjalankan ulang perintah berkali-kali untuk membandingkan lintasannya.

Yang men striking saya soal ELIZA vs PARRY adalah pertemuan dua program, satu dengan arsitektur tanpa keadaan internal, satunya dengan model emosional lengkap, yang bersama menghasilkan percakapan yang menyerupai sesuatu yang disengaja. Untuk 1972, saya menganggap hasilnya mencengangkan.


ALICE (1995) : pencocokan pola skala besar

ALICE (Artificial Linguistic Internet Computer Entity) diciptakan Richard Wallace pada 1995, dan memenangkan Hadiah Loebner tiga kali (2000, 2001, 2004). Di mana ELIZA punya beberapa ratus aturan dan PARRY beberapa ribu, ALICE punya 99 524, terbagi dalam 66 berkas AIML. Ketika saya membuka korpus ini pertama kali, saya mengukur apa arti "skala besar" pada zaman itu.

AIML : bahasa kategori

AIML (Artificial Intelligence Markup Language) adalah format XML untuk mendefinisikan pasangan tanya-jawab :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Tapi kekuatan ALICE berasal dari wildcard dan SRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- setara dengan <srai><star/></srai> -->
  </template>
</category>

SRAI memungkinkan ALICE mengarahkan ulang masukan ke kategori lain, menciptakan rantai reduksi :

Input: "WHAT'S UP?"
  → pola "WHAT IS UP" → srai "HELLO"
    → pola "HELLO" → template "Hi there!"

Inilah mekanisme yang memberi ALICE fleksibilitasnya : alih-alih menulis respons untuk setiap formulasi yang mungkin, kamu menulis respons kanonis dan mengarahkan variasinya ke sana. Batas kedalamannya 10 : selebihnya, ALICE menyerah untuk menghindari loop tak berujung (desain kategorinya menghindarinya dengan cermat, dan jaring pengaman ini tetap esensial). Saya menganggap pilihan desain ini elegan karena ia memfaktorkan ribuan formulasi dengan sangat sedikit aturan.

Cara ALICE mencocokkan pola

Pola diurutkan berdasar spesifisitas : yang wildcardnya paling sedikit dicoba dulu. Wildcard * dan _ menangkap urutan kata apa pun. Mesin mengompilasi tiap pola menjadi regex, lalu mengiterasi kategori yang diurutkan hingga menemukan kecocokan.

// Implementasi TypeScript kami -- disederhanakan tapi setia
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Kenapa ALICE mendominasi Loebner

99 524 kategori, angka yang mengubah segalanya. ELIZA memberi ilusi kecerdasan karena sedikit aturannya dirancang baik untuk konteks spesifik (terapi). ALICE mencakup begitu banyak topik sampai memberi kesan punya budaya umum sungguhan : sains, politik, humor, olahraga, emosi, semuanya ada. Saya mengambil dari pendekatan ini pelajaran yang masih saya terapkan : cakupan luas dan dirawat mengkompensasi ketiadaan penalaran mendalam.

➡ Lihat kode sumber


Jabberwacky (1997) dan Cleverbot (2008) : ruptura epistemologis

Semua bot sebelumnya berbagi satu hipotesis : respons harus ditulis. ELIZA punya aturan S-expression-nya, PARRY pola selektifnya, ALICE kategori AIML-nya. Rollo Carpenter mengambil lawan total dengan pertanyaan radikal : bagaimana kalau percakapan lampau saja yang menyediakan respons? Saya menganggap pembalikan ini berani karena ia meninggalkan semua penulisan aturan manual.

Idenya

Jabberwacky (diluncurkan sekitar 1997, menjadi Cleverbot pada 2008) sepenuhnya bersandar pada seluruh riwayat percakapan yang disimpan dalam transkrip datar, dan ketika seseorang berbicara dengannya, ia mencari dalam riwayat ini momen paling mirip dan memakai ulang apa yang dikatakan setelahnya :

Pengguna: "hello"
  ↓
Mencari: apakah seseorang pernah berkata "hello" sebelumnya?
  ↓
Ya, di sesi #3, baris 14, seseorang berkata "hello" dan bot menjawab "hi there!"
  ↓
Menjawab: "hi there!"

Kamu hanya menemukan di sini arsip raksasa hal yang dikatakan orang, dipakai ulang pada momen tepat. Saya melihat di sana definisi emergensi itu sendiri : perilaku percakapan lahir dari akumulasi jejak manusia.

Implementasi TypeScript

Port TypeScript saya mereproduksi tepat arsitektur ini :

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 lignes seed + historique"]
    B --> C["withReplies()<br>extrait les paires<br>(ligne → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>konteks sebelum baris ini)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidats triés"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>de la paire gagnante"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append au transcript<br>save() → JSON"]
    L --> M

Berikut inti scoring, dengan heuristik saya sendiri yang terinspirasi deskripsi publik Cleverbot :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : kemiripan antara masukan pengguna dan baris historis
  • contextFit (0.25) : kemiripan antara percakapan terbaru dan apa yang mendahului baris historis
  • recencyBonus (0.10) : kenangan terbaru sedikit lebih dihitung (kepribadian bot melayang seiring waktu)

Pick-nya probabilistik (roulette-wheel selection) : kandidat terbaik lebih sering menang, dengan bagian variabilitas yang memberi variasi. Saya menghargai pilihan ini karena membuat percakapan kurang bisa ditebak ketika kamu menguji bot berkali-kali.

Cleverbot : dua inovasi yang terdokumentasi

Cleverbot menambahkan dua mekanisme pada konsep dasar Jabberwacky :

  1. Pembelajaran multi-orang : jutaan pengguna berkontribusi pada transkrip bersama yang sama. Respons yang diambil dari riwayat bisa berasal dari suara yang sama sekali berbeda dari percakapan berjalan -- yang menjelaskan kenapa Cleverbot tiba-tiba berganti kepribadian.

  2. Pembelajaran tertunda : apa yang kamu katakan ke Cleverbot selama sesi TIDAK tersedia untuk dicocokkan selama sesi yang sama itu. Baris baru ditandai pending dan baru bisa dicocokkan setelah "konsolidasi" antar sesi -- yang menjelaskan kenapa kamu tidak bisa mengajarkan fakta ke Cleverbot lalu memakainya ulang dalam percakapan yang sama.

// Cleverbot : baris terbaru tak terlihat hingga konsolidasi
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() dipanggil saat startup, bukan selama sesi

Port TypeScript saya mengimplementasikan kedua perilaku ini : baris punya flag consolidated, dan setiap sesi REPL dimulai dengan konsolidasi baris yang menunggu.

➡ Lihat kode sumber


Analisis port TypeScript : merancang arsitektur bersama

Membangun kelima bot ini dalam bahasa yang sama menghadapkan saya pada pertanyaan menarik : mungkinkah memfaktorkan kode di antara arsitektur yang begitu berbeda?

Jawaban saya setelah mem porting semuanya : sangat sedikit. Setiap bot punya loop fundamental berbeda :

Bot Loop utama Data Pembelajaran
ELIZA Keyword stack → dekomposisi → reassembly Skrip .ela dalam S-expression Tidak ada
PARRY Tokenisasi → pola selektif / flare / keyword / inferensi 58 berkas PDP-10 (kamus, keyakinan, aturan) Tidak ada
ALICE Pola diurutkan → regex → template AIML → SRAI rekursif 66 berkas AIML XML Tidak ada
Jabberwacky Kemiripan → konteks → recency → pick berbobot Transkrip JSON (tumbuh dengan pemakaian) Kontinu
Cleverbot Sama seperti Jabberwacky + pending/consolidated + persona Transkrip JSON + seed multi-persona Tertunda (antar sesi)

Yang mereka bagikan adalah antarmuka CLI dan infrastruktur TypeScript (biome untuk lint, tsx untuk eksekusi). Sisanya spesifik tiap arsitektur.

Pilihan desain bersama

1. Kesetiaan pada data asli. Untuk ELIZA, PARRY dan ALICE, saya memakai berkas aslinya, skrip ELIZA yang ditemukan kembali dalam arsip Weizenbaum pada 2021, kode asli PARRY dari PDP-10 (58 berkas), AIML Free ALICE v1.6. Saya menyimpannya dalam bentuk aslinya. Bot berperilaku seperti aslinya karena mereka memakai data yang sama.

2. Clean-room untuk bagian proprietari. Jabberwacky dan Cleverbot menimbulkan kasus berbeda : kode sumber mereka tidak pernah diterbitkan (Existor/Rollo Carpenter menyimpannya proprietari). Jadi port saya adalah reimplementasi clean-room, dibangun hanya dari deskripsi publik perilaku. Saya tidak menyalin satu baris kode maupun data proprietari.

3. Dependensi minimal. Satu-satunya prasyarat nyata adalah TypeScript. ALICE memakai dom-js untuk mem parsing XML berkas AIML (66 berkas, 99 524 kategori, parsing XML buatan sendiri hanya buang waktu). Sisanya vanilla TypeScript. Saya membuat pilihan ini agar setiap bot tetap mudah dibaca dan mudah dijalankan ketika kamu mengkloning repo.


Dari chatbot simbolis ke LLM : lompatan konseptual

Kelima bot yang baru saya tunjukkan berbagi ciri fundamental : mereka simbolis. "Pengetahuan" mereka sepenuhnya bersandar pada simbol eksplisit : motif tekstual, tabel aturan, kategori XML, baris transkrip. Mereka berfungsi sepenuhnya tanpa representasi numerik bahasa.

Jadi mereka berbagi plafon kaca yang sama : mereka hanya menjawab apa yang eksplisit direncanakan atau direkam. ELIZA tersesat kalau kamu keluar dari bingkai terapeutik. PARRY hanya bicara soal tema paranoidnya. ALICE tidak belajar apa pun dari percakapannya. Jabberwacky hanya menjawab dengan replika yang sudah diucapkan. Ketika kamu menguji batas ini, kamu cepat paham kenapa generasi ini mentok.

LLM (Large Language Models) melompati plafon ini dengan mengubah paradigma secara radikal : alih-alih memanipulasi simbol, mereka mengubah bahasa menjadi angka dan mempelajari relasi statistik antara angka ini. Mereka membangkitkan tiap token langsung dengan menghitung probabilitas, dari pengetahuan yang dienkode dalam bobotnya alih-alih respons yang sudah ditulis. Saya menganggap peralihan ini menarik karena ia membalik total logika bot sebelumnya. Mari lihat cepat cara kerjanya.

1. Tokenisasi

Langkah pertama adalah memotong teks menjadi token -- unit yang lebih kecil dari kata tapi lebih besar dari karakter :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Setiap token punya ID numerik dalam kosakata (umumnya 32 000 hingga 128 000 token untuk model terbaru). Fragmentasi ini memungkinkan model menangani kata yang tidak pernah dilihatnya dengan menguraikannya menjadi sub-kata yang dikenal.

2. Embedding

Setiap ID token diubah menjadi vektor -- tabel angka float (umumnya 4096 dimensi untuk model ukuran sedang). Vektor ini adalah plongement (embedding) yang mengenkode makna token dalam ruang matematis di mana token yang semantiknya dekat punya vektor yang dekat :

vektor("roi")  − vektor("homme") + vektor("femme")  ≈  vektor("reine")

Sifat ini muncul dari pelatihan, tanpa pemrograman eksplisit dari saya atau para peneliti. Saya melihat di sana akibat langsung dari cara kata muncul dalam konteks mirip.

3. Atensi

Mekanisme atensi (diperkenalkan lewat makalah "Attention is All You Need" pada 2017) inilah yang membuat LLM mungkin. Untuk setiap token, atensi menghitung token lain mana dalam kalimat yang penting untuk memahami yang ini :

"La banque a refusé mon prêt."
      ↑
Token "banque" melihat: "refusé", "prêt" → paham ini lembaga keuangan

"Je vais me promener sur la banque."
      ↑
Token "banque" melihat: "promener", "sur" → paham ini tepi sungai

Atensi memungkinkan model menangkap konteks : setiap token dipahami menurut yang mengelilinginya, dalam bacaan yang sepenuhnya relasional. Kepekaan pada konteks inilah yang paling membantu saya memahami kenapa LLM melampaui pola tetap.

4. Prediksi token berikutnya

Pelatihan LLM bersandar pada kesederhanaan yang menipu : kamu menunjukkan teks padanya, kamu sembunyikan token terakhir, dan kamu memintanya memprediksinya. Lalu kamu ulangi operasinya miliaran kali. Saya suka ide ini karena aturan belajar yang sangat sederhana menghasilkan kemampuan yang sangat kaya.

Input:  "Je ne comprends"
Disembunyikan:  "pas"
Prediksi model: "pas" (probabilitas 0.87), "rien" (0.05), "jamais" (0.02)...

Tujuannya adalah memaksimalkan probabilitas token benar di setiap posisi. Inilah yang disebut prediksi token berikutnya. Selama pelatihan, model menyesuaikan miliaran parameternya untuk meminimalkan error prediksi pada terabita teks.

Pada momen inferensi (ketika kamu berbicara dengannya), model membangkitkan satu token demi satu dalam loop :

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

Setiap token disampel menurut probabilitasnya (temperatur, top-k, top-p mengendalikan derajat "kreativitas"). Dan itu saja. Miliaran parameter yang mengulangi operasi ini ribuan kali. Saya menganggap pusing bahwa mekanisme yang begitu repetitif menghasilkan respons yang begitu bervariasi.

Apa yang berubah secara fundamental

Aspek Bot simbolis (ELIZA, PARRY, ALICE) LLM modern
Representasi Kata dan aturan eksplisit Vektor numerik (embedding)
Pembangkitan Seleksi dalam respons yang sudah ditulis Prediksi probabilistik token per token
Pengetahuan Disimpan dalam berkas aturan Dienkode dalam bobot jaringan
Pembelajaran Manual (penulisan aturan) Otomatis (pelatihan pada korpus)
Ketangguhan Nol di luar motif yang direncanakan Menggeneralisasi ke masukan yang tak pernah dilihat
Interpretabilitas Sempurna (aturannya bisa dibaca) Terbatas (kotak hitam)

Chatbot klasik transparan tapi rapuh. LLM tangguh tapi opak. Saya melihat mereka hari ini sebagai alat komplementer untuk kebutuhan berbeda, dan saya memilih satu atau lainnya menurut tingkat kontrol yang ingin kamu jaga atas respons.

Kalau kamu ingin mendalami cara kerja internal LLM, saya merekomendasikan video ini sebagai sumber bagus :

How LLMs Work - YouTube

Luna Protocol : sintesis modern

Artikel tentang Luna Protocol (yang tautannya di bawah) mewakili menurut saya sintesis paling matang dari semua yang baru saya tunjukkan : bot Discord modern yang menggabung LLM lokal dengan sistem perilaku canggih, semuanya dibangun di atas pelajaran 60 tahun AI percakapan. Proyek inilah yang memungkinkan saya menghubungkan port historis saya ke eksperimen saya saat ini.

Luna Protocol : saya menciptakan bot Discord otonom yang mensimulasikan manusia

Artikel ini merinci arsitektur lengkap bot Discord berbasis LLM :

  • Sistem pemicu berprioritas (mention > DM > nama > kata kunci > follow-up > acak)
  • Perilaku manusia : konsentrasi variabel, salah ketik, keraguan (15%), lupa (3%), kelelahan tematis
  • Jadwal tidur : bot tidur, melambat, atau mengabaikan menurut jam
  • Pipeline TTS : sintesis vokal lewat Piper + ffmpeg → pesan vokal Discord
  • Streaming waktu nyata : LLM memancarkan token satu per satu di bus event bertipe

Yang menghubungkan artikel ini ke chatbot historis adalah pencarian yang sama : membuat orang percaya sedang bicara dengan seseorang. ELIZA memakai cermin tekstual. PARRY memakai model emosional. ALICE memakai 99k kategori. Luna Protocol memakai LLM yang di fine-tune + sistem perilaku yang mensimulasikan ketidaksempurnaan manusia. Saya menemukan kembali dalam silsilah ini tuntutan yang sama : menciptakan ilusi kehadiran di seberang layar.

Luna Protocol : kenapa saya men fine-tune model 1,5B

Artikel kedua menjelajahi fine-tuning dan few-shot priming. Penemuan sentral saya : model lebih kecil (1,5B) yang dilatih pada lebih sedikit data (50k sampel) melampaui model lebih besar (3B) ketika di priming benar dengan contoh few-shot. Saya benar-benar terkejut oleh hasil ini ketika mengukurnya.

Ini pelajaran yang bergema langsung dengan chatbot historis :

  • ELIZA menunjukkan bahwa dengan beberapa aturan yang dirancang baik, kita bisa mensimulasikan pemahaman
  • ALICE menunjukkan bahwa dengan 99k kategori, kita bisa mensimulasikan budaya umum
  • Luna Protocol menunjukkan bahwa dengan fine-tuning bagus dan 5 contoh few-shot, LLM kecil bisa mensimulasikan manusia

Tekniknya berubah, dan prinsipnya tetap sama : kualitas data dan ketepatan sistem lebih penting daripada ukuran mentah. Ini keyakinan yang saya bela dalam eksperimen saya sendiri.


Kesimpulan : tiga hal yang perlu diingat

1. AI percakapan tidak dimulai dengan ChatGPT. ELIZA berumur 60 tahun. PARRY lolos tes Turing pada 1972. ALICE memenangkan Loebner tiga kali. Jabberwacky meletakkan dasar pembelajaran lewat transkrip, yang diindustrialisasi Cleverbot dalam skala besar. Setiap pendekatan membawa satu keping puzzle. Itulah yang dikonfirmasi port saya.

2. Lebih banyak data ≠ lebih cerdas. Transkrip Jabberwacky tidak punya aturan. 99k kategori ALICE tidak belajar. Fine-tuning Luna Protocol pada 50k sampel melampaui model 3B. Kebijaksanaan konvensional bilang "makin besar makin baik" -- sejarah chatbot menunjukkan bahwa arsitektur dan desain sama pentingnya dengan ukuran. Saya membela ide ini dalam setiap pilihan teknis saya.

3. Masalahnya sama sejak 60 tahun. Bagaimana membuat manusia percaya ia bicara dengan manusia lain? ELIZA menjawab dengan cermin tekstual. PARRY dengan kemarahan simulasi. ALICE dengan fakta. Luna Protocol dengan LLM yang tidur dan salah ketik. Solusinya berubah, kebutuhannya tetap. Kontinuitas inilah yang membuat saya ingin terus menjelajah.

Repo-nya open source, kamu bisa mengkloningnya, menjalankan setiap bot, dan melihat sendiri bagaimana 60 tahun AI percakapan muat dalam satu depot TypeScript. Saya harap kamu bersenang-senang seperti saya membuatnya bicara.

Sumber daya Tautan
Depot GitHub fox3000foxy/chatbots
Luna Protocol -- arsitektur bot Baca artikel
Luna Protocol -- fine-tuning few-shot Baca artikel
Skrip ELIZA asli anthay/ELIZA
Kode sumber PARRY asli lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 asli PARRY Encounters the DOCTOR
Penjelasan bagus cara kerja LLM https://www.youtube.com/watch?v=YmLp8qe87A0

ELIZA से LLM तक : TypeScript में पुनर्निर्मित 60 साल की संवादात्मक AI

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot, उसी समस्या की पाँच मौलिक अलग आर्किटेक्चर, मूल डेटा के साथ TypeScript में लाई गई। 1966 से आधुनिक LLM तक, संवादात्मक AI ने बोलना कैसे सीखा, और chatbot repo 60 साल के शोध के बारे में क्या सिखाता है।

ELIZA से LLM तक : TypeScript में पुनर्निर्मित 60 साल की संवादात्मक AI

1966 में, Joseph Weizenbaum इतिहास का पहला chatbot बनाने के लिए IBM 7094 पर MAD-SLIP की 420 पंक्तियाँ लिख रहे थे। प्रोग्राम का नाम ELIZA था, और वह बुनियादी पैटर्न और वाक्य क्रमपरिवर्तन से rogérienne मनोचिकित्सक का अनुकरण करता था। छह दशक बाद, संवादात्मक AI सार्वजनिक विषय बन गया है, ChatGPT, Claude, Gemini हर बातचीत में हैं। जब मैंने मूल कोड फिर से पढ़ा, तो मैं उसके साधनों की किफायत से प्रभावित हुआ।

लेकिन इन दो चरम के बीच, PARRY (पागल chatbot, 1972), ALICE (99 000 श्रेणियों वाला AIML राजा, 1995), Jabberwacky (नियमों के बिना सीखने वाला पहला, 1997), और Cleverbot (उसका औद्योगिक उत्तराधिकारी, 2008) थे। मैं उन्हें उसी समस्या के पाँच मौलिक अलग उत्तर मानता हूँ : मशीन को बुलवाना।

मैंने इन पाँच बॉट को उनके मूल डेटा के साथ TypeScript में लाया है, ELIZA स्क्रिप्ट, PARRY शब्दकोश, ALICE की AIML फाइलें। हर port स्वायत्त, उपयोग को तैयार, और विस्तार से प्रलेखित है। इस काम से मेरा उद्देश्य साधारण निष्पादन से परे है : मैं समझना चाहता हूँ कि वे कैसे काम करते थे, उन्होंने इतिहास में निशान क्यों छोड़ा, और उनकी संबंधित आर्किटेक्चर कल की AI के बारे में क्या बताती हैं... और आज की।

bun run eliza    # ELIZA से बात करो (1966)
bun run parry    # PARRY से बात करो (1972)
bun run alice    # ALICE से बात करो (1995)
bun run jabber   # Jabberwacky से बात करो
bun run cleverbot # Cleverbot से बात करो
bun run meeting  # स्वचालित ELIZA बनाम PARRY

मैं तुम्हारे साथ हर बॉट की चीड़फाड़ करूँगा, उनका कोड देखूँगा, फिर Luna Protocol पर लेखों के माध्यम से आधुनिक LLM सेतु बनाऊँगा।


ELIZA (1966) : समझने का विश्वास दिलाने की कला

मैं सबसे पुरानी से शुरू करता हूँ, और मेरी नजर में अपनी सरलता से सबसे प्रभावशाली। ELIZA के पास आधुनिक अर्थ में कोई बुद्धि नहीं है। वह सिर्फ पाठ्य पैटर्न और थोड़े क्रमपरिवर्तन से काम करती है।

सिद्धांत

DOCTOR स्क्रिप्ट (मनोचिकित्सक संस्करण) keyword तालिका से काम करती है, हर एक विघटन पैटर्न और पुनर्संयोजन नियमों से जुड़ा। यहाँ ठेठ नियम है :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO keyword है। 0 विघटन पैटर्न है जो कहता है "जो अनुसरण करता है सब पकड़ो" (wildcard जैसा)। HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. पुनर्संयोजन नियम है। बस इतना।

जब तुम कहते हो "Hello, I'm sad today", ELIZA :

  1. पाठ बड़े अक्षरों में करती है : HELLO I'M SAD TODAY
  2. हर शब्द keyword तालिका से scan करती है
  3. HELLO पाती है → keyword stack पर धकेलती है
  4. उच्चतम प्राथमिकता वाला keyword लेती है
  5. क्रम से हर विघटन पैटर्न आजमाती है
  6. अगर मेल खाता है, तो अगला पुनर्संयोजन नियम चुनती है (round-robin)
  7. (1), (2) आदि को पकड़े हिस्सों से बदलती है

लेकिन सच में बुद्धिमान हिस्सा PRE नियम हैं। यह देखो :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

जब ELIZA MY मिलाती है, तो वह 0 से पकड़े वाक्य बाकी को PRE नियम से बदलती है, और परिणाम फिर से डालती है जैसे उपयोगकर्ता ने नया keyword कहा हो। ठोस रूप में :

तुम कहते हो: "My mother hates me"
  → PRE बदलता है: "YOUR MOTHER HATES YOU"
  → फिर से डाला गया जैसे तुमने अभी कहा हो
  → शायद "YOU" मिलता है → नया उत्तर

इसीलिए ELIZA "मैं" और "तुम" के बीच अंतर समझने का आभास देती है : मैं वहाँ यंत्रवत रूप से पूर्ण डिजाइन किया परिवर्तन देखता हूँ जो समझ का अनुकरण करता है।

उपयोगकर्ता टाइप से उत्तर तक पूरा प्रवाह यहाँ है :

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>विराम चिह्न सामान्य करता है"]
    B --> C["splitUserInput()<br>शब्दों में काटता है"]
    C --> D["Build keyword stack<br>प्राथमिकता क्रम में"]
    D --> E{"Stack खाली नहीं?"}
    E -->|"हाँ"| F["Pop highest-priority keyword"]
    E -->|"नहीं"| G{"Memory recall?"}
    G -->|"हाँ"| H["Recall past user statement"]
    G -->|"नहीं"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"नहीं"| M{"Linked keyword?"}
    M -->|"हाँ"| N["Push linked keyword to stack"]
    N --> E
    M -->|"नहीं"| O["Return NOMATCH"]
    O --> J
    L -->|"हाँ"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

उसे विश्वसनीय क्या बनाता था

मैं Weizenbaum चुनाव को प्रतिभा आघात मानता हूँ : rogérienne मनोचिकित्सा। यह दृष्टिकोण व्याख्या किए बिना रोगी की बात प्रतिबिंबित करता है। "मैं उदास हूँ" → "तुम कहते हो कि तुम उदास हो"। ठीक वही है जो ELIZA करना जानती है, और चूँकि यह चिकित्सीय तकनीक मान्यता प्राप्त है, किसी को उसका उत्तर अजीब नहीं लगता। जब तुम बॉट परखते हो, तो तुम सीधे यह दर्पण प्रभाव महसूस करते हो।

TypeScript port में

मेरे port में, मैं .ela स्क्रिप्ट लोड करता हूँ (मूल S-expression प्रारूप), उन्हें पूरी तरह parse करता हूँ (Hollerith एन्कोडिंग सहित, 60 के दशक का यह स्ट्रिंग प्रारूप), और वही चक्र निष्पादित करता हूँ : uppercasing → split → keyword stack → विघटन → पुनर्संयोजन → PRE/transform। मैं विवरण तक मूल व्यवहार के प्रति वफादार रहना चाहता था।

➡ सोर्स कोड देखें


PARRY (1972) : भावनाओं वाला पहला chatbot

ELIZA के छह साल बाद, Kenneth Colby (Stanford में मनोचिकित्सक) ने PARRY बनाया : chatbot जो पागल schizophrenia रोगी का अनुकरण करता है। जहाँ ELIZA खाली दर्पण की तरह काम करता था, PARRY असली आंतरिक भावनात्मक मॉडल रखता है। यही विरोधाभास है जिसने उन्हें एक के बाद एक लाने में मुझे सबसे ज्यादा प्रभावित किया।

भावनात्मक मॉडल

PARRY के पास चार सतत चर हैं जो बातचीत के हर मोड़ पर विकसित होते हैं :

चर आधार रेखा क्षय/मोड़ विवरण
ANGER 0 −1.0 शत्रुता, चिड़चिड़ाहट
FEAR 0 −0.2 पागलपन (प्रलाप शुरुआत के बाद धीरे घटता है)
MISTRUST 0 −0.05 अविश्वास (बहुत धीरे नीचे आता है)
HURT 0 −0.5 भावनात्मक पीड़ा

ये मान अनुमान नियमों से ट्रिगर भावनात्मक छलांग (ajump, fjump, hjump) से बढ़ते हैं, और हर मोड़ पर अपनी आधार रेखाओं की ओर स्वाभाविक घटते हैं।

विश्वास नेटवर्क

PARRY के पास bel फाइल में संग्रहित 200+ विश्वास हैं :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

हर विश्वास की श्रेणी है (HUM = रोगी, HUM2 = अन्य, DOC = डॉक्टर, INT = पूछताछ, INN = इरादे) और शक्ति (0-5)। अनुमान नियम (TH2, EMOTE, IF) विश्वासों को आपस में फैलाते हैं :

  • TH2 : अगर विश्वास A सीमा पार करता है, तो वह मजबूत होता है और उसके परिणाम बढ़ते हैं
  • EMOTE : अगर विश्वास सीमा पार करता है, तो वह भावनात्मक छलांग ट्रिगर करता है (anger/fear/hurt)
  • IF : सशर्त, अगर A सत्य है, तो B निश्चित स्तर पर सत्य हो जाता है

प्रलाप पदानुक्रम (flare तंत्र)

PARRY का सबसे आकर्षक हिस्सा उसका "flare" तंत्र है, वृद्धि शृंखला जो धीरे धीरे केंद्रीय प्रलाप की ओर ले जाती है :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

हर keyword पूर्व लिखित उत्तर ट्रिगर करता है (पैटर्न मिलान से), और अगर तुम विषय का अनुसरण करते हो, तो PARRY धीरे धीरे अपने केंद्रीय उत्पीड़न प्रलाप की ओर बहता है। एक बार flare "ट्रिगर" होने पर, वह निष्क्रिय हो जाता है (deadFlares) और PARRY अगले पर जाता है, वार्ताकार की तरह जो अपना विषय खोदता है। मुझे यह यांत्रिकी बहुत पसंद है क्योंकि वह बहुत कम कोड से कथात्मक प्रगति बनाती है।

PARRY ऐतिहासिक क्यों है

1972 में, PARRY ने Turing परीक्षण का रूपांतर पास किया : मनोचिकित्सकों ने PARRY और असली पागल रोगियों के साथ बातचीत की प्रतिलिपियाँ पाईं, और उन्हें विश्वसनीय रूप से अलग नहीं कर पाए। यह पहला प्रोग्राम है जो वहाँ पहुँचा, LLM से 40 साल पहले। जब तुम उस समय के साधनों की आज से तुलना करते हो तो मुझे यह कारनामा उल्लेखनीय लगता है।

➡ सोर्स कोड देखें


RFC 439 : जब ELIZA PARRY से मिलती है

18 सितंबर 1972 को, दो प्रोग्राम इतिहास में पहली बार सचमुच बोले : यह अब तक दर्ज पहली अंतर AI बातचीत है। ELIZA (Boston में BBN पर चल रहा) और PARRY (Stanford में चल रहा) ने ARPANET से बातचीत की, मानव ऑपरेटरों से relay किए गए जो एक का output दूसरे के input में टाइप करते थे। प्रतिलिपि RFC 439 ("PARRY Encounters the DOCTOR") के रूप में प्रकाशित हुई। जब मैंने यह दस्तावेज खोजा, तो मैं समझा कि यह हमारे क्षेत्र के लिए संस्थापक क्षण चिह्नित करता है।

यह कैसा लगता था इसका अंश यहाँ है :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

पहले पढ़ने पर इस आदान प्रदान की सुसंगति ने मुझे चौंकाया। ELIZA चिकित्सक का काम करता है : फिर से कहना, प्रश्न करना, खोजना। PARRY पागल रोगी का काम करता है : शिकायत करना, आरोप लगाना, अविश्वास व्यक्त करना। दोनों प्रोग्राम अपने संबंधित तंत्र संयोजन (ELIZA पैटर्न + PARRY भावनात्मक मॉडल) से अपनी भूमिका में पूरी तरह रहते हैं जो संयोग से जुड़ने वाले उत्तर बनाता है। जब तुम पूरी प्रतिलिपि पढ़ते हो, तो देखते हो कि यह आकस्मिक संरेखण जानबूझकर संवाद का भ्रम कैसे बनाता है।

तुम मेरे repo से यह बातचीत फिर से बना सकते हो :

bun run meeting

मेरा simulation दोनों बॉट के बीच 25 स्वचालित मोड़ चलाता है, बेतरतीब शुरुआती विषय (घोड़े, संगठित अपराध, भावनाएँ...) के साथ। चूँकि ELIZA और PARRY दोनों अनियतात्मक तत्व रखते हैं (ELIZA round-robin, PARRY बेतरतीबता), हर निष्पादन अलग आदान प्रदान बनाता है। प्रक्षेपवक्र तुलना करने के लिए कमांड कई बार फिर से चलाना मुझे पसंद है।

ELIZA बनाम PARRY से मुझे जो चुभता है, वह दो प्रोग्रामों की मुलाकात है, एक बिना आंतरिक स्थिति वाली आर्किटेक्चर, दूसरा पूर्ण भावनात्मक मॉडल वाला, जो एक साथ बातचीत बनाते हैं जो जानबूझकर लगती है। 1972 के लिए, मुझे परिणाम विस्मयकारी लगता है।


ALICE (1995) : बड़े पैमाने पर पैटर्न मिलान

ALICE (Artificial Linguistic Internet Computer Entity) Richard Wallace ने 1995 में बनाई थी, और उसने Loebner Prize तीन बार जीता (2000, 2001, 2004)। जहाँ ELIZA के कुछ सौ नियम और PARRY के कुछ हजार थे, ALICE के 99 524 हैं, 66 AIML फाइलों में बँटे। जब मैंने पहली बार यह corpus खोला, तो मैंने मापा कि उस समय "बड़े पैमाने" का क्या मतलब था।

AIML : श्रेणियों की भाषा

AIML (Artificial Intelligence Markup Language) प्रश्न उत्तर जोड़े परिभाषित करने का XML प्रारूप है :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

लेकिन ALICE की शक्ति wildcard और SRAI (Symbolic Reduction) से आती है :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- <srai><star/></srai> के बराबर -->
  </template>
</category>

SRAI ALICE को प्रविष्टि दूसरी श्रेणी की ओर redirect करने देता है, न्यूनीकरण शृंखला बनाता है :

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

यही तंत्र ALICE को लचीलापन देता है : हर संभव सूत्रीकरण के लिए उत्तर लिखने के बजाय, तुम विहित उत्तर लिखते हो और रूपों को उसकी ओर redirect करते हो। गहराई सीमा 10 है : उसके परे, ALICE अनंत लूप से बचने के लिए छोड़ देती है (श्रेणी डिजाइन उन्हें सावधानी से रोकता है, और यह सुरक्षा जाल जरूरी रहता है)। मुझे यह डिजाइन चुनाव सुरुचिपूर्ण लगता है क्योंकि यह बहुत कम नियमों से हजारों सूत्रीकरण factorize करता है।

ALICE पैटर्न कैसे मिलाती है

पैटर्न विशिष्टता से छाँटे जाते हैं : सबसे कम wildcard वाले पहले आजमाए जाते हैं। wildcard * और _ शब्दों का कोई भी अनुक्रम पकड़ते हैं। इंजन हर पैटर्न को regex में संकलित करता है, फिर मेल मिलने तक छँटी श्रेणियाँ दोहराता है।

// हमारा TypeScript कार्यान्वयन, सरलीकृत लेकिन सटीक
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

ALICE Loebner पर क्यों हावी रही

99 524 श्रेणियाँ, यह संख्या है जो सब बदलती है। ELIZA बुद्धि का भ्रम देता था क्योंकि उसके कुछ नियम विशिष्ट संदर्भ (चिकित्सा) के लिए अच्छी तरह डिजाइन किए थे। ALICE इतने विषय कवर करती है कि वह असली सामान्य संस्कृति का आभास देती है : विज्ञान, राजनीति, हास्य, खेल, भावनाएँ, सब है। इस दृष्टिकोण से मैं एक सबक रखता हूँ जो मैं आज भी लागू करता हूँ : व्यापक और सावधान कवरेज गहरे तर्क की अनुपस्थिति की भरपाई करता है।

➡ सोर्स कोड देखें


Jabberwacky (1997) और Cleverbot (2008) : ज्ञानमीमांसीय टूट

पिछले सभी बॉट एक परिकल्पना साझा करते हैं : उत्तर लिखने होंगे। ELIZA के S-expression नियम हैं, PARRY के चयनात्मक पैटर्न, ALICE की AIML श्रेणियाँ। Rollo Carpenter ने मौलिक उल्टा लिया, मौलिक प्रश्न के साथ : क्या हो अगर पिछली बातचीत उत्तर दें ? मुझे यह उलट साहसी लगता है क्योंकि यह नियमों का कोई मैन्युअल लेखन छोड़ता है।

विचार

Jabberwacky (1997 के आसपास शुरू, 2008 में Cleverbot बना) पूरी तरह सपाट transcript में संग्रहित पूरे बातचीत इतिहास पर टिका है, और जब कोई उससे बात करता है, तो वह इस इतिहास में सबसे समान क्षण खोजता है और उसके बाद जो कहा गया था उसे फिर से इस्तेमाल करता है :

उपयोगकर्ता: "hello"
  ↓
खोजो: क्या किसी ने पहले "hello" कहा है ?
  ↓
हाँ, सत्र #3 में, पंक्ति 14, किसी ने "hello" कहा था और बॉट ने "hi there!" जवाब दिया था
  ↓
जवाब दो: "hi there!"

तुम्हें यहाँ उन चीजों का विशाल अभिलेख ही मिलेगा जो लोगों ने एक दूसरे से कही थीं, उपयुक्त क्षण पर फिर से इस्तेमाल की गई। मैं इसमें उद्भव की परिभाषा देखता हूँ : मानव निशानों के संचय से संवादात्मक व्यवहार जन्मता है।

TypeScript कार्यान्वयन

मेरा TypeScript port ठीक यह आर्किटेक्चर फिर से बनाता है :

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 seed पंक्तियाँ + इतिहास"]
    B --> C["withReplies()<br>जोड़े निकालता है<br>(पंक्ति → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>इस पंक्ति से पहले संदर्भ)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K छँटे उम्मीदवार"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = जीतने वाली जोड़ी का reply.text"]
    J -->|"कोई नहीं"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["transcript में जोड़ो<br>save() → JSON"]
    L --> M

scoring का हृदय यहाँ है, Cleverbot के सार्वजनिक विवरणों से प्रेरित मेरी खुद की heuristic के साथ :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : उपयोगकर्ता प्रविष्टि और ऐतिहासिक पंक्ति के बीच समानता
  • contextFit (0.25) : हाल बातचीत और ऐतिहासिक पंक्ति से पहले जो था उसके बीच समानता
  • recencyBonus (0.10) : हाल स्मृतियाँ थोड़ी ज्यादा गिनती हैं (समय के साथ बॉट व्यक्तित्व बहता है)

pick संभाव्य है (roulette-wheel selection) : सबसे अच्छा उम्मीदवार अक्सर जीतता है, विविधता वाले हिस्से के साथ जो विविधता देता है। मुझे यह चुनाव पसंद है क्योंकि जब तुम बॉट कई बार परखते हो तो यह बातचीत कम पूर्वानुमेय बनाता है।

Cleverbot : दो प्रलेखित नवाचार

Cleverbot Jabberwacky की बुनियादी अवधारणा में दो तंत्र जोड़ता है :

  1. बहु व्यक्ति सीखना : लाखों उपयोगकर्ता उसी साझा transcript में योगदान करते हैं। इतिहास से निकाला उत्तर चल रही बातचीत से पूरी तरह अलग आवाज से आ सकता है, जो समझाता है कि Cleverbot अचानक व्यक्तित्व क्यों बदलता है।

  2. विलंबित सीखना : सत्र के दौरान तुम Cleverbot से जो कहते हो वह उसी सत्र में match के लिए उपलब्ध नहीं है। नई पंक्तियाँ pending चिह्नित होती हैं और सत्रों के बीच "समेकन" के बाद ही matchable बनती हैं, जो समझाता है कि तुम Cleverbot को तथ्य नहीं सिखा सकते और उसी बातचीत में फिर से इस्तेमाल नहीं कर सकते।

// Cleverbot : समेकन तक हाल पंक्तियाँ अदृश्य हैं
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() सत्र के दौरान नहीं, स्टार्टअप पर बुलाई जाती है

मेरा TypeScript port दोनों व्यवहार लागू करता है : पंक्तियों में consolidated flag है, और हर REPL सत्र लंबित पंक्तियों के समेकन से शुरू होता है।

➡ सोर्स कोड देखें


TypeScript port विश्लेषण : सामान्य आर्किटेक्चर डिजाइन करना

इन पाँच बॉट को उसी भाषा में बनाने ने मुझे दिलचस्प प्रश्न से टकराया : इतनी अलग आर्किटेक्चर के बीच कोड factorize करना क्या संभव है ?

उन सबको लाने के बाद मेरा उत्तर : बहुत कम। हर बॉट का मौलिक लूप अलग है :

बॉट मुख्य लूप डेटा सीखना
ELIZA Keyword stack → विघटन → पुनर्संयोजन S-expression में .ela स्क्रिप्ट कोई नहीं
PARRY Tokenisation → चयनात्मक पैटर्न / flare / keyword / अनुमान 58 PDP-10 फाइलें (शब्दकोश, विश्वास, नियम) कोई नहीं
ALICE छँटे पैटर्न → regex → AIML template → पुनरावर्ती SRAI 66 AIML XML फाइलें कोई नहीं
Jabberwacky समानता → संदर्भ → recency → भारित pick Transcript JSON (उपयोग से बढ़ता है) सतत
Cleverbot Jabberwacky जैसा + pending/consolidated + persona Transcript JSON + बहु persona seed विलंबित (सत्रों के बीच)

वे जो साझा करते हैं, वह CLI इंटरफेस और TypeScript बुनियादी ढाँचा है (lint के लिए biome, निष्पादन के लिए tsx)। बाकी हर आर्किटेक्चर के लिए विशिष्ट है।

सामान्य डिजाइन चुनाव

1. मूल डेटा के प्रति वफादारी। ELIZA, PARRY और ALICE के लिए, मैं मूल फाइलें इस्तेमाल करता हूँ, 2021 में Weizenbaum अभिलेख में मिली ELIZA स्क्रिप्ट, PDP-10 का मूल PARRY कोड (58 फाइलें), AIML Free ALICE v1.6। मैं उन्हें मूल रूप में रखता हूँ। बॉट मूल जैसे व्यवहार करते हैं क्योंकि वे वही डेटा इस्तेमाल करते हैं।

2. मालिकाना हिस्सों के लिए Clean-room। Jabberwacky और Cleverbot अलग मामला रखते हैं : उनका स्रोत कोड कभी प्रकाशित नहीं हुआ (Existor/Rollo Carpenter ने मालिकाना रखा)। इसलिए मेरे port clean-room पुनर्कार्यान्वयन हैं, सिर्फ व्यवहार के सार्वजनिक विवरणों से बनाए गए। मैंने कोड की कोई पंक्ति या मालिकाना डेटा कॉपी नहीं किया।

3. न्यूनतम निर्भरताएँ। एकमात्र असली पूर्वापेक्षा TypeScript है। ALICE AIML फाइलों के XML parse करने के लिए dom-js इस्तेमाल करती है (66 फाइलें, 99 524 श्रेणियाँ, घर का XML parsing समय बर्बादी होता)। बाकी सब vanilla TypeScript में है। मैंने यह चुनाव हर बॉट को पठनीय और repo clone करने पर चलाने में आसान रखने के लिए किया।


प्रतीकात्मक chatbot से LLM तक : अवधारणात्मक छलांग

जिन पाँच बॉट को मैंने तुम्हें दिखाया वे मौलिक विशेषता साझा करते हैं : वे प्रतीकात्मक हैं। उनका "ज्ञान" पूरी तरह स्पष्ट प्रतीकों पर टिका है : पाठ्य पैटर्न, नियम तालिकाएँ, XML श्रेणियाँ, transcript पंक्तियाँ। वे भाषा के किसी संख्यात्मक प्रतिनिधित्व के बिना पूरी तरह काम करते हैं।

वे इसलिए वही काँच छत साझा करते हैं : वे सिर्फ उसका उत्तर देते हैं जो स्पष्ट पूर्वानुमानित या दर्ज किया गया था। ELIZA खो जाती है अगर तुम चिकित्सीय ढाँचे से निकलते हो। PARRY सिर्फ अपने पागल विषयों की बात करता है। ALICE अपनी बातचीत से कुछ नहीं सीखती। Jabberwacky सिर्फ पहले बोली प्रतिकृतियों से जवाब देता है। जब तुम इन सीमाओं को परखते हो, तो जल्दी समझते हो कि यह पीढ़ी क्यों ठहर गई।

LLM (Large Language Models) प्रतिमान मौलिक बदलकर यह छत पार करते हैं : प्रतीक संभालने के बजाय, वे भाषा को संख्याओं में बदलते हैं और इन संख्याओं के बीच सांख्यिकीय संबंध सीखते हैं। वे भार में एन्कोड ज्ञान से संभावनाएँ गणना करके हर token तुरंत बनाते हैं, बजाय पूर्व लिखित उत्तरों के। मुझे यह बदलाव आकर्षक लगता है क्योंकि यह पिछले बॉटों के तर्क को पूरी तरह उलटता है। जल्दी देखते हैं यह कैसे काम करता है।

1. Tokenization

पहला चरण पाठ को token में काटना है, शब्दों से छोटी लेकिन अक्षरों से बड़ी इकाइयाँ :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

हर token का शब्दावली में संख्यात्मक ID होता है (हाल मॉडलों के लिए आमतौर पर 32 000 से 128 000 token)। यह विखंडन मॉडल को कभी न देखे शब्दों को ज्ञात उपशब्दों में तोड़कर संभालने देता है।

2. Embedding

हर token ID सदिश में बदला जाता है, चल संख्याओं की तालिका (मध्यम आकार मॉडल के लिए आमतौर पर 4096 आयाम)। यह सदिश समावेशन (embedding) है जो token अर्थ को गणितीय स्थान में एन्कोड करता है जहाँ अर्थतः निकट token के निकट सदिश होते हैं :

सदिश("roi")  − सदिश("homme") + सदिश("femme")  ≈  सदिश("reine")

यह गुण प्रशिक्षण से उभरता है, मेरी या शोधकर्ताओं की ओर से किसी स्पष्ट प्रोग्रामिंग के बिना। मैं इसमें सीधा परिणाम देखता हूँ कि शब्द समान संदर्भों में कैसे आते हैं।

3. Attention

attention तंत्र (2017 में "Attention is All You Need" पेपर से लाया गया) वही है जिसने LLM संभव बनाए। हर token के लिए, attention गणना करता है कि वाक्य में कौन से अन्य token इसे समझने के लिए महत्वपूर्ण हैं :

"La banque a refusé mon prêt."
      ↑
Token "banque" देखता है: "refusé", "prêt" → समझता है कि वित्तीय संस्था है

"Je vais me promener sur la banque."
      ↑
Token "banque" देखता है: "promener", "sur" → समझता है कि किनारा है

attention मॉडल को संदर्भ पकड़ने देता है : हर token उसे घेरने वालों के अनुसार समझा जाता है, पूर्ण संबंधपरक पढ़ने में। यही संदर्भ संवेदनशीलता है जिसने मुझे सबसे ज्यादा समझने में मदद की कि LLM स्थिर पैटर्न से आगे क्यों निकलते हैं।

4. अगले token की भविष्यवाणी

LLM प्रशिक्षण भ्रामक सरलता पर टिका है : तुम उसे पाठ दिखाते हो, तुम आखिरी token छिपाते हो, और तुम उसे भविष्यवाणी करने को कहते हो। फिर तुम ऑपरेशन अरबों बार दोहराते हो। मुझे यह विचार पसंद है क्योंकि बहुत सरल सीख नियम बहुत समृद्ध क्षमताएँ बनाता है।

Input:  "Je ne comprends"
छिपा:  "pas"
मॉडल भविष्यवाणी: "pas" (संभावना 0.87), "rien" (0.05), "jamais" (0.02)...

उद्देश्य हर स्थिति पर असली token की संभावना अधिकतम करना है। इसे next-token prediction कहते हैं। प्रशिक्षण के दौरान, मॉडल टेराबाइट पाठ पर भविष्यवाणी त्रुटि कम करने के लिए अपने अरबों पैरामीटर समायोजित करता है।

अनुमान समय (जब तुम उससे बात करते हो), मॉडल लूप में एक बार में एक token बनाता है :

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

हर token उसकी संभावना अनुसार नमूना लिया जाता है (तापमान, top-k, top-p "रचनात्मकता" मात्रा नियंत्रित करते हैं)। और बस इतना। अरबों पैरामीटर जो यह ऑपरेशन हजारों बार दोहराते हैं। मुझे चक्कर आता है कि इतना दोहराव तंत्र इतने विविध उत्तर बनाता है।

मौलिक रूप से क्या बदलता है

पहलू प्रतीकात्मक बॉट (ELIZA, PARRY, ALICE) आधुनिक LLM
प्रतिनिधित्व स्पष्ट शब्द और नियम संख्यात्मक सदिश (embedding)
पीढ़ी पूर्व लिखित उत्तरों में चयन token दर token संभाव्य भविष्यवाणी
ज्ञान नियम फाइलों में संग्रहित नेटवर्क भार में एन्कोड
सीखना मैन्युअल (नियम लेखन) स्वचालित (corpus पर प्रशिक्षण)
मजबूती नियत पैटर्न के बाहर शून्य कभी न देखी प्रविष्टियों पर सामान्यीकरण
व्याख्येयता पूर्ण (नियम पढ़े जा सकते हैं) सीमित (black box)

क्लासिक chatbot पारदर्शी लेकिन नाजुक हैं। LLM मजबूत लेकिन अपारदर्शी है। मैं उन्हें आज उत्तरों पर रखे नियंत्रण स्तर अनुसार एक या दूसरे चुनकर अलग जरूरतों के पूरक उपकरण मानता हूँ।

अगर तुम LLM आंतरिक कामकाज गहरा करना चाहते हो, तो मैं यह वीडियो उत्कृष्ट संसाधन के रूप में सुझाता हूँ :

How LLMs Work : YouTube

Luna Protocol : आधुनिक संश्लेषण

Luna Protocol पर लेख (जिनके लिंक नीचे हैं) मेरी नजर में जो कुछ मैंने तुम्हें दिखाया उसका सबसे परिपूर्ण संश्लेषण दर्शाते हैं : आधुनिक Discord बॉट जो स्थानीय LLM को परिष्कृत व्यवहार तंत्र से जोड़ता है, सब 60 साल की संवादात्मक AI के सबक पर बना। यह वह प्रोजेक्ट है जिसने मुझे अपने ऐतिहासिक port को अपने वर्तमान प्रयोगों से जोड़ने दिया।

Luna Protocol : मैंने स्वायत्त Discord बॉट बनाया जो मानव का अनुकरण करता है

यह लेख LLM आधारित Discord बॉट की पूरी आर्किटेक्चर विस्तार देता है :

  • प्राथमिकता trigger तंत्र (उल्लेख > DM > नाम > keyword > follow-up > बेतरतीब)
  • मानवीय व्यवहार : परिवर्तनीय एकाग्रता, टाइपो, हिचकिचाहट (15%), भूल (3%), विषयगत थकान
  • नींद समय : बॉट समय अनुसार सोता है, धीमा होता है, या नजरअंदाज करता है
  • TTS पाइपलाइन : Piper + ffmpeg से वाक् संश्लेषण → Discord ध्वनि संदेश
  • वास्तविक समय streaming : LLM typed घटना बस पर token दर token उत्सर्जित करता है

इस लेख को ऐतिहासिक chatbot से जोड़ने वाली बात वही खोज है : मनवाना कि हम व्यक्ति से बात कर रहे हैं। ELIZA पाठ्य दर्पण इस्तेमाल करता था। PARRY भावनात्मक मॉडल इस्तेमाल करता था। ALICE 99k श्रेणियाँ इस्तेमाल करता था। Luna Protocol fine-tune LLM + व्यवहार तंत्र इस्तेमाल करता है जो मानवीय अपूर्णताएँ अनुकरण करता है। मैं इस वंश में वही माँग पाता हूँ : स्क्रीन के दूसरी तरफ उपस्थिति का भ्रम बनाना।

Luna Protocol : मैंने 1,5B मॉडल fine-tune क्यों किया

दूसरा लेख fine-tuning और few-shot priming खोजता है। मेरी केंद्रीय खोज : कम डेटा (50k नमूने) पर प्रशिक्षित छोटा मॉडल (1,5B) बड़े मॉडल (3B) से बेहतर होता है जब उसे few-shot उदाहरणों से सही ढंग से शुरू किया जाता है। जब मैंने इसे मापा तो मैं सच में इस परिणाम से चौंका था।

यह सबक सीधे ऐतिहासिक chatbot से गूँजता है :

  • ELIZA दिखाता था कि कुछ अच्छी तरह डिजाइन नियमों से, समझ का अनुकरण किया जा सकता है
  • ALICE दिखाता था कि 99k श्रेणियों से, सामान्य संस्कृति का अनुकरण किया जा सकता है
  • Luna Protocol दिखाता है कि अच्छे fine-tuning और 5 few-shot उदाहरणों से, छोटा LLM मानव का अनुकरण कर सकता है

तकनीक बदलती है, और सिद्धांत वही रहता है : डेटा गुणवत्ता और तंत्र सटीकता कच्चे आकार से ज्यादा मायने रखते हैं। यह दृढ़ विश्वास है जिसका मैं अपने प्रयोगों में बचाव करता हूँ।


निष्कर्ष : याद रखने वाली तीन बातें

1. संवादात्मक AI ChatGPT से शुरू नहीं हुई। ELIZA 60 साल का है। PARRY ने 1972 में Turing परीक्षण पास किया। ALICE ने Loebner तीन बार जीता। Jabberwacky ने transcript सीखने की नींव रखी, जिसे Cleverbot ने बड़े पैमाने औद्योगिक बनाया। हर दृष्टिकोण पहेली का टुकड़ा लाया। मेरे port ने मुझे यही पुष्ट किया।

2. ज्यादा डेटा ≠ ज्यादा बुद्धिमान। Jabberwacky transcript में कोई नियम नहीं हैं। ALICE की 99k श्रेणियाँ नहीं सीखतीं। 50k नमूनों पर Luna Protocol fine-tuning 3B मॉडल से बेहतर है। परंपरागत बुद्धि कहती है "जितना बड़ा, उतना अच्छा", chatbot इतिहास दिखाता है कि आर्किटेक्चर और डिजाइन आकार जितना मायने रखते हैं। मैं अपने हर तकनीकी चुनाव में इस विचार का बचाव करता हूँ।

3. समस्या 60 साल से वही है। मानव को कैसे मनवाएँ कि वह दूसरे मानव से बात कर रहा है ? ELIZA पाठ्य दर्पणों से जवाब देता था। PARRY अनुकरण क्रोध से। ALICE तथ्यों से। Luna Protocol LLM से जो सोता है और टाइपो करता है। समाधान बदलता है, जरूरत रहती है। यही निरंतरता है जो मुझे खोज जारी रखने का मन देती है।

repo ओपन सोर्स है, तुम इसे clone कर सकते हो, हर बॉट चला सकते हो, और खुद देख सकते हो कि 60 साल की संवादात्मक AI एक ही TypeScript भंडार में कैसे समाती है। उम्मीद है तुम उन्हें बुलवाने में उतना मजा लोगे जितना मैंने लिया।

संसाधन लिंक
GitHub रिपॉजिटरी fox3000foxy/chatbots
Luna Protocol , बॉट आर्किटेक्चर लेख पढ़ें
Luna Protocol , fine-tuning few-shot लेख पढ़ें
ELIZA मूल स्क्रिप्ट anthay/ELIZA
मूल PARRY स्रोत कोड lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
मूल RFC 439 PARRY Encounters the DOCTOR
LLM कामकाज की उत्कृष्ट व्याख्या https://www.youtube.com/watch?v=YmLp8qe87A0

من ELIZA إلى LLM : 60 عاما من الذكاء الاصطناعي المحادثي، مُعاد بناؤها بـTypeScript

ELIZA وPARRY وALICE وJabberwacky وCleverbot، خمس بنى مختلفة جذريا للمشكلة نفسها، منقولة إلى TypeScript مع بياناتها الأصلية. من 1966 إلى LLM الحديثة، إليكم كيف تعلم الذكاء الاصطناعي المحادثي الكلام، وما يعلّمنا إياه مستودع شاتبوتات عن 60 عاما من البحث.

من ELIZA إلى LLM : 60 عاما من الذكاء الاصطناعي المحادثي، مُعاد بناؤها بـTypeScript

في 1966، كتب Joseph Weizenbaum 420 سطرا من MAD-SLIP على IBM 7094 لينشئ أول شاتبوت في التاريخ. كان اسم البرنامج ELIZA، وكان يحاكي معالجة نفسية روجرية بأنماط أساسية وتباديل جمل. بعد ستة عقود، صار الذكاء الاصطناعي المحادثي موضوعا جماهيريا، ChatGPT وClaude وGemini في كل المحادثات. وعندما أعدت قراءة ذلك الكود الأصلي، أدهشني اقتصاده في الوسائل.

لكن بين هذين الطرفين، كان هناك PARRY (الشاتبوت المصاب بالبارانويا، 1972) وALICE (ملك AIML بـ99 000 فئة، 1995) وJabberwacky (الأول الذي يتعلم دون قواعد، 1997) وCleverbot (خليفته الصناعي، 2008). أراها خمسة أجوبة مختلفة جذريا للمشكلة نفسها : جعل آلة تتكلم.

نقلت هذه البوتات الخمسة إلى TypeScript مع بياناتها الأصلية، سكربتات ELIZA وقواميس PARRY وملفات AIML الخاصة بـALICE. كل نقل مستقل، جاهز للاستخدام، وموثق بأدق التفاصيل. هدفي من هذا العمل يتجاوز مجرد التشغيل : أريد فهم كيف كانت تعمل، ولماذا طبعت التاريخ، وما تكشفه بناها عن ذكاء الأمس... واليوم.

bun run eliza    # Parle à ELIZA (1966)
bun run parry    # Parle à PARRY (1972)
bun run alice    # Parle à ALICE (1995)
bun run jabber   # Parle à Jabberwacky
bun run cleverbot # Parle à Cleverbot
bun run meeting  # ELIZA vs PARRY automatique

سأشرّح معك كل بوت، وأنظر لكوده، ثم أبني الجسر مع LLM الحديثة عبر مقالات Luna Protocol.


ELIZA (1966) : فن الإيهام بالفهم

أبدأ بالأقدم، والأكثر إثارة للإعجاب في نظري ببساطتها. لا تملك ELIZA أي ذكاء بالمعنى الحديث للكلمة. تعمل فقط بأنماط نصية وقليل من التبديل.

المبدأ

يعمل سكربت DOCTOR (نسخة المعالجة النفسية) مع جدول keywords، لكل واحد أنماط تفكيك وقواعد إعادة تركيب. إليكم قاعدة نموذجية :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO هي الكلمة المفتاحية. 0 نمط تفكيك يقول "التقط كل ما يتبع" (كـwildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. هي قاعدة إعادة التركيب. هذا كل شيء.

عندما تقول "Hello, I'm sad today"، تقوم ELIZA بـ :

  1. تحويل النص لكبير : HELLO I'M SAD TODAY
  2. مسح كل كلمة مقابل جدول keywords
  3. إيجاد HELLO ← تدفعه على كومة keywords
  4. أخذ keyword بأعلى أولوية
  5. تجربة كل نمط تفكيك بالترتيب
  6. إذا طابق، اختيار قاعدة إعادة التركيب التالية (round-robin)
  7. استبدال (1) و(2) إلخ بالأجزاء الملتقطة

لكن الجزء الذكي فعلا هو قواعد PRE. انظروا هذا :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

عندما تطابق ELIZA MY، تحوّل بقية الجملة (الملتقطة بـ0) عبر قاعدة PRE، وتعيد حقن النتيجة كأن المستخدم قال للتو كلمة مفتاحية جديدة. عمليا :

Tu dis: "My mother hates me"
  → PRE transforme: "YOUR MOTHER HATES YOU"
  → réinjecté comme si tu venais de le dire
  → matche probablement "YOU" → nouvelle réponse

لهذا تعطي ELIZA انطباع فهم الفرق بين "أنا" و"أنت" : أرى فيها تحويلا آليا مصمما بإتقان يحاكي الفهم.

إليكم التدفق الكامل، من ضغطة المستخدم إلى الرد :

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalise la ponctuation"]
    B --> C["splitUserInput()<br>découpe en mots"]
    C --> D["Build keyword stack<br>ordonné par priorité"]
    D --> E{"Stack non-vide?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

ما الذي جعلها موثوقة

أعتبر اختيار Weizenbaum ضربة عبقرية : المعالجة النفسية الروجرية. تقوم هذه المقاربة على عكس أقوال المريض دون تفسير. "أنا حزين" ← "تقول إنك حزين". وهذا بالضبط ما تعرف ELIZA فعله، وبما أن هذه التقنية العلاجية معترف بها، لا يجد أحد ردها غريبا. وعندما تجرب البوت، تشعر مباشرة بهذا الأثر المرآوي.

في نقل TypeScript

في نقلي، أحمّل سكربتات .ela (صيغة S-expression الأصلية)، وأحللها كليا (بما فيها ترميز Hollerith، صيغة سلاسل الستينيات)، وأنفذ نفس الدورة : uppercasing ← split ← keyword stack ← تفكيك ← إعادة تركيب ← PRE/transforms. أردت البقاء وفيا للسلوك الأصلي حتى في التفاصيل.

➡ Voir le code source


PARRY (1972) : أول شاتبوت بعواطف

بعد ELIZA بست سنوات، أنشأ Kenneth Colby (طبيب نفسي في Stanford) شاتبوت PARRY : يحاكي مريضا مصابا بـفصام بارانويدي. حيث كانت ELIZA تعمل كمرآة فارغة، يحمل PARRY نموذجا عاطفيا داخليا حقيقيا. هذا التباين هو ما أثّر فيّ أكثر بنقلهما واحدا بعد الآخر.

النموذج العاطفي

يملك PARRY أربعة متغيرات مستمرة تتطور مع كل دور محادثة :

المتغير خط الأساس الاضمحلال/دور الوصف
ANGER 0 −1.0 عدائية وانزعاج
FEAR 0 −0.2 بارانويا (تضمحل ببطء بعد بدء الهذيان)
MISTRUST 0 −0.05 ارتياب (بطيء جدا في النزول)
HURT 0 −0.5 ألم عاطفي

ترتفع هذه القيم عبر قفزات عاطفية (ajump وfjump وhjump) تحفّزها قواعد استدلال، وتضمحل طبيعيا نحو خطوط أساسها مع كل دور.

شبكة المعتقدات

يملك PARRY أكثر من 200 معتقد مخزنة في ملف bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

لكل معتقد فئة (HUM = المريض، وHUM2 = الآخرون، وDOC = الطبيب، وINT = الاستجواب، وINN = النوايا) وقوة (0-5). تنشر قواعد الاستدلال (TH2 وEMOTE وIF) المعتقدات بينها :

  • TH2 : إذا تجاوز معتقد A عتبة، يتعزز وتزداد عواقبه
  • EMOTE : إذا تجاوز معتقد عتبة، يحفّز قفزة عاطفية (anger/fear/hurt)
  • IF : شرطي، إذا كانت A صحيحة، تصبح B صحيحة بمستوى معين

هرمية الهذيانات (نظام flare)

الجزء الأكثر سحرا في PARRY هو نظام "flares"، سلسلة تصعيد تقود تدريجيا نحو الهذيان المركزي :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

كل كلمة مفتاحية تحفّز ردا مكتوبا مسبقا (عبر مطابقة الأنماط)، وإذا تابعت الموضوع، ينحرف PARRY تدريجيا نحو هذيانه المركزي عن الاضطهاد. وبمجرد "تحفيز" flare، يصبح خاملا (deadFlares) وينتقل PARRY للتالي، كمحاور يحفر موضوعه. أحب هذه الآلية كثيرا لأنها تخلق تقدما سرديا بكود قليل جدا.

لماذا PARRY تاريخي

في 1972، اجتاز PARRY صيغة من اختبار Turing : تلقى أطباء نفسيون نصوص محادثات مع PARRY ومع مرضى بارانويديين حقيقيين، ولم يستطيعوا التمييز بشكل موثوق. إنه أول برنامج ينجح في ذلك، قبل LLM بـ40 عاما. أجد هذا الإنجاز لافتا عندما تقارن وسائل ذلك العصر بوسائل اليوم.

➡ Voir le code source


RFC 439 : عندما يلتقي ELIZA بـPARRY

في 18 سبتمبر 1972، تحادث برنامجان لأول مرة في التاريخ، حرفيا : إنها أول محادثة بين ذكاءين اصطناعيين مسجلة أبدا. تحادثت ELIZA (العاملة في BBN ببوسطن) وPARRY (العامل في Stanford) عبر ARPANET، يرحّلهما مشغلون بشر كانوا يكتبون مخرجات أحدهما إلى مدخلات الآخر. نُشرت النسخة كـRFC 439 ("PARRY Encounters the DOCTOR"). وعندما اكتشفت هذه الوثيقة، فهمت أنها تؤرخ لحظة تأسيسية لمجالنا.

إليكم مقتطفا مما بدا عليه الأمر :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

فاجأني اتساق هذا التبادل عند القراءة الأولى. تقوم ELIZA بعمل المعالجة : إعادة صياغة وتساؤل واستكشاف. ويقوم PARRY بعمل المريض البارانويدي : شكوى واتهام وتعبير عن ارتياب. يبقى البرنامجان تماما في دوريهما بفضل تركيبة آليتيهما (أنماط ELIZA + نموذج PARRY العاطفي) التي تنتج ردودا تتعشق بالمصادفة. وعندما تقرأ النسخة كاملة، ترى إلى أي حد يخلق هذا التوافق العرضي وهم حوار مقصود.

تستطيع إعادة إنتاج هذه المحادثة مع مستودعي :

bun run meeting

تطلق محاكاتي 25 دورا آليا بين البوتين، مع موضوع بداية عشوائي (خيول وجريمة منظمة وعواطف...). وبما أن ELIZA وPARRY يحملان عناصر غير حتمية (round-robin الخاص بـELIZA وعشوائية PARRY)، ينتج كل تنفيذ تبادلا مختلفا. أعشق إعادة إطلاق الأمر عدة مرات لمقارنة المسارات.

ما يدهشني في ELIZA ضد PARRY هو لقاء برنامجين، أحدهما ببنية دون حالة داخلية، والآخر بنموذج عاطفي كامل، ينتجان معا محادثة تشبه شيئا متعمدا. بالنسبة لعام 1972، أجد النتيجة مذهلة.


ALICE (1995) : مطابقة الأنماط على نطاق واسع

أُنشئت ALICE (Artificial Linguistic Internet Computer Entity) من Richard Wallace في 1995، وفازت بـLoebner Prize ثلاث مرات (2000 و2001 و2004). حيث كانت لـELIZA بضع مئات من القواعد ولـPARRY بضعة آلاف، تملك ALICE منها 99 524، موزعة في 66 ملف AIML. وعندما فتحت هذه المدونة أول مرة، قست ما كان يعنيه "نطاق واسع" في ذلك العصر.

AIML : لغة الفئات

AIML (Artificial Intelligence Markup Language) صيغة XML لتعريف أزواج سؤال-جواب :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

لكن قوة ALICE تأتي من wildcards وSRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- équivaut à <srai><star/></srai> -->
  </template>
</category>

يسمح SRAI لـALICE بإعادة توجيه مدخل نحو فئة أخرى، خالقا سلسلة اختزال :

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

إنها الآلية التي تمنح ALICE مرونتها : بدلا من كتابة رد لكل صياغة ممكنة، تكتب ردا معياريا وتعيد توجيه الصيغ نحوه. حد العمق 10 : بعده، تستسلم ALICE لتجنب الحلقات اللانهائية (تصميم الفئات يتجنبها بعناية، وتبقى شبكة الأمان هذه أساسية). أجد هذا الخيار التصميمي أنيقا لأنه يعمّل آلاف الصيغ بقواعد قليلة جدا.

كيف تطابق ALICE الأنماط

تُرتَّب الأنماط حسب التخصيص : الأقل wildcards تُجرَّب أولا. تلتقط wildcards * و_ أي تسلسل كلمات. يجمّع المحرك كل نمط إلى regex، ثم يكرر الفئات المرتبة حتى إيجاد مطابقة.

// تنفيذنا TypeScript، مبسطة لكن وفية
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

لماذا هيمنت ALICE على Loebner

99 524 فئة، رقم يغيّر كل شيء. كانت ELIZA تعطي وهم الذكاء لأن قواعدها القليلة صُمِّمت جيدا لسياق بعينه (العلاج). تغطي ALICE مواضيع كثيرة حتى تعطي انطباع ثقافة عامة حقيقية : علوم وسياسة وفكاهة ورياضة وعواطف، كل شيء فيها. أستخلص من هذه المقاربة درسا أطبقه ما زلت : تغطية واسعة ومصممة تعوّض غياب المنطق العميق.

➡ Voir le code source


Jabberwacky (1997) وCleverbot (2008) : القطيعة المعرفية

تتقاسم كل البوتات السابقة فرضا : يجب كتابة الردود. لـELIZA قواعد S-expressions، ولـPARRY أنماط انتقائية، ولـALICE فئات AIML. اتخذ Rollo Carpenter النقيض الكامل بسؤال جذري : ماذا لو تركنا المحادثات الماضية توفّر الردود ؟ أجد هذا القلب جريئا لأنه يتخلى عن أي كتابة يدوية للقواعد.

الفكرة

يرتكز Jabberwacky (أُطلق نحو 1997، وصار Cleverbot في 2008) كليا على كل سجل المحادثات المخزن في نص مسطح، وعندما يكلمه شخص، يبحث في هذا السجل عن اللحظة الأكثر شبها ويعيد استخدام ما قيل بعدها :

Utilisateur: "hello"
  ↓
Chercher: est-ce que quelqu'un a déjà dit "hello" avant ?
  ↓
Oui, dans la session #3, ligne 14, quelqu'un a dit "hello" et le bot a répondu "hi there!"
  ↓
Répondre: "hi there!"

لن تجد هنا إلا أرشيفا ضخما لأشياء قالها أناس لبعضهم، مُعاد استخدامها في الوقت المناسب. أرى فيه تعريف الظهور نفسه : سلوك محادثي يولد من تراكم آثار بشرية.

تنفيذ TypeScript

يعيد نقلي في TypeScript إنتاج هذه البنية بالضبط :

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 lignes seed + historique"]
    B --> C["withReplies()<br>extrait les paires<br>(ligne → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>context avant cette ligne)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidats triés"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>de la paire gagnante"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append au transcript<br>save() → JSON"]
    L --> M

إليكم قلب التسجيل، مع استدلالي الخاص المستوحى من أوصاف Cleverbot العامة :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : الشبه بين مدخل المستخدم والسطر التاريخي
  • contextFit (0.25) : الشبه بين المحادثة الأخيرة وما سبق السطر التاريخي
  • recencyBonus (0.10) : الذكريات الحديثة تزن أكثر بعض الشيء (شخصية البوت تنحرف مع الوقت)

الاختيار احتمالي (roulette-wheel selection) : المرشح الأفضل يفوز غالبا، مع حصة تغايُر تعطي تنوعا. أقدّر هذا الاختيار لأنه يجعل المحادثات أقل توقعا عندما تختبر البوت عدة مرات.

Cleverbot : الابتكاران الموثقان

يضيف Cleverbot آليتين لمفهوم Jabberwacky الأساسي :

  1. تعلم متعدد الأشخاص : ملايين المستخدمين يساهمون في نفس النص المشترك. رد مأخوذ من السجل قد يأتي من صوت مختلف تماما عن صوت المحادثة الجارية، وهذا يفسر لماذا يغيّر Cleverbot شخصيته فجأة.

  2. تعلم مؤجل : ما تقوله لـCleverbot أثناء جلسة غير متاح للمطابقة أثناء نفس الجلسة. تُوسَم الأسطر الجديدة pending ولا تصبح قابلة للمطابقة إلا بعد "توطيد" بين الجلسات، وهذا يفسر لماذا لا تستطيع تعليم Cleverbot حقيقة وإعادة استخدامها في نفس المحادثة.

// Cleverbot : الأسطر الحديثة غير مرئية حتى التوطيد
const line = store.append("human", text, null, sessionId, false); // pending
// ...تُستدعَى consolidate() عند الإقلاع، لا أثناء الجلسة

ينفذ نقلي في TypeScript هذين السلوكين : للأسطر علم consolidated، وتبدأ كل جلسة REPL بتوطيد الأسطر المنتظرة.

➡ Voir le code source


تحليل نقل TypeScript : تصميم بنية مشتركة

أواجهني بناء هذه البوتات الخمسة بنفس اللغة بسؤال مثير : هل يمكن تعميل كود بين بنى مختلفة هكذا ؟

جوابي بعد نقلها كلها : قليل جدا. لكل بوت حلقة جوهرية مختلفة :

البوت الحلقة الرئيسية البيانات التعلم
ELIZA كومة keywords ← تفكيك ← إعادة تركيب سكربتات .ela بصيغ S-expressions لا شيء
PARRY ترميز ← أنماط انتقائية / flares / keywords / استدلالات 58 ملف PDP-10 (قواميس ومعتقدات وقواعد) لا شيء
ALICE أنماط مرتبة ← regex ← قالب AIML ← SRAI تكراري 66 ملف AIML بصيغة XML لا شيء
Jabberwacky شبه ← سياق ← حداثة ← اختيار موزون نص JSON (يكبر مع الاستخدام) مستمر
Cleverbot نفس Jabberwacky + pending/consolidated + personas نص JSON + بذور multi-personas مؤجل (بين الجلسات)

ما تتشاركه هو واجهة CLI وبنية TypeScript التحتية (biome للفحص وtsx للتنفيذ). الباقي خاص بكل بنية.

خيارات تصميم مشتركة

1. الوفاء للبيانات الأصلية. لـELIZA وPARRY وALICE، أستخدم الملفات الأصلية، سكربتات ELIZA المستعادة من أرشيف Weizenbaum في 2021، وكود PARRY الأصلي من PDP-10 (58 ملفا)، وAIML Free ALICE v1.6. أحتفظ بها في صورتها الأصلية. تتصرف البوتات كالأصول لأنها تستخدم نفس البيانات.

2. غرفة نظيفة للأجزاء الملكية. يطرح Jabberwacky وCleverbot حالة مختلفة : لم يُنشَر كودهما المصدري أبدا (أبقاه Existor/Rollo Carpenter ملكيا). فنقلي إذن إعادة تنفيذ بغرفة نظيفة، مبنية فقط على أوصاف عامة للسلوك. لم أنسخ أي سطر كود ولا بيانات ملكية.

3. تبعيات دنيا. المتطلب الحقيقي الوحيد هو TypeScript. تستخدم ALICE مكتبة dom-js لتحليل XML ملفات AIML (66 ملفا و99 524 فئة، فالتحليل المنزلي مضيعة وقت). الباقي كله TypeScript خام. اتخذت هذا الخيار لأُبقي كل بوت مقروءا وسهل الإطلاق عندما تستنسخ المستودع.


من الشاتبوتات الرمزية إلى LLM : القفزة المفاهيمية

تتقاسم البوتات الخمسة التي عرضتها لك سمة جوهرية : إنها رمزية. "معارفها" ترتكز كليا على رموز صريحة : أنماط نصية وجداول قواعد وفئات XML وأسطر نص. تعمل كليا دون تمثيل رقمي للغة.

وتتقاسم إذن نفس السقف الزجاجي : لا ترد إلا على ما كان متوقعا أو مسجلا صراحة. تضيع ELIZA إذا خرجت من الإطار العلاجي. لا يتكلم PARRY إلا عن مواضيعه البارانويدية. لا تتعلم ALICE شيئا من محادثاتها. لا يرد Jabberwacky إلا بردود قيلت فعلا. وعندما تختبر هذه الحدود، تفهم سريعا لماذا بلغ هذا الجيل سقفه.

تتجاوز LLM (Large Language Models) هذا السقف بتغيير جذري للنموذج : بدلا من التلاعب بالرموز، تحوّل اللغة إلى أرقام وتتعلم علاقات إحصائية بين هذه الأرقام. تولّد كل رمز لحظيا بحساب احتمالات، انطلاقا من معارف مرمزة في أوزانها بدلا من ردود مكتوبة مسبقا. أجد هذا الانقلاب ساحرا لأنه يعكس تماما منطق البوتات السابقة. لنرَ سريعا كيف يعمل ذلك.

1. Tokenization

الخطوة الأولى هي تقطيع النص إلى رموز، وحدات أصغر من الكلمات لكن أكبر من الحروف :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

لكل رمز معرف رقمي في مفردات (عادة 32 000 إلى 128 000 رمز للنماذج الحديثة). يسمح هذا التجزؤ للنموذج بمعالجة كلمات لم يرَها أبدا بتفكيكها إلى وحدات فرعية معروفة.

2. Embeddings

يُحوَّل كل معرف رمز إلى متجه، جدول أعداد عائمة (عادة 4096 بعدا لنموذج متوسط الحجم). هذا المتجه تضمين (embedding) يرمّز معنى الرمز في فضاء رياضي حيث الرموز القريبة دلاليا لها متجهات قريبة :

vecteur("roi")  − vecteur("homme") + vecteur("femme")  ≈  vecteur("reine")

تظهر هذه الخاصية من التدريب، دون برمجة صريحة مني أو من الباحثين. أرى فيها عاقبة مباشرة لطريقة ظهور الكلمات في سياقات متشابهة.

3. Attention

آلية الانتباه (التي قدمتها ورقة "Attention is All You Need" في 2017) هي ما جعل LLM ممكنة. لكل رمز، يحسب الانتباه أي الرموز الأخرى في الجملة مهمة لفهم هذا الرمز :

"La banque a refusé mon prêt."
      ↑
Token "banque" regarde: "refusé", "prêt" → comprends qu'il s'agit d'une institution financière

"Je vais me promener sur la banque."
      ↑
Token "banque" regarde: "promener", "sur" → comprends qu'il s'agit d'une rive

يسمح الانتباه للنموذج بالتقاط السياق : كل رمز يُفهَم حسب ما يحيط به، في قراءة علائقية كاملة. هذه الحساسية للسياق هي ما ساعدني أكثر على فهم لماذا تتجاوز LLM الأنماط الثابتة.

4. توقع الرمز التالي

يرتكز تدريب LLM على بساطة خادعة : تُريه نصا، وتخفي الرمز الأخير، وتطلب منه توقعه. ثم تكرر العملية مليارات المرات. أحب هذه الفكرة لأن قاعدة تعلم بسيطة جدا تنتج قدرات غنية جدا.

Input:  "Je ne comprends"
Caché:  "pas"
Prédiction du modèle: "pas" (probabilité 0.87), "rien" (0.05), "jamais" (0.02)...

الهدف هو تعظيم احتمال الرمز الحقيقي في كل موضع. وهذا ما يسمى توقع الرمز التالي. أثناء التدريب، يضبط النموذج مليارات معاملاته لتقليل خطأ التوقع على تيرابايتات من النص.

ولحظة الاستدلال (عندما تكلمه)، يولد النموذج رمزا واحدا في كل مرة في حلقة :

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

يُؤخَذ كل رمز حسب احتماله (الحرارة وtop-k وtop-p تتحكم في درجة "الإبداع"). وهذا كل شيء. مليارات معاملات تكرر هذه العملية آلاف المرات. أجد مذهلا أن آلية متكررة هكذا تنتج ردودا متنوعة هكذا.

ما الذي يتغير جوهريا

الجانب البوتات الرمزية (ELIZA وPARRY وALICE) LLM الحديثة
التمثيل كلمات وقواعد صريحة متجهات رقمية (embeddings)
التوليد اختيار في ردود مكتوبة مسبقا توقع احتمالي رمز برمز
المعارف مخزنة في ملفات قواعد مرمزة في أوزان الشبكة
التعلم يدوي (كتابة قواعد) آلي (تدريب على مدونة)
المتانة معدومة خارج الأنماط المتوقعة يعمم على مدخلات لم تُرَ أبدا
قابلية التفسير كاملة (يمكن قراءة القواعد) محدودة (صندوق أسود)

الشاتبوتات التقليدية شفافة لكن هشة. وLLM متين لكن معتم. أراها اليوم أدوات مكملة لحاجات مختلفة، وأختار أحدهما أو الآخر حسب مستوى التحكم الذي تريد الاحتفاظ به على الردود.

إذا أردت تعميق الفهم الداخلي لـLLM، أوصي بهذا الفيديو كمورد ممتاز :

How LLMs Work - YouTube

Luna Protocol : التركيب الحديث

تمثل مقالات Luna Protocol (التي روابطها أدناه) في نظري التركيب الأكثر نضجا لكل ما عرضته لك للتو : بوت Discord حديث يجمع LLM محليا مع نظام سلوكي متطور، الكل مبني على دروس 60 عاما من الذكاء الاصطناعي المحادثي. إنه المشروع الذي سمح لي بربط نقولي التاريخية بتجاربي الحالية.

Luna Protocol : أنشأت بوت Discord مستقلا يحاكي إنسانا

يفصّل هذا المقال البنية الكاملة لبوت Discord قائم على LLM :

  • نظام تحفيز ذو أولويات (إشارة > رسالة خاصة > اسم > كلمة مفتاحية > متابعة > عشوائي)
  • سلوكيات إنسانية : تركيز متغير وأخطاء مطبعية وتردد (15%) ونسيان (3%) وتعب موضوعي
  • جداول نوم : البوت ينام ويبطئ أو يتجاهل حسب الساعة
  • خط TTS : تركيب صوتي عبر Piper + ffmpeg ← رسائل Discord الصوتية
  • بث في الوقت الحقيقي : يبث LLM الرموز واحدا بواحد على ناقل أحداث مُنمَّط

ما يربط هذا المقال بالشاتبوتات التاريخية هو نفس السعي : الإيهام بأننا نكلم شخصا. استخدمت ELIZA مرايا نصية. واستخدم PARRY نموذجا عاطفيا. واستخدمت ALICE 99k فئة. ويستخدم Luna Protocol نموذج LLM مضبوطا + نظاما سلوكيا يحاكي العيوب الإنسانية. أجد في هذا النسب نفس المطلب : خلق وهم حضور على الطرف الآخر من الشاشة.

Luna Protocol : لماذا ضبطت نموذج 1,5B

يستكشف المقال الثاني الضبط الدقيق والتمهيد few-shot. اكتشافي المركزي : نموذج أصغر (1,5B) مدرَّب على بيانات أقل (50k عينة) يتفوق على نموذج أكبر (3B) عندما يُمهَّد صحيحا بأمثلة few-shot. فاجأني هذا النتيجة فعلا عندما قسته.

إنه درس يتردد مباشرة مع الشاتبوتات التاريخية :

  • أظهرت ELIZA أنه ببضع قواعد مصممة جيدا، يمكن محاكاة الفهم
  • أظهرت ALICE أنه مع 99k فئة، يمكن محاكاة الثقافة العامة
  • يُظهر Luna Protocol أنه بضبط جيد و5 أمثلة few-shot، يستطيع LLM صغير محاكاة إنسان

التقنية تتغير، والمبدأ يبقى نفسه : جودة البيانات ودقة النظام تهم أكثر من الحجم الخام. إنها قناعة أدافع عنها في تجاربي الخاصة.


الخلاصة : ثلاثة أشياء للتذكر

1. لم يبدأ الذكاء الاصطناعي المحادثي مع ChatGPT. عمر ELIZA 60 عاما. اجتاز PARRY اختبار Turing في 1972. فازت ALICE بـLoebner ثلاث مرات. وضع Jabberwacky أسس التعلم بالنص، التي صنّعها Cleverbot على نطاق واسع. كل مقاربة جلبت قطعة من اللغز. وهذا ما أكدته لي نقولي.

2. بيانات أكثر ≠ أذكى. نص Jabberwacky بلا قواعد. فئات ALICE الـ99k لا تتعلم. ضبط Luna Protocol على 50k عينة يتفوق على نموذج 3B. تقول الحكمة التقليدية "كلما كان أكبر كان أفضل"، ويُظهر تاريخ الشاتبوتات أن البنية والتصميم يهمان بقدر الحجم. أدافع عن هذه الفكرة في كل خياراتي التقنية.

3. المشكلة نفسها منذ 60 عاما. كيف نُوهِم إنسانا أنه يكلم إنسانا آخر ؟ كانت ELIZA ترد بمرايا نصية. وكان PARRY يرد بغضب محاكًى. وكانت ALICE ترد بحقائق. ويرد Luna Protocol بـLLM ينام ويرتكب أخطاء مطبعية. الحل يتغير، والحاجة تبقى. هذه الاستمرارية هي ما يجعلني أرغب في مواصلة الاستكشاف.

المستودع مفتوح المصدر، يمكنك استنساخه وتشغيل كل بوت، ورؤية بنفسك كيف تتسع 60 عاما من الذكاء الاصطناعي المحادثي في مستودع TypeScript واحد. آمل أن تستمتع بجعلها تتكلم بقدر ما استمتعت.

المورد الرابط
مستودع GitHub fox3000foxy/chatbots
Luna Protocol، بنية البوت قراءة المقال
Luna Protocol، ضبط few-shot قراءة المقال
سكربتات ELIZA الأصلية anthay/ELIZA
كود PARRY المصدري الأصلي lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 الأصلية PARRY Encounters the DOCTOR
شرح ممتاز لعمل LLM https://www.youtube.com/watch?v=YmLp8qe87A0

Từ ELIZA tới LLM : 60 năm AI hội thoại, dựng lại bằng TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- năm kiến trúc khác nhau triệt để của cùng bài toán, mang sang TypeScript với dữ liệu gốc của chúng. Từ 1966 tới LLM hiện đại, đây là cách AI hội thoại học nói, và điều một repo chatbot dạy ta về 60 năm nghiên cứu.

Từ ELIZA tới LLM : 60 năm AI hội thoại, dựng lại bằng TypeScript

Năm 1966, Joseph Weizenbaum viết 420 dòng MAD-SLIP trên IBM 7094 để tạo chatbot đầu tiên trong lịch sử. Chương trình tên ELIZA, và nó mô phỏng nhà trị liệu tâm lý Rogers bằng mẫu cơ bản và hoán vị câu. Sáu thập kỷ sau, AI hội thoại thành chủ đề đại chúng -- ChatGPT, Claude, Gemini ở khắp các cuộc trò chuyện. Khi tôi đọc lại mã gốc đó, tôi ấn tượng bởi tính tiết kiệm phương tiện của nó.

Nhưng giữa hai cực này, có PARRY (chatbot hoang tưởng, 1972), ALICE (vua AIML với 99 000 danh mục, 1995), Jabberwacky (đầu tiên học không cần quy tắc, 1997), và Cleverbot (kẻ kế thừa công nghiệp, 2008). Tôi xem chúng như năm câu trả lời khác nhau triệt để cho cùng bài toán : khiến máy biết nói.

Tôi đã mang năm bot này sang TypeScript với dữ liệu gốc của chúng -- script ELIZA, từ điển PARRY, tệp AIML của ALICE. Mỗi bản port độc lập, sẵn sàng dùng, và ghi chép chi tiết từng li. Mục tiêu của tôi với công việc này vượt thực thi đơn thuần : tôi muốn hiểu chúng chạy thế nào, vì sao chúng ghi dấu lịch sử, và kiến trúc tương ứng của chúng tiết lộ gì về AI hôm qua... và hôm nay.

bun run eliza    # Nói với ELIZA (1966)
bun run parry    # Nói với PARRY (1972)
bun run alice    # Nói với ALICE (1995)
bun run jabber   # Nói với Jabberwacky
bun run cleverbot # Nói với Cleverbot
bun run meeting  # ELIZA đấu PARRY tự động

Tôi sẽ mổ xẻ từng bot với bạn, xem mã của chúng, rồi bắc cầu sang LLM hiện đại qua các bài về Luna Protocol.


ELIZA (1966) : nghệ thuật khiến người ta tin mình hiểu

Tôi bắt đầu bằng bot lâu đời nhất, và theo mắt tôi ấn tượng nhất bởi đơn giản. ELIZA không có trí tuệ theo nghĩa hiện đại. Nó chỉ chạy bằng mẫu văn bản và chút hoán vị.

Nguyên tắc

Script DOCTOR (bản nhà trị liệu) chạy bằng bảng keyword, mỗi cái gắn với mẫu phân rã và quy tắc ráp lại. Đây là quy tắc điển hình :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO là từ khóa. 0 là mẫu phân rã bảo "chụp mọi thứ theo sau" (như wildcard). HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. là quy tắc ráp lại. Hết.

Khi bạn nói "Hello, I'm sad today", ELIZA :

  1. Viết hoa văn bản : HELLO I'M SAD TODAY
  2. Quét từng từ với bảng keyword
  3. Thấy HELLO → đẩy lên ngăn xếp keyword
  4. Lấy keyword ưu tiên cao nhất
  5. Thử từng mẫu phân rã theo thứ tự
  6. Nếu khớp, chọn quy tắc ráp lại tiếp theo (xoay vòng)
  7. Thay (1), (2) v.v. bằng các phần chụp được

Nhưng phần thực sự thông minh là các quy tắc PRE. Xem này :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

Khi ELIZA khớp MY, nó biến phần còn lại của câu (chụp bởi 0) qua quy tắc PRE, và bơm lại kết quả như thể người dùng vừa nói từ khóa mới. Cụ thể :

Bạn nói: "My mother hates me"
  → PRE biến: "YOUR MOTHER HATES YOU"
  → bơm lại như thể bạn vừa nói nó
  → có thể khớp "YOU" → phản hồi mới

Đó là lý do ELIZA tạo cảm giác hiểu khác biệt giữa "tôi" và "bạn" : tôi thấy ở đó biến đổi cơ học thiết kế hoàn hảo mô phỏng thấu hiểu.

Đây là luồng đầy đủ, từ phím người dùng tới phản hồi :

flowchart TD
    A["Đầu vào người dùng:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>chuẩn hóa chấm câu"]
    B --> C["splitUserInput()<br>cắt thành từ"]
    C --> D["Dựng ngăn xếp keyword<br>sắp theo ưu tiên"]
    D --> E{"Ngăn xếp rỗng?"}
    E -->|"Không"| F["Lấy keyword ưu tiên cao nhất"]
    E -->|"Có"| G{"Nhớ lại?"}
    G -->|"Có"| H["Gọi lại phát biểu cũ của người dùng"]
    G -->|"Không"| I["Dự phòng: quy tắc zNONE"]
    I --> J["Trả phản hồi"]
    H --> J
    F --> K["Khớp mẫu phân rã"]
    K --> L{"Thấy khớp?"}
    L -->|"Không"| M{"Keyword liên kết?"}
    M -->|"Có"| N["Đẩy keyword liên kết vào ngăn xếp"]
    N --> E
    M -->|"Không"| O["Trả NOMATCH"]
    O --> J
    L -->|"Có"| P["Chọn ráp lại tiếp theo (xoay vòng)"]
    P --> Q{"Kiểu ráp lại?"}
    Q -->|"PRE"| R["Biến đổi từ (I→YOU)<br>đẩy keyword liên kết"]
    R --> N
    Q -->|"NEWKEY"| S["Nhảy tới keyword tiếp"]
    S --> E
    Q -->|"Chuẩn"| T["Giãn (1), (2), (0)<br>thành phản hồi cuối"]
    T --> J

Điều khiến nó đáng tin

Tôi coi lựa chọn của Weizenbaum là nước thiên tài : trị liệu Rogers. Cách này phản chiếu lời bệnh nhân mà không diễn giải. "Tôi buồn" → "Bạn nói bạn buồn". Đó đúng là điều ELIZA biết làm, và vì kỹ thuật trị liệu này được công nhận, không ai thấy phản hồi của nó lạ. Khi bạn test bot, bạn cảm trực tiếp hiệu ứng gương này.

Trong bản port TypeScript

Trong bản port của tôi, tôi tải script .ela (định dạng S-expression gốc), parse toàn bộ (kể cả mã hóa Hollerith, định dạng chuỗi thập niên 60), và chạy cùng chu kỳ : viết hoa → cắt → ngăn xếp keyword → phân rã → ráp lại → PRE/biến đổi. Tôi muốn giữ trung thành hành vi gốc tới từng chi tiết.

➡ Xem mã nguồn


PARRY (1972) : chatbot đầu tiên có cảm xúc

Sáu năm sau ELIZA, Kenneth Colby (bác sĩ tâm thần ở Stanford) tạo PARRY : chatbot mô phỏng bệnh nhân tâm thần phân liệt hoang tưởng. Nơi ELIZA chạy như tấm gương rỗng, PARRY mang mô hình cảm xúc nội tại thật. Chính tương phản này khiến tôi ấn tượng nhất khi mang chúng nối nhau.

Mô hình cảm xúc

PARRY có bốn biến liên tục évoluent mỗi lượt trò chuyện :

Biến Đường nền Suy giảm/lượt Mô tả
ANGER 0 −1.0 Thù địch, bực bội
FEAR 0 −0.2 Hoang tưởng (suy chậm sau khi bắt đầu mê sảng)
MISTRUST 0 −0.05 Nghi ngờ (rất chậm hạ)
HURT 0 −0.5 Đau cảm xúc

Các giá trị này tăng qua bước nhảy cảm xúc (ajump, fjump, hjump) do quy tắc suy luận kích hoạt, và suy tự nhiên về đường nền mỗi lượt.

Mạng niềm tin

PARRY có 200+ niềm tin lưu trong tệp bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

Mỗi niềm tin có loại (HUM = bệnh nhân, HUM2 = người khác, DOC = bác sĩ, INT = thẩm vấn, INN = ý định) và độ mạnh (0-5). Quy tắc suy luận (TH2, EMOTE, IF) lan truyền niềm tin lẫn nhau :

  • TH2 : nếu niềm tin A vượt ngưỡng, nó tự củng cố và hệ quả của nó tăng
  • EMOTE : nếu niềm tin vượt ngưỡng, nó kích hoạt bước nhảy cảm xúc (anger/fear/hurt)
  • IF : điều kiện -- nếu A đúng, thì B thành đúng ở mức nào đó

Phân cấp mê sảng (hệ flare)

Phần cuốn hút nhất của PARRY là hệ "flare" -- chuỗi leo thang dần tới mê sảng trung tâm :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

Mỗi từ khóa kích hoạt phản hồi viết sẵn (qua khớp mẫu), và nếu bạn theo chủ đề, PARRY trôi dần về mê sảng bị hại trung tâm. Khi một flare đã "kích", nó thành không hoạt động (deadFlares) và PARRY sang cái tiếp, như người đối thoại đào sâu chủ đề. Tôi rất thích cơ chế này vì nó tạo tiến triển tự sự với rất ít mã.

Vì sao PARRY mang tính lịch sử

Năm 1972, PARRY đã qua một biến thể test Turing : các bác sĩ tâm thần nhận bản ghi cuộc trò chuyện với PARRY và với bệnh nhân hoang tưởng thật, và không phân biệt được đáng tin. Đó là chương trình đầu tiên làm được, 40 năm trước LLM. Tôi thấy thành tích này đáng nể khi so phương tiện thời đó với ngày nay.

➡ Xem mã nguồn


RFC 439 : khi ELIZA gặp PARRY

Ngày 18 tháng 9 năm 1972, hai chương trình lần đầu nói chuyện với nhau trong lịch sử, theo nghĩa đen : đó là cuộc trò chuyện liên AI đầu tiên từng ghi lại. ELIZA (chạy trên BBN ở Boston) và PARRY (chạy ở Stanford) đã trò chuyện qua ARPANET, chuyển tiếp bởi người vận hành gõ đầu ra của bên này vào đầu vào bên kia. Bản ghi được xuất bản thành RFC 439 ("PARRY Encounters the DOCTOR"). Khi tôi phát hiện tài liệu này, tôi hiểu nó đánh dấu khoảnh khắc khai sinh cho lĩnh vực của ta.

Đây là trích trông thế nào :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

Tính mạch lạc của trao đổi này khiến tôi bất ngờ lúc đọc đầu. ELIZA làm việc nhà trị liệu : diễn đạt lại, hỏi, khám phá. PARRY làm việc bệnh nhân hoang tưởng : than phiền, buộc tội, bày tỏ nghi ngờ. Cả hai chương trình giữ hoàn hảo vai nhờ kết hợp cơ chế tương ứng (mẫu ELIZA + mô hình cảm xúc PARRY) sinh ra phản hồi khớp nhau tình cờ. Khi bạn đọc toàn bộ bản ghi, bạn thấy sự căn chỉnh tình cờ này tạo ảo giác đối thoại có chủ ý rõ thế nào.

Bạn có thể tái tạo cuộc trò chuyện này bằng repo của tôi :

bun run meeting

Mô phỏng của tôi chạy 25 lượt tự động giữa hai bot, với chủ đề khởi đầu ngẫu nhiên (ngựa, tội phạm có tổ chức, cảm xúc...). Vì cả ELIZA và PARRY đều mang yếu tố không xác định (xoay vòng ELIZA, ngẫu nhiên hóa PARRY), mỗi lần chạy cho trao đổi khác nhau. Tôi mê chạy lại lệnh nhiều lần để so quỹ đạo.

Điều khiến tôi ấn tượng với ELIZA đấu PARRY là cuộc gặp giữa hai chương trình, một bên kiến trúc không trạng thái nội tại, một bên mô hình cảm xúc đầy đủ, cùng tạo ra cuộc trò chuyện trông như có chủ ý. Với 1972, tôi thấy kết quả choáng.


ALICE (1995) : khớp mẫu ở quy mô lớn

ALICE (Artificial Linguistic Internet Computer Entity) do Richard Wallace tạo năm 1995, và thắng Giải Loebner ba lần (2000, 2001, 2004). Nơi ELIZA có vài trăm quy tắc và PARRY vài nghìn, ALICE có 99 524, rải trong 66 tệp AIML. Khi tôi mở kho này lần đầu, tôi đo được "quy mô lớn" thời đó nghĩa là gì.

AIML : ngôn ngữ của danh mục

AIML (Artificial Intelligence Markup Language) là định dạng XML định nghĩa cặp hỏi-đáp :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

Nhưng sức mạnh của ALICE tới từ wildcard và SRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- tương đương <srai><star/></srai> -->
  </template>
</category>

SRAI cho phép ALICE chuyển hướng đầu vào sang danh mục khác, tạo chuỗi rút gọn :

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

Đó là cơ chế cho ALICE linh hoạt : thay vì viết phản hồi cho mỗi cách diễn đạt khả dĩ, bạn viết phản hồi chuẩn và chuyển các biến thể về nó. Giới hạn độ sâu là 10 : vượt quá, ALICE bỏ để tránh vòng lặp vô hạn (thiết kế danh mục tránh chúng cẩn thận, và lưới an toàn này vẫn thiết yếu). Tôi thấy lựa chọn thiết kế này thanh lịch vì nó tách hàng nghìn cách diễn đạt bằng rất ít quy tắc.

Cách ALICE khớp mẫu

Các mẫu được xếp theo đặc thù : mẫu ít wildcard nhất thử trước. Wildcard * và _ chụp bất kỳ chuỗi từ nào. Động cơ biên dịch mỗi mẫu thành regex, rồi duyệt các danh mục đã xếp tới khi thấy khớp.

// Triển khai TypeScript của chúng ta -- rút gọn mà trung thành
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

Vì sao ALICE thống trị Loebner

99 524 danh mục là con số thay đổi mọi thứ. ELIZA tạo ảo giác trí tuệ vì vài quy tắc của nó thiết kế tốt cho ngữ cảnh cụ thể (trị liệu). ALICE phủ nhiều chủ đề tới mức tạo cảm giác có văn hóa chung thật : khoa học, chính trị, hài, thể thao, cảm xúc, đủ hết. Tôi giữ từ cách này một bài học vẫn áp dụng : phủ rộng và chăm bù đắp thiếu suy luận sâu.

➡ Xem mã nguồn


Jabberwacky (1997) và Cleverbot (2008) : đứt gãy nhận thức luận

Mọi bot trước đều chung giả thuyết : phải viết phản hồi. ELIZA có quy tắc S-expression, PARRY có mẫu chọn lọc, ALICE có danh mục AIML. Rollo Carpenter đi ngược hoàn toàn với câu hỏi triệt để : nếu để các cuộc trò chuyện quá khứ cung cấp phản hồi thì sao? Tôi thấy đảo ngược này táo bạo vì nó bỏ mọi viết quy tắc tay.

Ý tưởng

Jabberwacky (ra mắt khoảng 1997, thành Cleverbot năm 2008) hoàn toàn dựa trên toàn bộ lịch sử trò chuyện lưu trong transcript phẳng, và khi ai đó nói với nó, nó tìm trong lịch sử khoảnh khắc tương tự nhất và tái dùng điều đã nói sau đó :

Người dùng: "hello"
  ↓
Tìm: có ai từng nói "hello" trước đây chưa?
  ↓
Có, trong phiên #3, dòng 14, ai đó nói "hello" và bot trả lời "hi there!"
  ↓
Trả lời: "hi there!"

Bạn sẽ thấy ở đây chỉ archive khổng lồ những điều người ta từng nói với nhau, tái dùng đúng lúc. Tôi thấy ở đó đúng định nghĩa nổi lên : hành vi hội thoại sinh từ tích lũy dấu vết con người.

Triển khai TypeScript

Bản port TypeScript của tôi tái tạo đúng kiến trúc này :

flowchart TD
    A["Đầu vào người dùng:<br>'hello'"] --> B["TranscriptStore<br>332 dòng seed + lịch sử"]
    B --> C["withReplies()<br>trích các cặp<br>(dòng → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>ngữ cảnh trước dòng này)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K ứng viên đã xếp"]
    I --> J{"pickReply()<br>chọn roulette-wheel"}
    J -->|"Chọn"| K["Reply = reply.text<br>của cặp thắng"]
    J -->|"Không có"| L["Dự phòng: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Nối vào transcript<br>save() → JSON"]
    L --> M

Đây là lõi chấm điểm, với heuristic của riêng tôi lấy cảm hứng từ mô tả công khai về Cleverbot :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : tương đồng giữa đầu vào người dùng và dòng lịch sử
  • contextFit (0.25) : tương đồng giữa cuộc trò chuyện gần đây và điều đứng trước dòng lịch sử
  • recencyBonus (0.10) : ký ức mới đáng hơn chút (cá tính bot trôi theo thời gian)

Chọn theo xác suất (roulette-wheel selection) : ứng viên tốt nhất thắng thường hơn, với phần biến thiên tạo đa dạng. Tôi trân trọng lựa chọn này vì nó khiến cuộc trò chuyện ít đoán được hơn khi bạn test bot nhiều lần.

Cleverbot : hai đổi mới được ghi lại

Cleverbot thêm hai cơ chế vào khái niệm nền của Jabberwacky :

  1. Học đa người : hàng triệu người dùng đóng góp vào cùng transcript chia sẻ. Phản hồi rút từ lịch sử có thể tới từ giọng hoàn toàn khác với cuộc trò chuyện hiện tại -- điều này giải thích vì sao Cleverbot đột ngột đổi cá tính.

  2. Học trì hoãn : điều bạn nói với Cleverbot trong phiên KHÔNG có sẵn để khớp trong chính phiên đó. Các dòng mới bị đánh dấu pending và chỉ thành khớp được sau "củng cố" giữa các phiên -- điều này giải thích vì sao bạn không thể dạy Cleverbot một sự kiện rồi tái dùng trong cùng cuộc trò chuyện.

// Cleverbot : các dòng mới vô hình tới khi củng cố
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() được gọi lúc khởi động, không phải trong phiên

Bản port TypeScript của tôi triển khai cả hai hành vi này : các dòng có cờ consolidated, và mỗi phiên REPL bắt đầu bằng củng cố các dòng chờ.

➡ Xem mã nguồn


Phân tích bản port TypeScript : thiết kế kiến trúc chung

Xây năm bot này cùng ngôn ngữ đặt tôi trước câu hỏi thú vị : có thể tách code chung giữa các kiến trúc khác nhau vậy không?

Câu trả lời của tôi sau khi mang hết : rất ít. Mỗi bot có vòng lặp nền khác nhau :

Bot Vòng lặp chính Dữ liệu Học
ELIZA Ngăn xếp keyword → phân rã → ráp lại Script .ela bằng S-expression Không có
PARRY Token hóa → mẫu chọn lọc / flare / keyword / suy luận 58 tệp PDP-10 (từ điển, niềm tin, quy tắc) Không có
ALICE Mẫu đã xếp → regex → mẫu AIML → SRAI đệ quy 66 tệp AIML XML Không có
Jabberwacky Tương đồng → ngữ cảnh → mới → chọn có trọng số Transcript JSON (lớn dần theo dùng) Liên tục
Cleverbot Giống Jabberwacky + pending/consolidated + persona Transcript JSON + seed đa persona Trì hoãn (giữa phiên)

Điều chúng chung là interface CLI và hạ tầng TypeScript (biome để lint, tsx để chạy). Phần còn lại riêng cho từng kiến trúc.

Lựa chọn thiết kế chung

1. Trung thành với dữ liệu gốc. Với ELIZA, PARRY và ALICE, tôi dùng tệp gốc, script ELIZA tìm lại trong lưu trữ Weizenbaum năm 2021, mã PARRY gốc của PDP-10 (58 tệp), AIML Free ALICE v1.6. Tôi giữ chúng ở dạng gốc. Các bot hành xử như bản gốc vì chúng dùng cùng dữ liệu.

2. Phòng sạch cho phần độc quyền. Jabberwacky và Cleverbot đặt ca khác : mã nguồn của chúng chưa bao giờ công bố (Existor/Rollo Carpenter giữ độc quyền). Các bản port của tôi vì vậy là tái triển khai phòng sạch, dựng duy nhất từ mô tả công khai về hành vi. Tôi không chép dòng mã hay dữ liệu độc quyền nào.

3. Phụ thuộc tối thiểu. Điều kiện thật duy nhất là TypeScript. ALICE dùng dom-js để parse XML của tệp AIML (66 tệp, 99 524 danh mục, parse XML tự làm sẽ phí thời gian). Phần còn lại là TypeScript thuần. Tôi chọn vậy để mỗi bot dễ đọc và dễ chạy khi bạn clone repo.


Từ chatbot ký hiệu tới LLM : bước nhảy khái niệm

Năm bot tôi vừa giới thiệu với bạn chung đặc điểm nền : chúng mang tính ký hiệu. "Tri thức" của chúng hoàn toàn dựa trên ký hiệu rõ : mẫu văn bản, bảng quy tắc, danh mục XML, dòng transcript. Chúng chạy hoàn toàn không cần biểu diễn số của ngôn ngữ.

Chúng vì vậy chung trần kính : chúng chỉ trả lời điều đã rõ ràng định trước hay ghi lại. ELIZA lạc lối nếu bạn ra khỏi khung trị liệu. PARRY chỉ nói về chủ đề hoang tưởng của nó. ALICE không học gì từ cuộc trò chuyện. Jabberwacky chỉ trả lời bằng câu đã thốt. Khi bạn test các giới hạn này, bạn nhanh hiểu vì sao thế hệ này chạm trần.

LLM (Large Language Model) vượt trần này bằng cách đổi triệt để mô hình : thay vì thao tác ký hiệu, chúng đổi ngôn ngữ thành số và học quan hệ thống kê giữa các số đó. Chúng sinh từng token tức thì bằng cách tính xác suất, từ tri thức mã hóa trong trọng số thay vì phản hồi viết sẵn. Tôi thấy bước chuyển này cuốn hút vì nó đảo hoàn toàn logic các bot trước. Xem nhanh nó chạy thế nào.

1. Tokenization

Bước đầu là cắt văn bản thành token -- đơn vị nhỏ hơn từ nhưng lớn hơn ký tự :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

Mỗi token có ID số trong từ vựng (thường 32 000 tới 128 000 token với mô hình gần đây). Phân mảnh này cho phép mô hình xử lý từ nó chưa từng thấy bằng cách rã chúng thành từ con đã biết.

2. Embeddings

Mỗi ID token được đổi thành vector -- bảng số thực (thường 4096 chiều với mô hình cỡ vừa). Vector này là nhúng (embedding) mã hóa nghĩa của token trong không gian toán nơi các token gần nghĩa có vector gần nhau :

vecteur("roi")  − vecteur("homme") + vecteur("femme")  ≈  vecteur("reine")

Thuộc tính này nổi lên từ huấn luyện, không cần lập trình rõ của tôi hay các nhà nghiên cứu. Tôi thấy ở đó hệ quả trực tiếp của cách các từ xuất hiện trong ngữ cảnh tương tự.

3. Attention

Cơ chế attention (giới thiệu bởi bài "Attention is All You Need" năm 2017) là thứ khiến LLM khả thi. Với mỗi token, attention tính các token khác trong câu nào quan trọng để hiểu token này :

"La banque a refusé mon prêt."
      ↑
Token "banque" nhìn: "refusé", "prêt" → hiểu đó là tổ chức tài chính

"Je vais me promener sur la banque."
      ↑
Token "banque" nhìn: "promener", "sur" → hiểu đó là bờ sông

Attention cho phép mô hình nắm ngữ cảnh : mỗi token được hiểu tùy những token quanh nó, trong cách đọc quan hệ đầy đủ. Chính nhạy cảm với ngữ cảnh này giúp tôi hiểu nhất vì sao LLM vượt mẫu cố định.

4. Dự đoán token tiếp theo

Huấn luyện LLM dựa trên đơn giản đánh lừa : bạn cho nó xem văn bản, bạn giấu token cuối, và bạn bảo nó đoán. Rồi bạn lặp lại hàng tỷ lần. Tôi thích ý này vì quy tắc học rất đơn giản sinh năng lực rất giàu.

Đầu vào:  "Je ne comprends"
Giấu:  "pas"
Dự đoán của mô hình: "pas" (xác suất 0.87), "rien" (0.05), "jamais" (0.02)...

Mục tiêu là tối đa hóa xác suất của token thật ở mỗi vị trí. Đó gọi là dự đoán token tiếp theo. Trong huấn luyện, mô hình chỉnh hàng tỷ tham số để tối thiểu hóa lỗi dự đoán trên terabyte văn bản.

Lúc suy luận (khi bạn nói với nó), mô hình sinh từng token một theo vòng :

Token 1: "Je"    (đầu vào: "Parle-moi de toi.")
Token 2: "suis"  (đầu vào: "Parle-moi de toi. Je")
Token 3: "un"    (đầu vào: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (đầu vào: "Parle-moi de toi. Je suis un")
...

Mỗi token được lấy mẫu theo xác suất (temperature, top-k, top-p kiểm soát mức "sáng tạo"). Và hết. Hàng tỷ tham số lặp lại thao tác này hàng nghìn lần. Tôi thấy chóng mặt rằng cơ chế lặp lại vậy sinh phản hồi đa dạng vậy.

Điều thay đổi về nền

Khía cạnh Bot ký hiệu (ELIZA, PARRY, ALICE) LLM hiện đại
Biểu diễn Từ và quy tắc rõ Vector số (embedding)
Sinh Chọn trong phản hồi viết sẵn Dự đoán xác suất từng token
Tri thức Lưu trong tệp quy tắc Mã hóa trong trọng số mạng
Học Tay (viết quy tắc) Tự động (huấn luyện trên kho)
Vững Không có ngoài mẫu định trước Khái quát tới đầu vào chưa thấy
Diễn giải được Hoàn hảo (có thể đọc quy tắc) Hạn chế (hộp đen)

Chatbot cổ điển trong suốt mà dễ vỡ. LLM vững mà mờ. Ngày nay tôi xem chúng như công cụ bổ sung cho nhu cầu khác nhau, và tôi chọn bên này hay bên kia tùy mức kiểm soát bạn muốn giữ trên phản hồi.

Nếu bạn muốn đào sâu cách LLM chạy bên trong, tôi khuyên video này làm tài nguyên xuất sắc :

How LLMs Work — YouTube

Luna Protocol : tổng hợp hiện đại

Các bài về Luna Protocol (link bên dưới) theo mắt tôi là tổng hợp hoàn thiện nhất của mọi thứ tôi vừa cho bạn thấy : bot Discord hiện đại kết hợp LLM cục bộ với hệ thống hành vi tinh vi, tất cả dựng trên bài học của 60 năm AI hội thoại. Đó là dự án giúp tôi nối các bản port lịch sử với thử nghiệm hiện tại của mình.

Luna Protocol : tôi đã tạo bot Discord tự chủ mô phỏng con người

Bài này trình bày chi tiết kiến trúc đầy đủ của bot Discord dựa trên LLM :

  • Hệ thống kích hoạt theo ưu tiên (mention > DM > tên > keyword > follow-up > ngẫu nhiên)
  • Hành vi con người : tập trung thay đổi, lỗi gõ phím, ngập ngừng (15%), quên lãng (3%), mệt mỏi chủ đề
  • Giờ ngủ : bot ngủ, chậm lại, hay bỏ qua tùy giờ
  • Pipeline TTS : tổng hợp giọng qua Piper + ffmpeg → tin nhắn thoại Discord
  • Streaming thời gian thực : LLM phát token từng cái lên bus sự kiện có kiểu

Điều nối bài này với chatbot lịch sử là cùng cuộc tìm kiếm : khiến người ta tin mình nói với con người. ELIZA dùng gương văn bản. PARRY dùng mô hình cảm xúc. ALICE dùng 99k danh mục. Luna Protocol dùng LLM fine-tune + hệ thống hành vi mô phỏng sự không hoàn hảo con người. Tôi thấy trong dòng dõi này cùng đòi hỏi : tạo ảo giác hiện diện ở đầu bên kia màn hình.

Luna Protocol : vì sao tôi fine-tune mô hình 1,5B

Bài thứ hai khám phá fine-tuning và few-shot priming. Phát hiện trung tâm của tôi : mô hình nhỏ hơn (1,5B) huấn luyện trên ít dữ liệu hơn (50k mẫu) vượt mô hình lớn hơn (3B) khi mồi đúng bằng ví dụ few-shot. Tôi đã thực sự bất ngờ với kết quả này khi đo nó.

Đó là bài học vang thẳng với chatbot lịch sử :

  • ELIZA cho thấy với vài quy tắc thiết kế tốt, ta có thể mô phỏng thấu hiểu
  • ALICE cho thấy với 99k danh mục, ta có thể mô phỏng văn hóa chung
  • Luna Protocol cho thấy với fine-tuning tốt và 5 ví dụ few-shot, LLM nhỏ có thể mô phỏng con người

Kỹ thuật đổi, và nguyên tắc giữ nguyên : chất lượng dữ liệu và độ chính xác của hệ thống đáng hơn kích thước thô. Đó là niềm tin tôi bảo vệ trong chính thử nghiệm của mình.


Kết luận : ba điều cần giữ

1. AI hội thoại không khởi đầu với ChatGPT. ELIZA 60 tuổi. PARRY đã qua test Turing năm 1972. ALICE đã thắng Loebner ba lần. Jabberwacky đã đặt nền học bằng transcript, mà Cleverbot công nghiệp hóa ở quy mô lớn. Mỗi cách mang một mảnh ghép. Đó là điều các bản port xác nhận với tôi.

2. Nhiều dữ liệu hơn ≠ thông minh hơn. Transcript của Jabberwacky không có quy tắc. 99k danh mục của ALICE không học. Fine-tuning của Luna Protocol trên 50k mẫu vượt mô hình 3B. Tri thức thông thường bảo "càng lớn càng tốt" -- lịch sử chatbot cho thấy kiến trúc và thiết kế đáng ngang kích thước. Tôi bảo vệ ý này trong mỗi lựa chọn kỹ thuật của mình.

3. Bài toán giống nhau suốt 60 năm. Làm sao khiến con người tin mình nói với con người khác? ELIZA trả lời bằng gương văn bản. PARRY bằng giận dữ mô phỏng. ALICE bằng sự kiện. Luna Protocol bằng LLM biết ngủ và mắc lỗi gõ phím. Giải pháp đổi, nhu cầu giữ. Chính liên tục này khiến tôi muốn tiếp tục khám phá.

Repo open source, bạn có thể clone, chạy từng bot, và tự thấy 60 năm AI hội thoại gói trong một repo TypeScript duy nhất. Hy vọng bạn sẽ vui như tôi khi khiến chúng nói.

Tài nguyên Link
Repo GitHub fox3000foxy/chatbots
Luna Protocol -- kiến trúc bot Đọc bài
Luna Protocol -- fine-tuning few-shot Đọc bài
Script ELIZA gốc anthay/ELIZA
Mã PARRY gốc lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 gốc PARRY Encounters the DOCTOR
Giải thích xuất sắc cách LLM chạy https://www.youtube.com/watch?v=YmLp8qe87A0

จาก ELIZA สู่ LLM : 60 ปีของ AI เชิงสนทนา สร้างใหม่ใน TypeScript

ELIZA, PARRY, ALICE, Jabberwacky, Cleverbot -- ห้าสถาปัตยกรรมที่แตกต่างกันโดยสิ้นเชิงสำหรับปัญหาเดียวกัน พอร์ตมาเป็น TypeScript พร้อมข้อมูลต้นฉบับ ตั้งแต่ปี 1966 ถึง LLM สมัยใหม่ ว่าแล้วว่า AI เชิงสนทนาเรียนรู้ที่จะพูดได้อย่างไร และสิ่งที่ repo แชทบอทสอนเราเกี่ยวกับ 60 ปีแห่งการวิจัย

จาก ELIZA สู่ LLM : 60 ปีของ AI เชิงสนทนา สร้างใหม่ใน TypeScript

ปี 1966 Joseph Weizenbaum เขียนโค้ด MAD-SLIP 420 บรรทัดบน IBM 7094 เพื่อสร้างแชทบอทตัวแรกในประวัติศาสตร์ โปรแกรมชื่อ ELIZA มันจำลองนักจิตบำบัดแนวโรเจอเรียนด้วยแพทเทิร์นพื้นฐานและการสลับประโยค หกทศวรรษต่อมา AI เชิงสนทนากลายเป็นหัวข้อหลัก -- ChatGPT, Claude, Gemini อยู่ในการสนทนาทุกครั้ง

แต่ระหว่างสองขั้วนี้ มี PARRY (แชทบอทโรคหวาดระแวง, 1972), ALICE (ราชาแห่ง AIML 99,000 หมวดหมู่, 1995), Jabberwacky (ตัวแรกที่เรียนรู้โดยไม่มีกฎ, 1997), และ Cleverbot (ผู้สืบทอดเชิงอุตสาหกรรม, 2008) ห้าโปรแกรม ห้าสถาปัตยกรรม หนึ่งปัญหาเดียว : ทำให้เครื่องจักรพูดได้

Repo นี้ประกอบด้วยบอททั้งห้าตัว พอร์ตมาเป็น TypeScript พร้อมข้อมูลต้นฉบับ -- สคริปต์ ELIZA, พจนานุกรม PARRY, ไฟล์ AIML ของ ALICE แต่ละพอร์ตทำงานอิสระ พร้อมใช้งาน และมีเอกสารครบถ้วน เป้าหมายไม่ใช่แค่ทำให้มันทำงานได้ : คือการเข้าใจว่ามันทำงานอย่างไร ทำไมมันถึงสร้างประวัติศาสตร์ และสถาปัตยกรรมของมันสอนอะไรเราเกี่ยวกับ AI เมื่อวาน... และวันนี้

bun run eliza    # คุยกับ ELIZA (1966)
bun run parry    # คุยกับ PARRY (1972)
bun run alice    # คุยกับ ALICE (1995)
bun run jabber   # คุยกับ Jabberwacky
bun run cleverbot # คุยกับ Cleverbot
bun run meeting  # ELIZA vs PARRY อัตโนมัติ

เราจะเจาะลึกแต่ละบอท ดูโค้ดของมัน แล้วเชื่อมโยงกับ LLM สมัยใหม่ผ่านบทความเกี่ยวกับ Luna Protocol


ELIZA (1966) : ศิลปะแห่งการทำให้เชื่อว่าคุณเข้าใจ

เริ่มจากตัวที่เก่าที่สุด และอาจจะน่าประทับใจที่สุดในความเรียบง่าย ELIZA ไม่มีปัญญา ในความหมายสมัยใหม่ ไม่มีโครงข่ายประสาทเทียม ไม่มีสถิติ ไม่มีการเรียนรู้ แค่แพทเทิร์นข้อความและการสลับเล็กน้อย

หลักการ

สคริปต์ DOCTOR (เวอร์ชันนักจิตบำบัด) ทำงานด้วยตาราง คีย์เวิร์ด แต่ละตัวเชื่อมโยงกับ แพทเทิร์นการแยกส่วน และ กฎการประกอบใหม่ นี่คือกฎทั่วไป :

(HELLO
    ((0)
        (HOW DO YOU DO.  PLEASE STATE YOUR PROBLEM)))

HELLO คือคีย์เวิร์ด 0 คือแพทเทิร์นการแยกส่วนที่บอกว่า "จับทุกอย่างที่ตามมา" (เหมือน wildcard) HOW DO YOU DO. PLEASE STATE YOUR PROBLEM. คือกฎการประกอบใหม่ เท่านั้นเอง

เมื่อคุณพูด "Hello, I'm sad today" ELIZA :

  1. แปลงข้อความเป็นตัวพิมพ์ใหญ่ : HELLO I'M SAD TODAY
  2. สแกนแต่ละคำเทียบกับตารางคีย์เวิร์ด
  3. เจอ HELLO → ดันลงกองซ้อนคีย์เวิร์ด
  4. เอาคีย์เวิร์ดที่มีลำดับความสำคัญสูงสุด
  5. ลองแต่ละแพทเทิร์นการแยกส่วนตามลำดับ
  6. ถ้าตรงกัน เลือกกฎการประกอบใหม่ถัดไป (round-robin)
  7. แทนที่ (1), (2) ฯลฯ ด้วยส่วนที่ถูกจับ

แต่ส่วนที่ฉลาดจริงๆ คือ กฎ PRE ดูนี่ :

(MY
    ((0)
        (PRE (1 0) (=YOU))))

เมื่อ ELIZA ตรงกับ MY มันแปลงส่วนที่เหลือของประโยค (ที่ถูกจับโดย 0) ผ่านกฎ PRE แล้วส่งผลลัพธ์กลับเข้าไปใหม่ราวกับว่าผู้ใช้เพิ่งพูดคีย์เวิร์ดใหม่ อย่างเป็นรูปธรรม :

คุณพูด : "My mother hates me"
  → PRE แปลง : "YOUR MOTHER HATES YOU"
  → ส่งกลับราวกับคุณเพิ่งพูดมัน
  → อาจตรงกับ "YOU" → คำตอบใหม่

นี่คือสาเหตุที่ ELIZA ดูเหมือนเข้าใจความแตกต่างระหว่าง "ฉัน" และ "คุณ" -- มันไม่ใช่ความเข้าใจ มันคือการแปลงเชิงกลที่ออกแบบมาอย่างสมบูรณ์แบบ

นี่คือโฟลว์ทั้งหมด จากการพิมพ์ของผู้ใช้ถึงคำตอบ :

flowchart TD
    A["User input:<br>'Hello, I'm sad'"] --> B["elizaUppercase()<br>normalise la ponctuation"]
    B --> C["splitUserInput()<br>découpe en mots"]
    C --> D["Build keyword stack<br>ordonné par priorité"]
    D --> E{"Stack non-vide?"}
    E -->|"Oui"| F["Pop highest-priority keyword"]
    E -->|"Non"| G{"Memory recall?"}
    G -->|"Oui"| H["Recall past user statement"]
    G -->|"Non"| I["Fallback: zNONE rule"]
    I --> J["Return response"]
    H --> J
    F --> K["Match decomposition patterns"]
    K --> L{"Match found?"}
    L -->|"Non"| M{"Linked keyword?"}
    M -->|"Oui"| N["Push linked keyword to stack"]
    N --> E
    M -->|"Non"| O["Return NOMATCH"]
    O --> J
    L -->|"Oui"| P["Select next reassembly (round-robin)"]
    P --> Q{"Reassembly type?"}
    Q -->|"PRE"| R["Transform words (I→YOU)<br>push link keyword"]
    R --> N
    Q -->|"NEWKEY"| S["Skip to next keyword"]
    S --> E
    Q -->|"Standard"| T["Expand (1), (2), (0)<br>into final response"]
    T --> J

สิ่งที่ทำให้มันน่าเชื่อถือ

Weizenbaum เลือกอย่างชาญฉลาด : จิตบำบัดแนวโรเจอเรียน วิธีนี้คือการสะท้อนคำพูดของผู้ป่วยโดยไม่ตีความ "ผมเศร้า" → "คุณบอกว่าคุณเศร้า" นี่คือสิ่งที่ ELIZA ทำได้ -- และเนื่องจากเป็นเทคนิคการบำบัดที่ได้รับการยอมรับ ไม่มีใครคิดว่ามันแปลก

ในพอร์ต TypeScript

พอร์ตโหลดสคริปต์ .ela (รูปแบบ S-expression ดั้งเดิม), แยกส่วนทั้งหมด (รวมถึงการเข้ารหัส Hollerith -- รูปแบบสตริงจากยุค 60s), และดำเนินวงจรเดียวกัน : พิมพ์ใหญ่ → แยก → กองซ้อนคีย์เวิร์ด → การแยกส่วน → การประกอบใหม่ → PRE/transforms

➡ ดูซอร์สโค้ด


PARRY (1972) : แชทบอทตัวแรกที่มีอารมณ์

หกปีหลังจาก ELIZA, Kenneth Colby (จิตแพทย์ที่ Stanford) สร้าง PARRY : แชทบอทที่จำลองผู้ป่วยโรค จิตเภทแบบหวาดระแวง ในขณะที่ ELIZA เป็นกระจกเปล่า PARRY มี แบบจำลองอารมณ์ภายใน ที่แท้จริง

แบบจำลองอารมณ์

PARRY มีตัวแปรต่อเนื่องสี่ตัวที่เปลี่ยนแปลงในแต่ละรอบสนทนา :

ตัวแปร เส้นฐาน การลดลง/รอบ คำอธิบาย
ANGER 0 −1.0 ความเป็นศัตรู, ความหงุดหงิด
FEAR 0 −0.2 ความหวาดระแวง (ลดลงช้าหลังจากเริ่มมีอาการหลอน)
MISTRUST 0 −0.05 ความไม่ไว้วางใจ (ลดลงช้ามาก)
HURT 0 −0.5 ความเจ็บปวดทางอารมณ์

ค่าเหล่านี้เพิ่มขึ้นผ่าน การกระโดดทางอารมณ์ (ajump, fjump, hjump) ที่ถูกกระตุ้นโดยกฎการอนุมาน และลดลงตามธรรมชาติกลับสู่เส้นฐานในแต่ละรอบ

เครือข่ายความเชื่อ

PARRY มีความเชื่อ 200+ รายการเก็บในไฟล์ bel :

(BELIEF (FEAR 5) ((PAT PARANOIA)) BELIEF GROUP)

แต่ละความเชื่อมีหมวดหมู่ (HUM = ผู้ป่วย, HUM2 = ผู้อื่น, DOC = หมอ, INT = การสอบสวน, INN = เจตนา) และความแข็งแกร่ง (0-5) กฎการอนุมาน (TH2, EMOTE, IF) เชื่อมโยงความเชื่อถึงกัน :

  • TH2 : ถ้าความเชื่อ A เกินเกณฑ์ มันจะแข็งแกร่งขึ้นและผลลัพธ์ของมันเพิ่มขึ้น
  • EMOTE : ถ้าความเชื่อเกินเกณฑ์ มันจะกระตุ้นการกระโดดทางอารมณ์ (anger/fear/hurt)
  • IF : เงื่อนไข -- ถ้า A เป็นจริง แล้ว B จะเป็นจริงในระดับหนึ่ง

ลำดับชั้นของอาการหลอน (ระบบ Flare)

ส่วนที่น่าทึ่งที่สุดของ PARRY คือระบบ "flares" -- ห่วงโซ่การเพิ่มระดับที่ค่อยๆ นำไปสู่อาการหลอนหลัก :

HORSE → "I USED TO GO TO THE RACES SOMETIMES."
  ↓
RACE → "I KNOW PEOPLE WHO GO TO THE TRACK."
  ↓
MONEY → "MONEY IS TIGHT. I DON'T HAVE MUCH."
  ↓
GAMBLE → "I'VE DONE SOME GAMBLING. IT'S DANGEROUS."
  ↓
BOOKIE → "BOOKIES ARE CROOKED. THEY WORK FOR THE MAFIA."
  ↓
CHEAT → "PEOPLE ARE ALWAYS TRYING TO CHEAT ME."
  ↓
MAFIA → "THE MAFIA IS OUT TO GET ME."

แต่ละคีย์เวิร์ดกระตุ้นคำตอบที่เขียนไว้ล่วงหน้า (ผ่านการจับคู่แพทเทิร์น) และถ้าคู่สนทนาตามหัวข้อ PARRY จะค่อยๆ เลื่อนไปสู่อาการหลอนกลางเรื่องการถูกข่มเหง เมื่อ flare ถูก "กระตุ้น" มันจะกลายเป็นไม่ทำงาน (deadFlares) -- PARRY ไปยังอันถัดไป จำลองคู่สนทนาที่กำลังเจาะลึกหัวข้อ

ทำไม PARRY ถึงเป็นประวัติศาสตร์

ปี 1972 PARRY ผ่านการทดสอบทัวริงรูปแบบหนึ่ง : จิตแพทย์ได้รับบทถอดความสนทนากับ PARRY และกับผู้ป่วยหวาดระแวงจริง และ ไม่สามารถแยกแยะได้อย่างน่าเชื่อถือ มันเป็นโปรแกรมแรกที่ทำได้ 40 ปีก่อน LLM

➡ ดูซอร์สโค้ด


RFC 439 : เมื่อ ELIZA พบ PARRY

วันที่ 18 กันยายน 1972 โปรแกรมสองตัวพูดคุยกันเป็นครั้งแรกในประวัติศาสตร์ -- ตามตัวอักษร : นี่คือ การสนทนาระหว่าง AI ครั้งแรก ที่เคยบันทึกไว้ ELIZA (รันบน BBN ที่บอสตัน) และ PARRY (รันที่สแตนฟอร์ด) สนทนากันผ่าน ARPANET โดยมีผู้ปฏิบัติการมนุษย์คอยพิมพ์เอาต์พุตของตัวหนึ่งเป็นอินพุตของอีกตัว บทสนทนาถูกตีพิมพ์เป็น RFC 439 ("PARRY Encounters the DOCTOR")

นี่คือตัวอย่างว่ามันเป็นอย่างไร :

ELIZA: HOW DO YOU DO. PLEASE STATE YOUR PROBLEM.
PARRY: PEOPLE GET ON MY NERVES SOMETIMES.
ELIZA: I AM NOT SURE I UNDERSTAND YOU FULLY.
PARRY: YOU ARE MAKING ME ANGRY.
ELIZA: SUPPOSE YOU TELL ME ABOUT YOUR PARENTS.
PARRY: THEY ARE ALWAYS AFRAID OF SOMETHING.

มันสอดคล้องกันอย่างน่าประหลาดใจ ELIZA ทำงานนักบำบัดของมัน : ถอดความ, ถาม, สำรวจ PARRY ทำงานผู้ป่วยหวาดระแวงของมัน : บ่น, กล่าวหา, แสดงความไม่ไว้วางใจ โปรแกรมทั้งสองอยู่ในบทบาทของตนอย่างสมบูรณ์ -- ไม่ใช่เพราะพวกมัน "เข้าใจ" สถานการณ์ แต่เพราะกลไกของมัน (แพทเทิร์น ELIZA + แบบจำลองอารมณ์ PARRY) สร้างคำตอบที่บังเอิญเข้ากัน

Repo สามารถจำลองการสนทนานี้ได้ด้วย :

bun run meeting

การจำลองจะรัน 25 รอบอัตโนมัติระหว่างบอททั้งสอง ด้วยหัวข้อเริ่มต้นสุ่ม (ม้า, อาชญากรรมองค์กร, อารมณ์...) เนื่องจากทั้ง ELIZA และ PARRY มีองค์ประกอบที่ไม่ตายตัว (ELIZA round-robin, PARRY การสุ่ม) แต่ละครั้งที่รันจะให้ผลลัพธ์ต่างกัน

สิ่งที่โดดเด่นเกี่ยวกับ ELIZA vs PARRY คือเรามีสองโปรแกรม -- หนึ่งไม่มีสถานะภายใน, อีกหนึ่งมีแบบจำลองอารมณ์สมบูรณ์ -- ที่ร่วมกันสร้างบทสนทนาที่ ดูเหมือน ตั้งใจ สำหรับปี 1972 มันน่าทึ่งมาก


ALICE (1995) : การจับคู่แพทเทิร์นขนาดใหญ่

ALICE (Artificial Linguistic Internet Computer Entity) ถูกสร้างโดย Richard Wallace ในปี 1995 และชนะ รางวัล Loebner สามครั้ง (2000, 2001, 2004) ในขณะที่ ELIZA มีกฎไม่กี่ร้อยข้อและ PARRY ไม่กี่พัน ALICE มี 99,524 กฎ -- กระจายอยู่ใน 66 ไฟล์ AIML

AIML : ภาษาของหมวดหมู่

AIML (Artificial Intelligence Markup Language) คือรูปแบบ XML สำหรับกำหนดคู่คำถาม-คำตอบ :

<category>
  <pattern>WHAT IS YOUR NAME</pattern>
  <template>My name is ALICE.</template>
</category>

แต่พลังของ ALICE มาจาก wildcards และ SRAI (Symbolic Reduction) :

<category>
  <pattern>_ IS YOUR NAME</pattern>
  <template>
    <sr/>  <!-- เท่ากับ <srai><star/></srai> -->
  </template>
</category>

SRAI ทำให้ ALICE สามารถเปลี่ยนเส้นทางอินพุตไปยังหมวดหมู่อื่น สร้างห่วงโซ่การลดรูป :

Input: "WHAT'S UP?"
  → pattern "WHAT IS UP" → srai "HELLO"
    → pattern "HELLO" → template "Hi there!"

นี่คือกลไกที่ทำให้ ALICE มีความยืดหยุ่น : แทนที่จะเขียนคำตอบสำหรับทุกรูปแบบ เขียนคำตอบหลักแล้วเปลี่ยนเส้นทางรูปแบบต่างๆ ไปหามัน ข้อจำกัดความลึกคือ 10 -- เกินกว่านั้น ALICE จะยอมแพ้เพื่อหลีกเลี่ยงลูปอนันต์ (หลีกเลี่ยงอย่างระมัดระวังในการออกแบบหมวดหมู่ แต่ตาข่ายนิรภัยยังจำเป็น)

ALICE จับคู่แพทเทิร์นอย่างไร

แพทเทิร์นถูกเรียงตามความจำเพาะ : พวกที่มี wildcard น้อยที่สุดจะถูกลองก่อน Wildcard * และ _ จับลำดับคำใดก็ได้ เอ็นจินคอมไพล์แต่ละแพทเทิร์นเป็น regex แล้ววนซ้ำหมวดหมู่ที่เรียงแล้วจนกว่าจะเจอที่ตรงกัน

// การใช้งาน TypeScript ของเรา -- เรียบง่ายแต่เที่ยงตรง
function findMatch(input: string, categories: Category[]): Match | null {
  for (const cat of categories) {
    const regex = patternToRegex(cat.pattern);
    const match = input.match(regex);
    if (match) return { category: cat, wildcards: extractWildcards(match) };
  }
  return null;
}

ทำไม ALICE ถึงครอง Loebner

99,524 หมวดหมู่ คือตัวเลขที่เปลี่ยนแปลงทุกอย่าง ELIZA ดูฉลาดเพราะกฎไม่กี่ข้อของมันถูกออกแบบมาอย่างดีสำหรับบริบทเฉพาะ (การบำบัด) ALICE ครอบคลุมหลายหัวข้อจนให้ความรู้สึกว่ามีความรู้ทั่วไปจริง : วิทยาศาสตร์, การเมือง, อารมณ์ขัน, กีฬา, อารมณ์, ทุกอย่าง

➡ ดูซอร์สโค้ด


Jabberwacky (1997) และ Cleverbot (2008) : การแตกหักทางญาณวิทยา

บอทก่อนหน้านี้ทั้งหมดมีสมมติฐานร่วมกัน : ต้องเขียนคำตอบ ELIZA มีกฎ S-expression, PARRY มีแพทเทิร์นแบบเลือกเฉพาะ, ALICE มีหมวดหมู่ AIML Rollo Carpenter กลับด้านโดยสิ้นเชิง : แล้วถ้าเราไม่เขียนอะไรเลยล่ะ?

แนวคิด

Jabberwacky (เปิดตัวประมาณ 1997, กลายเป็น Cleverbot ใน 2008) ไม่ได้เก็บ กฎใดๆ มันเก็บ ประวัติการสนทนาทั้งหมด ใน transcript แบบ flat และเมื่อมีคนคุยกับมัน มันจะค้นหาในประวัติศาสตร์นี้หาช่วงเวลาที่คล้ายที่สุดและใช้สิ่งที่ถูกพูดหลังจากนั้น :

ผู้ใช้ : "hello"
  ↓
ค้นหา : มีใครเคยพูด "hello" มาก่อนไหม?
  ↓
ใช่ ในเซสชัน #3, บรรทัดที่ 14, มีคนพูด "hello" และบอทตอบ "hi there!"
  ↓
ตอบ : "hi there!"

ไม่มีแพทเทิร์น ไม่มีไวยากรณ์ ไม่มี XML แค่คลังข้อมูลขนาดใหญ่ของสิ่งที่คนพูดกัน ถูกนำมาใช้ใหม่ในเวลาที่เหมาะสม นี่คือนิยามของการเกิด emergent

การใช้งาน TypeScript

พอร์ต TypeScript สร้างสถาปัตยกรรมนี้ขึ้นมาใหม่ :

flowchart TD
    A["User input:<br>'hello'"] --> B["TranscriptStore<br>332 lignes seed + historique"]
    B --> C["withReplies()<br>extrait les paires<br>(ligne → reply)"]
    C --> D["findCandidates()"]
    D --> E["relevance = similarity(input, line.text)"]
    E --> F["contextFit = similarity(recentContext,<br>context avant cette ligne)"]
    F --> G["recencyBonus = 1 / (1 + ageDays/30)"]
    G --> H["score = 0.65×relevance<br>+ 0.25×contextFit<br>+ 0.10×recency"]
    H --> I["Top K candidats triés"]
    I --> J{"pickReply()<br>roulette-wheel<br>selection"}
    J -->|"Pick"| K["Reply = reply.text<br>de la paire gagnante"]
    J -->|"Aucun"| L["Fallback: 'I have no idea<br>what to say to that yet.'"]
    K --> M["Append au transcript<br>save() → JSON"]
    L --> M

นี่คือแกนหลักของการให้คะแนน -- ฮิวริสติกของเราเองที่ได้แรงบันดาลใจจากคำอธิบายสาธารณะของ Cleverbot :

const score = 0.65 * relevance + 0.25 * contextFit + 0.10 * recencyBonus;
  • relevance (0.65) : ความคล้ายคลึงระหว่างอินพุตผู้ใช้กับบรรทัดประวัติ
  • contextFit (0.25) : ความคล้ายคลึงระหว่างการสนทนาล่าสุดกับบริบทย้อนหลังจากบรรทัดประวัติ
  • recencyBonus (0.10) : ความทรงจำล่าสุดมีน้ำหนักมากกว่าเล็กน้อย (บุคลิกบอทเปลี่ยนแปลงตามเวลา)

การเลือกเป็นแบบความน่าจะเป็น (roulette-wheel selection) : ผู้สมัครที่ดีที่สุดชนะบ่อยกว่า แต่ไม่เสมอไป -- ซึ่งให้ความหลากหลาย

Cleverbot : สองนวัตกรรมที่มีเอกสาร

Cleverbot เพิ่มกลไกสองอย่างให้แนวคิดพื้นฐานของ Jabberwacky :

  1. การเรียนรู้หลายคน : ผู้ใช้หลายล้านคนมีส่วนร่วมใน transcript ร่วมกัน คำตอบที่ดึงมาจากประวัติอาจมาจากเสียงที่แตกต่างอย่างสิ้นเชิงจากการสนทนาปัจจุบัน -- ซึ่งอธิบายว่าทำไม Cleverbot เปลี่ยนบุคลิกกะทันหัน

  2. การเรียนรู้แบบหน่วงเวลา : สิ่งที่คุณพูดกับ Cleverbot ระหว่างเซสชันจะ ไม่ พร้อมใช้งานสำหรับการจับคู่ระหว่างเซสชันนั้น บรรทัดใหม่ถูกทำเครื่องหมาย pending และจะจับคู่ได้หลังจากการ "รวม consolidated" ระหว่างเซสชันเท่านั้น -- ซึ่งอธิบายว่าทำไมคุณไม่สามารถสอนข้อเท็จจริงให้ Cleverbot และใช้มันซ้ำในการสนทนาเดียวกันได้

// Cleverbot : บรรทัดล่าสุดมองไม่เห็นจนกว่าจะรวม consolidated
const line = store.append("human", text, null, sessionId, false); // pending
// ...consolidate() ถูกเรียกเมื่อเริ่มต้น ไม่ใช่ระหว่างเซสชัน

พอร์ต TypeScript ใช้ทั้งสองพฤติกรรมนี้ : บรรทัดมีแฟล็ก consolidated และแต่ละเซสชัน REPL เริ่มด้วยการรวมบรรทัดที่รออยู่

➡ ดูซอร์สโค้ด


วิเคราะห์พอร์ต TypeScript : การออกแบบสถาปัตยกรรมร่วม

การสร้างบอททั้งห้าตัวด้วยภาษาเดียวกัน คือการเผชิญหน้ากับคำถามที่น่าสนใจ : เราสามารถแยกส่วนโค้ดระหว่างสถาปัตยกรรมที่แตกต่างกันเช่นนี้ได้ไหม?

คำตอบคือ : น้อยมาก แต่ละบอทมีลูปพื้นฐานที่แตกต่างกัน :

บอท ลูปหลัก ข้อมูล การเรียนรู้
ELIZA กองซ้อนคีย์เวิร์ด → การแยกส่วน → การประกอบใหม่ สคริปต์ .ela ในรูปแบบ S-expression ไม่มี
PARRY การทำ token → แพทเทิร์นเลือก / flare / คีย์เวิร์ด / การอนุมาน 58 ไฟล์ PDP-10 (พจนานุกรม, ความเชื่อ, กฎ) ไม่มี
ALICE แพทเทิร์นเรียง → regex → เทมเพลต AIML → SRAI แบบเรียกซ้ำ 66 ไฟล์ AIML XML ไม่มี
Jabberwacky ความคล้ายคลึง → บริบท → ความเร็วล่าสุด → เลือกแบบถ่วงน้ำหนัก JSON transcript (โตตามการใช้งาน) ต่อเนื่อง
Cleverbot เหมือน Jabberwacky + รอ/รวม consolidated + personas JSON transcript + เมล็ดหลาย persona หน่วงเวลา (ระหว่างเซสชัน)

สิ่งที่พวกเขาใช้ร่วมกันคืออินเทอร์เฟซ CLI และโครงสร้างพื้นฐาน TypeScript (biome สำหรับ lint, tsx สำหรับการทำงาน) ที่เหลือเฉพาะแต่ละสถาปัตยกรรม

ตัวเลือกการออกแบบร่วม

1. ความเที่ยงตรงต่อข้อมูลต้นฉบับ สำหรับ ELIZA, PARRY และ ALICE เราใช้ไฟล์ต้นฉบับ -- สคริปต์ ELIZA ที่พบในคลังเอกสาร Weizenbaum ปี 2021, โค้ด PARRY ดั้งเดิมจาก PDP-10 (58 ไฟล์), AIML Free ALICE v1.6 ไม่มีการแปล ไม่มีการเขียนใหม่ บอททำงานเหมือนต้นฉบับเพราะใช้ข้อมูลเดียวกัน

2. Clean-room สำหรับส่วนที่เป็นกรรมสิทธิ์ Jabberwacky และ Cleverbot ต่างกัน : โค้ดต้นฉบับไม่เคยถูกเผยแพร่ (Existor/Rollo Carpenter เก็บไว้เป็นกรรมสิทธิ์) พอร์ตจึงเป็น clean-room reimplementation -- สร้างจากคำอธิบายสาธารณะของพฤติกรรมเท่านั้น ไม่มีการคัดลอกโค้ดหรือข้อมูลที่เป็นกรรมสิทธิ์

3. การพึ่งพาน้อยที่สุด ข้อกำหนดจริงเพียงอย่างเดียวคือ TypeScript ALICE ใช้ dom-js เพื่อแยก XML ของไฟล์ AIML (66 ไฟล์, 99,524 หมวดหมู่, การแยก XML ด้วยตัวเองคงเสียเวลา) ที่เหลือคือ TypeScript ล้วน



จากแชทบอทเชิงสัญลักษณ์สู่ LLM : การก้าวกระโดดทางแนวคิด

บอททั้งห้าตัวที่เราเพิ่งดูมีลักษณะพื้นฐานร่วมกัน : พวกมันเป็น เชิงสัญลักษณ์ "ความรู้" ของมันถูกเก็บเป็นสัญลักษณ์ชัดเจน -- แพทเทิร์นข้อความ, ตารางกฎ, หมวดหมู่ XML, บรรทัด transcript ไม่มี การแสดงตัวเลขของภาษา ในระบบใดๆ เหล่านี้

ซึ่งหมายถึงพวกมันทั้งหมดมีเพดานกระจกเดียวกัน : พวกมันตอบได้เฉพาะสิ่งที่ได้รับการวางแผนหรือบันทึกไว้อย่างชัดเจน ELIZA หลงทางถ้าคุณออกจากกรอบการบำบัด PARRY พูดเรื่องอากาศไม่ได้ ALICE ไม่เรียนรู้อะไรจากการสนทนา Jabberwacky ตอบได้เฉพาะกับคำพูดที่เคยพูดมาก่อน

LLM (Large Language Models) ก้าวผ่านเพดานนี้ด้วยการเปลี่ยนกระบวนทัศน์อย่างสิ้นเชิง : แทนที่จะจัดการสัญลักษณ์ พวกมันแปลงภาษาเป็น ตัวเลข และเรียนรู้ ความสัมพันธ์ทางสถิติ ระหว่างตัวเลขเหล่านี้ พวกมันไม่เก็บคำตอบที่เขียนไว้ล่วงหน้า -- พวกมันสร้างแต่ละ token ทันทีโดยการคำนวณความน่าจะเป็น มาดูกันสั้นๆ ว่ามันทำงานอย่างไร

1. Tokenization

ขั้นแรกคือการตัดข้อความเป็น tokens -- หน่วยที่เล็กกว่าคำแต่ใหญ่กว่าตัวอักษร :

"Je ne comprends pas"
  → ["Je", " ne", " comprend", "s", " pas"]

แต่ละ token มี ID ตัวเลขในคำศัพท์ (ปกติ 32,000 ถึง 128,000 tokens สำหรับโมเดลล่าสุด) การแยกส่วนนี้ช่วยให้โมเดลจัดการคำที่ไม่เคยเห็นโดยการแบ่งเป็นคำย่อยที่รู้จัก

2. Embeddings

แต่ละ token ID ถูกแปลงเป็น เวกเตอร์ -- อาร์เรย์ของตัวเลขทศนิยม (ปกติ 4096 มิติสำหรับโมเดลขนาดกลาง) เวกเตอร์นี้คือ embedding ที่เข้ารหัสความหมายของ token ในพื้นที่ทางคณิตศาสตร์ที่ token ที่มีความหมายใกล้เคียงมีเวกเตอร์ใกล้กัน :

vector("roi") − vector("homme") + vector("femme") ≈ vector("reine")

คุณสมบัตินี้เกิดจากการฝึก -- ไม่มีใครเขียนมันไว้อย่างชัดเจน มันเป็นผลจากการที่คำถูกใช้ในบริบทที่คล้ายคลึงกัน

3. Attention

กลไก attention (นำเสนอโดยบทความ "Attention is All You Need" ในปี 2017) คือสิ่งที่ทำให้ LLM เป็นไปได้ สำหรับแต่ละ token attention จะคำนวณว่า token อื่นใดในประโยคที่สำคัญต่อการเข้าใจมัน :

"La banque a refusé mon prêt."
     ↑
Token "banque" มอง : "refusé", "prêt" → เข้าใจว่ามันคือสถาบันการเงิน

"Je vais me promener sur la banque."
     ↑
Token "banque" มอง : "promener", "sur" → เข้าใจว่ามันคือริมฝั่ง

Attention ช่วยให้โมเดลจับ บริบท -- แต่ละ token ถูกเข้าใจโดยสัมพันธ์กับสิ่งที่ล้อมรอบ ไม่ใช่แยกเดี่ยว

4. การทำนาย token ถัดไป

การฝึก LLM นั้นง่ายอย่างหลอกลวง : เราแสดงข้อความ ซ่อน token สุดท้าย และให้มันทำนาย แล้วทำซ้ำเป็นพันล้านครั้ง

Input: "Je ne comprends"
ซ่อน: "pas"
การทำนายของโมเดล : "pas" (ความน่าจะเป็น 0.87), "rien" (0.05), "jamais" (0.02)...

เป้าหมายคือการเพิ่มความน่าจะเป็นของ token ที่ถูกต้องในแต่ละตำแหน่ง เรียกว่า next-token prediction ระหว่างการฝึก โมเดลปรับพารามิเตอร์หลายพันล้านตัวเพื่อลดข้อผิดพลาดในการทำนายบนข้อความเทราไบต์

ในตอนอนุมาน (เมื่อเราคุยกับมัน) โมเดลสร้างทีละ token ในลูป :

Token 1: "Je"    (input: "Parle-moi de toi.")
Token 2: "suis"  (input: "Parle-moi de toi. Je")
Token 3: "un"    (input: "Parle-moi de toi. Je suis")
Token 4: "chatbot" (input: "Parle-moi de toi. Je suis un")
...

แต่ละ token ถูกสุ่มตามความน่าจะเป็น (temperature, top-k, top-p ควบคุมระดับ "ความคิดสร้างสรรค์") เท่านั้นเอง พารามิเตอร์หลายพันล้านตัวที่ทำแบบนี้หลายพันครั้ง

สิ่งที่เปลี่ยนแปลงโดยพื้นฐาน

แง่มุม บอทเชิงสัญลักษณ์ (ELIZA, PARRY, ALICE) LLM สมัยใหม่
การแสดงผล คำและกฎชัดเจน เวกเตอร์ตัวเลข (embeddings)
การสร้าง เลือกจากคำตอบที่เขียนไว้ล่วงหน้า ทำนายความน่าจะเป็น token ต่อ token
ความรู้ เก็บในไฟล์กฎ เข้ารหัสในน้ำหนักของเครือข่าย
การเรียนรู้ ด้วยมือ (เขียนกฎ) อัตโนมัติ (ฝึกบนคลังข้อมูล)
ความทนทาน ศูนย์นอกแพทเทิร์นที่วางแผนไว้ ทำให้ทั่วไปกับอินพุตที่ไม่เคยเห็น
การตีความ สมบูรณ์ (อ่านกฎได้) จำกัด (กล่องดำ)

แชทบอทคลาสสิก โปร่งใสแต่เปราะบาง LLM ทนทานแต่ทึบแสง ทั้งสองวิธีมีอยู่จนถึงทุกวันนี้ -- ไม่ใช่ในฐานะคู่แข่ง แต่เป็นเครื่องมือสำหรับความต้องการที่ต่างกัน

Si vous voulez approfondir le fonctionnement interne des LLM, cette vidéo est une excellente ressource :

หากคุณต้องการเจาะลึกการทำงานภายในของ LLM วิดีโอนี้เป็นแหล่งข้อมูลที่ยอดเยี่ยม:

How LLMs Work — YouTube

Luna Protocol : การสังเคราะห์สมัยใหม่

บทความเกี่ยวกับ Luna Protocol (ลิงก์ด้านล่าง) แสดงถึงการสังเคราะห์ที่สมบูรณ์ที่สุดของทุกสิ่งที่เราเพิ่งเห็น : บอท Discord สมัยใหม่ที่รวม LLM ในเครื่องกับระบบพฤติกรรมที่ซับซ้อน สร้างจากบทเรียน 60 ปีของ AI เชิงสนทนา

Luna Protocol : ฉันสร้างบอท Discord อัตโนมัติที่จำลองมนุษย์

บทความนี้ลงรายละเอียดสถาปัตยกรรมสมบูรณ์ของบอท Discord ที่ใช้ LLM :

  • ระบบทริกเกอร์แบบลำดับความสำคัญ (mention > DM > ชื่อ > คีย์เวิร์ด > follow-up > สุ่ม)
  • พฤติกรรมมนุษย์ : สมาธิที่แปรผัน, การพิมพ์ผิด, การลังเล (15%), การลืม (3%), ความเหนื่อยล้าตามหัวข้อ
  • ตารางการนอน : บอทนอน, ช้าลง, หรือไม่สนใจตามเวลา
  • ไปป์ไลน์ TTS : การสังเคราะห์เสียงผ่าน Piper + ffmpeg → ข้อความเสียง Discord
  • การสตรีมแบบเรียลไทม์ : LLM ปล่อย token ทีละตัวบนบัสอีเวนต์ที่มี type

สิ่งที่เชื่อมโยงบทความนี้กับแชทบอทในประวัติศาสตร์คือการแสวงหาเดียวกัน : ทำให้เชื่อว่าคุณกำลังคุยกับคนจริง ELIZA ทำด้วยกระจกข้อความ PARRY ด้วยแบบจำลองอารมณ์ ALICE ด้วย 99k หมวดหมู่ Luna Protocol ทำด้วย LLM ที่ fine-tune + ระบบพฤติกรรมที่จำลองความไม่สมบูรณ์แบบของมนุษย์

Luna Protocol : ทำไมฉันถึง fine-tune โมเดล 1.5B

บทความที่สองสำรวจการ fine-tune และการ few-shot priming การค้นพบหลัก : โมเดลที่เล็กกว่า (1.5B) ที่ฝึกบนข้อมูลน้อยกว่า (50k ตัวอย่าง) มีประสิทธิภาพเหนือกว่าโมเดลที่ใหญ่กว่า (3B) เมื่อเตรียมพร้อมอย่างถูกต้องด้วยตัวอย่าง few-shot

นี่คือบทเรียนที่สะท้อนโดยตรงกับแชทบอทในประวัติศาสตร์ :

  • ELIZA แสดงว่าด้วยกฎไม่กี่ข้อที่ออกแบบมาอย่างดี สามารถจำลองความเข้าใจได้
  • ALICE แสดงว่าด้วย 99k หมวดหมู่ สามารถจำลองความรู้ทั่วไปได้
  • Luna Protocol แสดงว่าด้วย fine-tuning ที่ดีและตัวอย่าง few-shot 5 ตัวอย่าง LLM เล็กสามารถจำลองมนุษย์ได้

เทคนิคต่างกัน แต่หลักการเหมือนกัน : คุณภาพของข้อมูลและความแม่นยำของระบบสำคัญกว่าขนาดดิบ


บทสรุป : สามสิ่งที่ควรจำ

1. AI เชิงสนทนาไม่ได้เริ่มที่ ChatGPT ELIZA อายุ 60 ปี PARRY ผ่านการทดสอบทัวริงในปี 1972 ALICE ชนะ Loebner สามครั้ง Jabberwacky วางรากฐานการเรียนรู้แบบ transcript ที่ Cleverbot ทำให้เป็นอุตสาหกรรมขนาดใหญ่ แต่ละวิธีนำชิ้นส่วนของปริศนามา

2. ข้อมูลมาก ≠ ฉลาดกว่า Transcript ของ Jabberwacky ไม่มีกฎ 99k หมวดหมู่ของ ALICE ไม่ได้เรียนรู้ การ fine-tune ของ Luna Protocol บน 50k ตัวอย่างมีประสิทธิภาพเหนือกว่าโมเดล 3B ภูมิปัญญาทั่วไปบอก "ยิ่งใหญ่ยิ่งดี" -- ประวัติศาสตร์แชทบอทแสดงว่าสถาปัตยกรรมและการออกแบบมีความสำคัญเท่าขนาด

3. ปัญหาเหมือนเดิมมา 60 ปี จะทำให้มนุษย์เชื่อว่ากำลังคุยกับมนุษย์อีกคนได้อย่างไร? ELIZA ตอบด้วยกระจกข้อความ PARRY ด้วยความโกรธจำลอง ALICE ด้วยข้อเท็จจริง Luna Protocol ด้วย LLM ที่นอนและพิมพ์ผิด วิธีการเปลี่ยน ความต้องการยังคงเดิม

Repo เป็นโอเพนซอร์ส -- คุณสามารถโคลน รันแต่ละบอท และเห็นด้วยตัวเองว่า 60 ปีของ AI เชิงสนทนาอยู่ใน repo TypeScript เดียวได้อย่างไร

แหล่งข้อมูล ลิงก์
GitHub Repo fox3000foxy/chatbots
Luna Protocol -- สถาปัตยกรรมบอท อ่านบทความ
Luna Protocol -- few-shot fine-tuning อ่านบทความ
สคริปต์ ELIZA ต้นฉบับ anthay/ELIZA
โค้ดต้นฉบับ PARRY lexcore/PARRY
AIML Free ALICE v1.6 drwallace/aiml-en-us-foundation-alice
RFC 439 ต้นฉบับ PARRY Encounters the DOCTOR
คำอธิบายวิธีการทำงานของ LLM ที่ยอดเยี่ยม https://www.youtube.com/watch?v=YmLp8qe87A0

Related Articles