II
Conversando con Eliza
Para interactuar con el programa Eliza, se debe ejecutar el intérprete SWI-Prolog,
abrir el archvo eliza_complete.pl y compilarlo. En la consola de SWI-Prolog se
deberá ingresar “eliza.” Eliza mostrará un mensaje de bienvenida y esperará por
más consultas del usuario. Éstas deben estar en minúsculas y tener un punto al
final.
Una conversación de ejemplo es la siguiente:
Eliza da las respuestas en forma de listas de palabras,
separadas por comas y entre corchetes. Se mantuvo este formato porque en una
aplicación Eliza real, es muy probable que las respuesta halladas requieran un
procesamiento posterior.
La
aplicación ha sido probada en SWI-Prolog versión 7.2.1. No se ha probado en
versiones anteriores. No funcionará en versiones de SWI-Prolog que no tengan
implementadas las funciones length, nth0, atom y select.
Esta implementación de Eliza no tiene memoria, es decir, no recuerda las
consultas ingresadas y sus respuestas, por lo que la coherencia de la
conversación depende del usuario. Por ejemplo, para Eliza es válido ingresar
primero la consulta “because you are creepy.” Y luego “i hate you.”.
Para ampliar las respuestas de Eliza a más estímulos basta
crear más templates con los formatos ya mencionados.
En este programa, la aparente inteligencia de Eliza está en función a la
amplitud de su base de conocimientos. Como la coincidencia de las consultas del
usuario y los estímulos se realiza término a término, la base de conocimiento de
Eliza debe ser bastante grande para producir una conversación coherente, y poder
respetar género, número, tiempos verbales, etc. Un programa más completo
consideraría toda una lista de palabras clave en lugar de sólo una.
El código completo es (Escrito en Prolog y probado en Swi Prolog v7.2.):
% by: Y3l1nna_Broken_Window:
% la función match permite crear un output para varios inputs.
eliza:- writeln('Hi, I am Eliza your chatbot,
please enter your query,
use only lowercases and a dot at the end:'),
readln(Input),
eliza(Input),!.
eliza(Input):- Input == ['bye'],
writeln('Goodbye. I hope I have helped you.'), !.
eliza(Input):- Input == ['bye', '.'],
writeln('Goodbye. I hope I have helped you.'), !.
eliza(Input) :-
template(Stim, Resp, IndStim),
match(Stim, Input),
% si he llegado aquí es que he
% hallado el template correcto:
replace0(IndStim, Input, 0, Resp, R),
writeln(R),
readln(Input1),
eliza(Input1), !.
% algunos saludos
% debido al backtracking, los templates más específicos deben ir antes
% de los más genéricos. Eso se nota acá:
template([hi, my, name, is, s(_), '.'], ['Hi', 0, 'How', are, you, '?'], [4]).
template([hello, my, name, is, s(_), '.'], ['Hello', 'How', are, you, 0, '?'], [4]).
template([hi, ',', my, name, is, s(_), '.'], ['Hi', 0, 'How', are, you, '?'], [5]).
template([hello, ',', my, name, is, s(_), '.'], ['Hello', 'How', are, you, 0, '?'], [5]).
template([hi, _], ['Hi', 'How', are, you, '?'], []).
template([hello, _], ['Hello', 'How', are, you, '?'], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
template([i, s(_), i, am, s(_),'.'], [why, do, you, 0, you, are, 1, '?'], [1, 4]).
template([i, s(_), you, '.'], [why, do, you, 0, me ,'?'], [1]).
template([i, am, s(_),'.'], [why, are, you, 0, '?'], [2]).
% pregunta algo que le gusta a eliza
template([do, you, like, s(_), _], [flagLike], [3]).
% pregunta algo que hace eliza
template([do, you, s(_), _], [flagDo], [2]).
% pregunta algo que es eliza
template([you, are, s(_)], [flagIs], [2]).
template([are, you, s(_), '?'], [flagIs], [2]).
template([how, are, you, '?'], [i, am, fine, ',', thanks, for, asking, '.'], []).
template([i, think, _], [well, that, is, just, your, opinion], []).
template([because, _], [that, is, not, a, good, reason, '.'], []).
template([i, have, s(_), with, s(_), '.'], ['You', have, to, deal, with, your, 0, and, your, 1, in, a, mature, way, '.'], [2, 4]).
template([i, s(_), _], [i, can, recommend, you, a, book, about, that, issue], []).
template([please, s(_), _], ['No', i, can, not, help, ',', i, am, just, a, machine], []). % didn't passed the Turing Test
template([tell, me, a, s(_), _], ['No', i, can, not, ',', i, am, bad, at, that], []). % classic AI problem: una máquina aún no puede contar una historia coherente.
template(_, ['Please', explain, a, little, more, '.'], []). % cuando se ingresa algo que Eliza no tiene en sus templates, esto debe ir al final o eliza responderá a todo con esto.
% Lo que le gusta a eliza : flagLike
elizaLikes(X, R):- likes(X), R = ['Yeah', i, like, X].
elizaLikes(X, R):- \+likes(X), R = ['Nope', i, do, not, like, X].
likes(apples).
likes(ponies).
likes(zombies).
%%%%%%%%%%%%%%%%%%%%%%
% lo que hace eliza: flagDo
elizaDoes(X, R):- does(X), R = ['Yes', i, X, and, i, love, it].
elizaDoes(X, R):- \+does(X), R = ['No', i, do, not, X ,'.', it, is, too, hard, for, me].
does(study).
does(cook).
does(work).
%%%%%%%%%%%%%%%%%
% lo que es eliza: flagIs
elizaIs(X, R):- is0(X), R = ['Yes', i, am, X].
elizaIs(X, R):- \+is0(X), R = ['No', i, am, not, X].
is0(dumb).
is0(weird).
is0(nice).
is0(fine).
is0(happy).
is0(redundant).
% http://stackoverflow.com/questions/12939425/prolog-access-specific-member-of-list
% magic happens here :D
% lo que hace match es comparar el input y el estímulo término a
% término, ignorando los s(X) en cada template. Si match falla en el
% loop de la función eliza, empezará el backtracking que permite
% recorrer los templates ingresados. match devolverá verdadero si haya
% un estímulo (primer término del template) que coincide con el Input, y
% devolverá la respuesta.
match([],[]).
match([], _):- true.
match([S|Stim],[I|Input]) :-
atom(S), % si I es un s(X) devuelve falso
S == I,
match(Stim, Input),!.
match([S|Stim],[_|Input]) :-
% I es un s(X), lo ignoro y continúo con el resto de la lista
\+atom(S),
match(Stim, Input),!.
% esta función es para que se muestre bien en pantalla las respuestas de
% eliza. Además responde a lo que le gusta Eliza y a lo que hace, la
% función select reemplaza el número en la respuesta por la palabra
% clave hallada:
replace0([], _, _, Resp, R):- append(Resp, [], R),!.
% Eliza likes:
replace0([I|_], Input, _, Resp, R):-
nth0(I, Input, Atom),
nth0(0, Resp, X),
X == flagLike,
elizaLikes(Atom, R).
% Eliza does:
replace0([I|_], Input, _, Resp, R):-
nth0(I, Input, Atom),
nth0(0, Resp, X),
X == flagDo,
elizaDoes(Atom, R).
% Eliza is:
replace0([I|_], Input, _, Resp, R):-
nth0(I, Input, Atom),
nth0(0, Resp, X),
X == flagIs,
elizaIs(Atom, R).
replace0([I|Index], Input, N, Resp, R):-
length(Index, M), M =:= 0,
nth0(I, Input, Atom),
select(N, Resp, Atom, R1), append(R1, [], R),!.
replace0([I|Index], Input, N, Resp, R):-
nth0(I, Input, Atom),
length(Index, M), M > 0,
select(N, Resp, Atom, R1),
N1 is N + 1,
replace0(Index, Input, N1, R1, R),!.