Мини-проект: свой чат-бот по правилам

Хватит читать про ИИ — пора собрать своего бота своими руками и на нём понять, чем твой код отличается от ChatGPT.
Чат-бот по правилам — это программа, которая отвечает не «думая», а сверяясь со списком заранее прописанных правил «если увидел такое слово — ответь так».

Представь: ты пишешь маленькую программу, открываешь её, печатаешь «привет» — и она отвечает «Привет! Как дела?». Печатаешь «расскажи анекдот» — и она выдаёт шутку. Выглядит почти как настоящий ассистент в телефоне. Только внутри нет ни нейросети, ни обучения, ни эмбеддингов — всего пара десятков строк кода, которые ты написал и полностью понимаешь.

К концу урока у тебя будет работающий бот. А ещё — главное — ты на собственной шкуре прочувствуешь, где такой подход упирается в стену и почему настоящий ИИ устроен совсем иначе. Это и есть лучший способ понять ChatGPT: сначала сделать его наивную версию сам.

Звучит как магия — «программа разговаривает», — но никакой магии тут нет. Когда ты увидишь весь код целиком и поймёшь каждую строчку, у тебя пропадёт священный трепет перед «искусственным интеллектом» и появится кое-что куда полезнее: умение спросить про любую умную систему «а что именно у неё внутри?». Это тот же навык, с которым ты разбираешь, как работает рекомендательная лента в соцсети или автодополнение в телефоне, — не веришь на слово, а заглядываешь под капот.

Зачем вообще собирать бота вручную

Ты уже прошёл длинный путь: от признаков и обучения до языковых моделей и трансформеров. Но всё это время ты был зрителем — читал, как оно работает где-то там, внутри огромных систем. Сейчас ты станешь автором.

Есть огромная разница между «я прочитал, как работает велосипед» и «я проехал на велосипеде». Можно идеально знать теорию равновесия и не уметь крутить педали. С ИИ так же: пока ты сам не написал хотя бы примитивного бота, устройство ChatGPT остаётся красивой абстракцией. А стоит собрать своего — и многие фразы из прошлых уроков вдруг щёлкнут на место.

Бот по правилам — это самый первый, самый честный способ заставить компьютер «разговаривать». Именно с таких ботов начиналась история диалоговых программ: ещё в 1960-х существовала ELIZA — программа, которая изображала психотерапевта, просто переставляя слова из твоей же фразы. Люди всерьёз верили, что их понимают. А под капотом — те самые правила «если в тексте есть слово X, ответь Y».

Собрав такого бота, ты получишь точку отсчёта. Когда ты потом скажешь «ChatGPT понимает контекст» или «модель обобщает», у тебя в голове будет конкретная картинка: вот что бот не умел, а вот что добавила нейросеть. Без этой точки отсчёта слова «обучение» и «понимание» остаются пустыми.

Главная идея: правила против обучения

Вспомни наш сквозной пример — отличить кошку от собаки. В самом начале курса мы делали это по правилам: если уши торчком и морда круглая — кошка, если уши висят и морда вытянутая — собака. Мы, люди, заранее придумали эти правила и вписали их в программу.

Потом мы перешли к обучению: вместо того чтобы выдумывать правила, мы показали модели тысячи фоток с метками «кошка»/«собака», и она сама нашла закономерности. Никто не писал ей «смотри на уши» — она докопалась до этого по примерам.

Чат-бот по правилам — это ровно тот же первый подход, но для текста. Ты заранее прописываешь: «если в сообщении есть слово привет — отвечай так-то». Сравни два мира в таблице:

Бот по правилам (твой проект)Языковая модель (ChatGPT)
Откуда берутся ответыИх вписал человек вручнуюМодель научилась на миллиардах текстов
Что делает с новой фразойИщет знакомое слово; не нашёл — сдаётсяПредсказывает правдоподобное продолжение
Понимает ли смыслНет, сравнивает строки буквальноРаботает с эмбеддингами — близкими по смыслу
Легко ли понять, почему ответил такДа, видно конкретное правилоТрудно — ответ «размазан» по миллионам весов

Запомни эту разницу: в правилах знание пишет человек, в обучении модель добывает его сама из данных. Это водораздел между «старым» программированием и машинным обучением.

Разбор: собираем бота шаг за шагом

Шаг 1. Самый наивный бот

Начнём с минимума. Бот берёт сообщение, приводит его к нижнему регистру (чтобы «Привет» и «привет» считались одинаковыми) и проверяет, есть ли в нём ключевое слово.

function ответить(сообщение) {
  const текст = сообщение.toLowerCase();

  if (текст.includes("привет")) {
    return "Привет! Как дела?";
  }
  if (текст.includes("пока")) {
    return "До встречи!";
  }
  return "Извини, я тебя не понял.";
}

console.log(ответить("Привет, бот!"));
console.log(ответить("ну всё, ПОКА"));
console.log(ответить("какая погода?"));

Вывод:

Привет! Как дела?
До встречи!
Извини, я тебя не понял.

Разберём по шагам. toLowerCase() делает буквы строчными — без этого «ПОКА» не совпало бы с «пока». includes("привет") проверяет, встречается ли подстрока в тексте. Правила проверяются по порядку сверху вниз; первое сработавшее даёт ответ. Если не сработало ни одно — бот честно говорит, что не понял. Это и есть тот самый «нет кнопки понимания» в чистом виде.

Обрати внимание на одну важную вещь: бот никогда не ошибётся непредсказуемо. Его поведение полностью определяется этими тремя проверками. Дай ему одну и ту же фразу хоть тысячу раз — ответ будет один и тот же. В этом и сила, и слабость правил: ты на сто процентов знаешь, что бот сделает, но он на сто процентов глуп ко всему, чего ты не предусмотрел. ChatGPT — наоборот: непредсказуем, зато гибок. Эта развилка между «предсказуемо и тупо» и «гибко и непредсказуемо» проходит через весь мир ИИ.

Шаг 2. Список правил вместо лесенки if

Когда правил два — if подходит. Когда их двадцать — это превратится в нечитаемую лесенку. Сделаем аккуратнее: вынесем правила в список, где каждое правило — это пара «ключевые слова → ответ». Так добавить новое правило можно одной строкой, не трогая логику.

const правила = [
  { ключи: ["привет", "здаров", "хай"], ответ: "Привет! Чем помочь?" },
  { ключи: ["пока", "до встречи"],      ответ: "Пока! Заходи ещё." },
  { ключи: ["анекдот", "шутк"],         ответ: "Почему программист пьёт чай? Потому что Java закончилась." },
  { ключи: ["как дела", "как ты"],       ответ: "Я всего лишь набор правил, но у меня всё стабильно!" }
];

function ответить(сообщение) {
  const текст = сообщение.toLowerCase();
  for (const правило of правила) {
    // сработало, если хоть один ключ встретился в тексте
    if (правило.ключи.some(ключ => текст.includes(ключ))) {
      return правило.ответ;
    }
  }
  return "Хм, я пока не знаю, что на это ответить.";
}

console.log(ответить("хай!"));
console.log(ответить("расскажи шутку"));
console.log(ответить("как ты сегодня?"));
console.log(ответить("посоветуй фильм"));

Вывод:

Привет! Чем помочь?
Почему программист пьёт чай? Потому что Java закончилась.
Я всего лишь набор правил, но у меня всё стабильно!
Хм, я пока не знаю, что на это ответить.

Что нового. Каждое правило держит несколько синонимов в массиве ключи: «привет», «здаров», «хай» ведут к одному ответу. Метод some(...) возвращает true, если хотя бы один ключ нашёлся в тексте. Заметь приём с обрезанными словами: ключ "шутк" ловит и «шутка», и «шутку», и «пошути» — потому что includes ищет подстроку. Это грубый, но рабочий способ обойти разные окончания, которых в русском море.

Шаг 3. Чуть-чуть «памяти» и случайности

Настоящие боты звучат живее, когда не повторяют одну и ту же фразу. Добавим выбор случайного ответа из нескольких вариантов и крошечную «память» — счётчик сообщений.

const приветствия = ["Привет!", "Здорово!", "О, снова ты!"];
let счётчикСообщений = 0;

function выбратьСлучайно(массив, номер) {
  // детерминированно для примера: крутим по кругу по номеру сообщения
  return массив[номер % массив.length];
}

function ответить(сообщение) {
  счётчикСообщений++;
  const текст = сообщение.toLowerCase();

  if (текст.includes("привет")) {
    return выбратьСлучайно(приветствия, счётчикСообщений);
  }
  if (текст.includes("сколько") && текст.includes("сообщен")) {
    return "Ты написал мне " + счётчикСообщений + " сообщений.";
  }
  return "Я не понял, но я запоминаю — это уже сообщение №" + счётчикСообщений + ".";
}

console.log(ответить("привет"));
console.log(ответить("привет ещё раз"));
console.log(ответить("сколько сообщений я написал?"));

Вывод:

Здорово!
О, снова ты!
Ты написал мне 3 сообщений.

Тут две новые идеи. Во-первых, переменная счётчикСообщений живёт между вызовами — это примитивная «память» о диалоге. Во-вторых, ответ на «привет» теперь меняется. В настоящем коде ты взял бы Math.random(), но для воспроизводимого примера мы крутим варианты по номеру сообщения. Кажется, что бот «помнит» и «оживает», — но это всё ещё жёсткие правила, просто чуть хитрее.

И вот тут стоит притормозить и быть честным с самим собой. Эта «память» — всего лишь одно число. Бот не помнит, о чём вы говорили: он не знает, что ты спрашивал про погоду, а потом про музыку. Он помнит ровно столько, сколько ты явно велел запомнить, — и ни байтом больше. Сравни с ChatGPT, который держит в «голове» весь ваш разговор и может сослаться на твою фразу из начала диалога. Эту способность дают не правила, а механизм внимания внутри трансформера, который мы разбирали раньше: модель смотрит сразу на все слова контекста и решает, какие из них важны для следующего ответа. Твой счётчик — это карикатура на такую память, и полезно понимать, насколько она бедна.

Частые ошибки и подводные камни

Когда школьники собирают такого бота впервые, они спотыкаются на одних и тех же местах. Лови список, чтобы не наступить на грабли.

ОшибкаЧто происходит и почему
Забыть toLowerCase()«Привет» с большой буквы не совпадёт с ключом «привет», и бот «не услышит» приветствие.
Сравнивать через === вместо includesтекст === "привет" сработает только на голое слово «привет», но не на «привет, бот!». Нужно искать подстроку, а не точное равенство.
Порядок правил перепутанСлишком общее правило стоит первым и перехватывает фразы, которые должны были попасть в более точное правило ниже.
Один ключ ловит лишнееКлюч "да" сработает внутри слов «погоДА», «приДАёт». Короткие ключи дают ложные срабатывания.
Ждать, что бот «поймёт» синонимЕсли ты прописал «привет», но не «прив», бот на «прив» ответит «не понял». Он не догадывается — он сверяет буквы.

Последняя строка — самая важная для всего курса. Бот не обобщает. ChatGPT на «прив», «здравствуй» и «йоу» ответит как на приветствие, даже если таких слов не было в явных инструкциях, потому что в его эмбеддингах эти слова лежат рядом по смыслу. Об этом мы говорили в уроке «Языковые модели: предсказание слова». Твой бот так не умеет: для него «привет» и «здравствуй» — две абсолютно разные строки букв.

Где правила упираются в стену

Поиграй с ботом подольше — и стена станет очевидной. Чтобы он понимал больше, тебе придётся вручную дописывать правило за правилом. Хочешь, чтобы реагировал на 100 тем — пиши 100 правил. На опечатки — добавляй варианты. На перефразировки — ещё варианты. Это бесконечная ручная работа, и всё равно найдётся фраза, которая собьёт бота с толку.

Вот тут и понятно, зачем придумали обучение. Вместо того чтобы человек выписывал миллионы правил, модель сама вытягивает закономерности из текстов. Вспомни сквозной пример «Кошка пьёт ...»: твой бот по правилам ответил бы только если ты заранее вписал правило «после ‘Кошка пьёт’ говори ‘молоко’». А языковая модель предсказывает «молоко» сама, потому что встречала похожие фразы миллионы раз и обобщила. Никто не вписывал это правило руками — оно «выросло» из данных. В этом вся разница между твоим проектом и ChatGPT.

Подумай о масштабе живого языка. Только приветствий в русском — десятки: «привет», «здравствуй», «здаров», «хай», «йоу», «доброго времени суток», «приветик», плюс опечатки и сленг, который появляется каждый месяц. Чтобы покрыть их все правилами, тебе пришлось бы вести бесконечный список и каждую неделю его дополнять. А ведь приветствие — это самая простая категория! Представь то же самое для вопросов о математике, истории, играх, чувствах. Становится ясно: ручные правила — это лестница, по которой невозможно дойти до настоящего разговора. Где-то на сотом правиле ты сдашься, а человек всё равно напишет сто первую фразу, которую ты не предусмотрел.

Именно эта боль и подтолкнула людей к идее обучения. Раз мы, программисты, физически не способны перечислить все случаи — пусть машина выведет правила сама, посмотрев на горы примеров. Так наивный бот, которого ты только что собрал, превращается в логичную ступеньку к огромным языковым моделям. Ты прошёл этот путь не в теории, а руками.

Мини-практика: доделай бота сам

Возьми код из Шага 2 за основу и прокачай его. Делай по порядку — каждый пункт чуть сложнее предыдущего.

  1. Добавь 3 своих правила на близкие тебе темы: про любимую игру, музыку, мемы. У каждого — минимум два синонима в массиве ключи.
  2. Сделай «умолчание» полезным. Вместо «не понял» пусть бот предлагает список тем, на которые умеет отвечать. Так пользователь поймёт, чего от него ждать.
  3. Поймай ложное срабатывание. Добавь короткий ключ вроде "да" и найди фразу, на которой он срабатывает не к месту. Потом исправь: например, проверяй слово целиком, а не как подстроку.
  4. Сравни с ChatGPT. Возьми любую фразу, которую твой бот не понимает (перефразированное приветствие, опечатку), и задай её настоящему ИИ-чату. Запиши: где он справился, а твой бот — нет. Это и есть наглядная граница между правилами и обучением.

Когда будешь формулировать запрос настоящему ИИ в пункте 4, вспомни урок «Что такое промпт и как его писать» — чёткая формулировка повышает шанс на полезный ответ.

Итоги

  • Чат-бот по правилам отвечает, сверяя текст с заранее прописанными человеком правилами «если слово X — ответь Y».
  • Ты собрал такого бота сам: от наивной лесенки if до списка правил с синонимами и простой «памятью».
  • Главная граница: в правилах знание пишет человек, в обучении модель добывает его из данных сама — и поэтому умеет обобщать, а бот по правилам нет.
  • Правила быстро упираются в стену: на каждую новую фразу нужно новое правило вручную. Именно этот тупик и сделал обучение нужным.
  • Сквозные примеры «кошка/собака» и «Кошка пьёт ...» наглядно показывают: что раньше человек прописывал правилами, нейросеть теперь добывает из примеров.

Дальше в разделе «Сделай сам и будущее» мы посмотрим, куда движется ИИ и какие профессии и навыки он меняет. Но теперь у тебя есть то, чего нет у большинства, — собственный, пусть крошечный, но честно понятый бот. С него и начинается настоящее понимание больших систем.

Проверьте себя
1. Чем чат-бот по правилам принципиально отличается от ChatGPT?
AБот работает быстрее, потому что написан на JavaScript
BВ боте ответы заранее вписал человек, а языковая модель научилась им на данных
CБот понимает смысл слов, а ChatGPT просто ищет подстроки
DМежду ними нет никакой разницы
2. Зачем в коде бота вызывают text.toLowerCase() перед проверкой ключей?
AЧтобы ответ выглядел красивее
BЧтобы «Привет» и «привет» считались одинаковыми и правило срабатывало независимо от регистра
CЧтобы бот отвечал быстрее
DЭто нужно только для английского языка
3. Почему для поиска ключа используют includes("привет"), а не сравнение === "привет"?
Aincludes работает только с числами
B=== сработает лишь на голое слово, а includes найдёт ключ внутри фразы вроде «привет, бот!»
CЭто одно и то же, разницы нет
D=== быстрее, поэтому его всегда предпочитают
4. Почему бот по правилам не отвечает на «здравствуй», если в правилах прописан только «привет»?
AОн сравнивает буквы и не догадывается, что слова близки по смыслу
BОн специально игнорирует вежливые слова
CУ него закончилась память
D«здравствуй» — слишком длинное слово для includes
5. В чём главная проблема подхода «добавить ещё одно правило» при росте бота?
AПравила начинают работать медленнее обучения
BНа каждую новую тему, опечатку и перефразировку нужно вручную дописывать правила — и это бесконечно
CJavaScript не умеет хранить много правил
DПравила со временем сами стираются из памяти
6. Как связать наш бот со сквозным примером «Кошка пьёт ...»?
AБот предскажет «молоко» сам, как языковая модель
BБот ответит «молоко», только если человек заранее вписал такое правило, а модель предсказывает это сама из данных
CБот вообще не способен работать с текстом про кошек
DЭто разные вещи и связать их нельзя