Pull to refresh

Разговорный бот на php+prolog

Reading time5 min
Views62K

Введение


Многие считают, что язык программирования prolog является сугубо научным и устаревшим языком для обучения студентов. Большинство в университете «напрягали» именно этим языком на предметах, хоть немного приближенных к теме искусственного интеллекта, эта тема не обошла и меня.

Стандартная задача, которая мне попалась – оказалась настолько скучной и распространенной для этого языка, что был соблазн «скачать готовую бесплатно без регистрации», но я решил вынести максимум из этого курса и повернуть тему пролога, для написания чего-нибудь практически значимого и интересного. Прочитав достаточно много разной документации, в том числе и посты на хабре о prolog, я пришел к выводу, что пролог является отличным инструментом для обработки предложений формального языка. Я решил, что было бы круто – написать бота, который бы умел отвечать на фразы (может быть даже складно) вконтакте.

Реализация


Первым делом я написал основной функционал бота, т.е. интерфейс, позволяющий мне, как админу — посылать команды для выполнения боту.

Список текущих команд:
$commands = array('выйди', 'слушать', 'up', 'uptime', 'аптайм', 'last');


Собственно, каждая команда говорит сама за себя. Остановиться следует на двух командах:
  • слушать %текст% сообщает боту, что нужно поставить в статус песню, где исполнитель – название = %текст%. К делу вообще не относится, просто стало любопытно.
  • И last – отображает последнее сообщение, отправленное боту. И пользователя, который его отправил.

Выглядит это так:



Все это делалось в рамках обработки входящих сообщений. Далее, обработка сообщений не от админа, т.е. предложения, которые требуют ответа.

Вот тут в дело входит prolog. А точнее, swi-prolog, реализация которого имеется как для windows ( там разрабатывалась ), так и для *nix (там работает сейчас). Так же плюс swi-prolog в том, что он – интерпретируемый. Следовательно, нужно просто скормить файл со скриптом интерпретатору swi и поймать результат. При этом запомнить входную фразу, и при имеющейся результирующей фразе – записать эту связку в файл. Лист масок лучше представить на примере, для входной строки:
 Привет, как твои дела? 


список масок будет выглядеть как:

['*привет*','привет*','*привет','*как*','как*','*как','*твои*','твои*','*твои','*дела*','дела*','*дела’]


При этом мы должны запомнить, что это именно вопрос. Для этого список масок обрамляем предикатом request, и сохраняем в файл. Пример:

request(‘?’,['*привет*','привет*','*привет','*как*','как*','*как','*твои*','твои*','*твои','*дела*','дела*','*дела’]).


Далее, запускаем скрипт, описание алгоритма которого сводится к нескольким строчкам:
  • Читаем предикат из входного файла (тот самый request)
    
    consult('request.txt')
    

    А для того, чтобы текст в файле воспринимался как набор предикатов, нужно добавить описание динамических предикатов где-нибудь в заголовке:
    
    :-dynamic(request/2).
    

  • Читаем базу знаний и получаем список вопросов-ответов и список вопросов без ответов.
    
    :-dynamic(question/2).
    :-dynamic(answer/2).
    :-dynamic(notanswered/1).
    :-consult('questionanswerlink.txt').
    :-consult('withoutanswer.txt').
    

  • Ищем вопрос\ответ
    
    goal2 :-
        toFile,
        consult('request.txt'),
        request(Type, KeyWords),%получаем вопрос и тип вопроса
        twoFindAllByType(Type, KeyWords, ActualByType), % моя функция, которая получает список строк обратных типу (если тип - вопрос, вернется утвердительное, и наоборот) и в которых имеются KeyWords (наши ключевые маски).
        str(ActualByType, ActualPhrasesString),%строка из списка
        toFile(ActualPhrasesString),fail.%запись в файл
    

  • если ответ не найден — берем случайный вопрос из списка вопросов (на которых нет ответа)
    noAnswer :- 
        toFile,
        consult('request.txt'), %читаем файл
        request(Type, _), %для каждого предиката берем тип
        twoAsk(Type, Res),%получаем не отвеченные вопросы
        str(Res, ActualPhrasesString),write(ActualPhrasesString), %перерабатываем список в строку и сохраняем в файл
        toFile(S),fail.
    

  • Записываем результат другой файл (обычно, результат — несколько фраз)


Дополнительные функции:

Список в строку

str([], _). %для пустого списка - ничего
str([H|[]], S):- %для последнего элемента
    string_concat('', H, S),!.	%конкатенация последнего элемента (H) с остальным результатом S и остановка !
str([H|_], String) :- % Для верхнего элемента в списке
    string_concat(H, '', S),%конкатенация его с S
    String = S. %отдаем результат в String


Сохранение в файл данных

toFile(Data) :- 
    open('output.txt', append, OS),
    toFile(OS, Data).
toFile:- 
    open('output.txt', write, OS), 
    close(OS). 
toFile(OS, Data) :- 
    write(OS,Data), nl(OS), false.
toFile(OS, _) :- close(OS).	


Ну и получение не отвеченных вопросов:

twoAsk('.', Res):- %работает только для утвердительных входных вопросов
    notanswered(R),Res = [R|Res]. %Для каждого предиката notanswered("какой-то вопрос") добавляем его в голову списка Res.


Собственно это все, чем занимается скрипт на prolog. Остальные вещи довольно просты: берем случайную строку из результирующего файла, и отдаем её пользователю, заодно записываем вопрос-ответ в файл БЗ. Так же, если бот задал вопрос, а ответа не прозвучало – этот вопрос записывается в базу вопросов, на которых так и не было дано ответа.

Результат


Наверно, это самая ожидаемая часть статьи. Даже учитывая то, что этот вариант бота был написан за 3 дня, с учетом алгоритма вывода фраз, результат может выдавать фразы, зависящие от контекста разговора (иногда). Но, особенно точно он отвечает, при наличии уникальных\малоиспользуемых фраз (оно и понятно).

Алгоритм бота – очень кривой, особенно та часть, которая должна быть «умной». К слову сказать, чтобы немного обучить бота, пришлось идти на некоторые неудобства. Например, пару раз писал от имени бота в группах, где девушки продают себя или фотографируют и продают фотографии. Это подействовало, и, как и следовало ожидать – распределение по полу: 98% мужчины, 2% остальные. Но это дало некоторый негативный эффект. Бот начал писать как от имени мужчин, так и от имени женщин. Это путало пользователей, и в итоге в базе знаний содержится достаточно много вопросов, содержащих слова «пол» «твой» «какой» и тому подобные. Но даже учитывая эти неудобства, люди продолжали общаться с ботом, даже не смотря — на высокую скорость ответа (пол секунды — секунда, особенно для знакомых фраз), повторы сообщений, ответы невпопад. Некоторым людям просто необходимо общение, даже если оно не натуральное.

А совсем недавно нас даже начали узнавать:


Иногда бот выдавал философские фразы, над которыми можно думать очень долго смотря на небо:

или нет


Иногда, правда, проскакивает абсолютный бред.


Но даже так за этим весело следить:

вовремя исправился:


самой интересно стало:


Кроме аккаунта бота, я создал заодно и публичную страницу, на которую отсылал скриншоты наиболее занимательных диалогов, и, большинство диалогов можно прочитать написав в гугле “бот баба vk”, строго +18. Их уже набралось около 150ти. Кстати, если эту статью читают гуру prolog, может они преподадут мне пару уроков, или можно, например, вместе поработать для создания более умного алгоритма работы, конечно, если это кому-то интересно.

К величайшему сожалению – текущий алгоритм не позволяет боту обслуживать даже 10 клиентов (очень с трудом), бывает, что процесс обработки прерывается на пару минут, перебирая большую базу знаний с большим количеством входных масок. Поэтому ссылку на самого бота могу дать только паре человек и исключительно в ЛС.
Спасибо, что дочитали до этого момента.
Tags:
Hubs:
Total votes 39: ↑35 and ↓4+31
Comments21

Articles