Недавно, в попытках разобраться с nlp, мне пришла идея написать простого telegram бота, который будет разговаривать, как дерзкий гопник. То есть:
- давать ответ по слову-триггеру, как "хочу", "короче", "нет" и т.д.;
- отвечать дерзким вопросом на вопрос;
- отвечать нецензурной рифмой;
- если ничего не подходит и бот в замешательстве, отвечать злой фразой.
Для имплементации был выбран JavaScript с ES6 и Flow. Возможно, Python подошёл бы лучше, так как под него существует больше стабильных и проверенных библиотек для nlp. Но для JS есть Az.js, которого вполне хватило.
Для работы с Telegram API был использован node-telegram-bot-api.
TLDR: бот, исходный код
Осторожно, под катом присутствует нецензурная речь и детали реализации!
Часть с реализацией работы с Telegram API не сильно интересная, и про это уже написано множество статей, и её я опущу.
Начну сразу с того, как бот пытается найти подходящий ответ. Первый метод поиска ответа – слово-триггер:
user: Хочу новую машину!
bot: Хотеть невредно!
Для начала мы имеем список пар [regexp, ответ]
:
const TRIGGERS = [
[/^к[оа]роч[ье]?$/i, 'У кого короче, тот дома сидит!'],
[/^нет$/i, 'Пидора ответ!'],
[/^хо(чу|тим|тят|тел|тела)$/i, 'Хотеть невредно!'],
];
Потом мы должны разбить сообщение от пользователя на слова:
const getWords = (text: string): string[] =>
Az.Tokens(text)
.tokens
.filter(({type}) => type === Az.Tokens.WORD)
.map(({st, length}) => text.substr(st, length).toLowerCase());
Пройти по всем триггерам и вернуть возможные ответы:
const getByWordTrigger = function*(text: string): Iterable<string> {
for (const word of getWords(text)) {
for (const [regexp, answer] of constants.TRIGGERS) {
if (word.match(regexp)) {
yield answer;
}
}
}
};
Вышло очень просто. Теперь пришло время второго метода поиска ответа – отвечать дерзким вопросом на вопрос:
user: Когда мы уже пойдём домой?
bot: А тебя ебёт?
Для того чтобы определить, является ли сообщение вопросом, мы должны проверить его на наличие вопросительного знака в конце и на наличие вопросительных слов, как "когда", "где" и т.д.:
const getAnswerToQuestion = (text: string): string[] => {
if (text.trim().endsWith('?')) {
return [constants.ANSWER_TO_QUESTION];
}
const questionWords = getWords(text)
.map((word) => Az.Morph(word))
.filter((morphs) => morphs.length && morphs[0].tag.Ques);
if (questionWords.length) {
return [constants.ANSWER_TO_QUESTION];
} else {
return [];
}
};
Так, в случае вопроса, бот вернёт захардкоженый constants.ANSWER_TO_QUESTION
.
Третий метод поиска ответа – ответ нецензурной рифмой. Этот метод наиболее сложный:
user: хочу в Австрию!
bot: хуявстрию
user: у него есть трактор
bot: хуяктор
Вкратце: мы просто заменяем первый слог существительного или прилагательного на "ху" и трансформированную гласную из слога, как "о" > "ё", "а" > "я" и т.д.
Для начала мы должны уметь получать первый слог слова. Это несложно:
const getFirstSyllable = (word: string): string => {
const result = [];
let readVowel = false;
for (const letter of word) {
const isVowel = constants.VOWELS.indexOf(letter) !== -1;
if (readVowel && !isVowel) {
break;
}
if (isVowel) {
readVowel = true;
}
result.push(letter);
}
return result.join('');
};
Потом нужно заменять первый слог на "ху" + гласную, если это возможно:
const getRhyme = (word: string): ?string => {
const morphs = Az.Morph(word);
if (!morphs.length) {
return;
}
const {tag} = morphs[0];
if (!tag.NOUN && !tag.ADJF) {
return;
}
const syllable = getFirstSyllable(word);
if (!syllable || syllable === word) {
return;
}
const prefix = constants.VOWEL_TO_RHYME[last(syllable)];
const postfix = word.substr(syllable.length);
return `${prefix}${postfix}`;
};
И, наконец, возвращать все возможные рифмы для слов из сообщения:
const getRhymes = (text: string): string[] =>
getWords(text)
.map(getRhyme)
.filter(Boolean)
.reverse();
Последний метод поиска ответа – отвечать в замешательстве грубой фразой:
user: wtf
bot: Чё?
Этот метод более чем простой, поэтому будет реализован в агрегирующей все методы функции:
export default (text: string): string[] => {
const answers = uniq([
...getByWordTrigger(text),
...getAnswerToQuestion(text),
...getRhymes(text),
]);
if (answers.length) {
return answers;
} else {
return constants.NO_ANSWERS;
}
};
И это всё. Бот, исходный код.
Комментарии (25)
Ravebinovich
28.04.2017 16:32+3Тренер ведения диалога с аборигенами неблагополучных районов. Полезный бот.
P.S. Можно добавить функционал, чтоб он ещё докапывался в духе «Чё такой дерзкий?», «Ты с какого района?», «Поясни за шмот.», «А чё волосатый?», «Мелочь есть?» ну итд.
Assada
28.04.2017 16:41+2Если бот отвечает на каждое сообщение — в группу его не добавить. Было бы хорошо чтобы он внезапно и лишь иногда отвечал участникам группы. Так делает бот «Виталя» (@P06at_E60boT). Он отвечает только если к нему обращаются по имени «Веталь, Виталя, etc..» ну или иногда встревает в разговор одним сообщением.
Смотреть ответы
Alexey_mosc
28.04.2017 19:48+1«Чики-брыки», «ты чё чёкаешь», «стоять-бояться», «Лысого знаешь?» Развивайте идею и потом код в студию ) Буду на автоответчик ставить )))
Greesha
28.04.2017 20:49+2Идея такого искусственного интеллекта постоянно приходит в голову разным пытливым умам. Одна из известных реализаций — Мыши Дебиляриуса.
А мой одногруппник ещё в 1988 году написал интерактивный гороскоп на языке REXX (под VM/370), который вежливо задавал вопросы, а потом выдавал на основании полученных ответов такую характеристику, что этого моего одногруппника неоднократно пытались побить. Но я уверен, что подобные вещи делали и намного раньше.
Alex_ME
28.04.2017 21:18+1Забавано!
Мой друг descine делал бота на основе библиотеки chatterbot (хоть и с акцентом на голосовое взаимодействие). Вдохновившись, я прикрутил к этому боту VK API и отправил обучаться в чаты. Получилось весьма… нецензурно.
А потом, спустя пару дней, я часа 3 получал капчу на каждую отправку сообщения.
cats_shadow
29.04.2017 12:39+2Чойта он какой-то не дерзкий. :) Всё время в однословную рифму сваливается. :)
Alexey_mosc
29.04.2017 22:53Пообщался с ним. Он не то, что поддатый, а в дупель пьяный гопник. По ощущениям ))) Но забавно…
kesn
Ждём, когда вы начнёте изучать нейронные сети и вырастите гопника уже на них :)
nvbn
Если бы был большой датасет пар `сообщение: ответы`, то было бы легко =)
Жалко что в Mechanical Turk нет русскоязычных.
mizhgun
> Жалко что в Mechanical Turk нет русскоязычных.
Есть Толока вообще-то.
Antelle
Можно попробовать
двачвконтакт, какие-нибудь затролленные паблики, теоретически должно быть то, что надо.alex4321
reactor.cc/tag/anon :-)
Но это из другой области.
alex4321
Хотя, чего-то анон теперь ещё большая хрень, чем при последнем моём попадании в него.