Eliza’s Legacy

A work in progress

George Woodrow

Join the Conversation #WolframTechConf

Background

Eliza is a program first developed in the 1960’s by Joseph Weizenbaum at the MIT artificial Intelligence Laboratory.It simulated a Rogerian psychotherapist.
I first came across Eliza in a BASIC implementation in the late 1970s. I expanded on the code that appeared in Creative Computing in 1977, and it was effective enough that it ‘fooled’ some graduate students into thinking it was ‘intelligent’.
It was, arguably, the first chat-bot. In its format, it resembled any number of messaging applications, and at times, it is hard to tell whether the ‘person’ at the other end is real or a computer.
Today, chat-bots are all over. I found one that helps you beat parking tickets in New York City and other large cities. I am sure that at least some of the time, when you are chatting with customer service, you are actually interacting with a bot.

Revisiting Eliza

Wolfram Language has extensive language processing capabilities that make it possible to greatly extend the artificial intelligence that was in earlier implementations. This is reason enough to implement Eliza in Wolfram Language.

However, the main reason I embarked on this project was in response I had to an experience I had talking with some maths professors about doing some work in Mathematica. I was dissatisfied by the fact that they wanted to use true-false and multiple-quess questions for quizzes. Students deserve better.

A chat-bot would be a very useful interface for educational software, where the user can explore a topic, and then ask questions about something that is not understood. There is a limit to what can be discovered using sliders and other controls. It would be useful if a student could say something like “I don’t understand what is happening in step 2.”

As Conner Flood points out in his Wolfram Blog about his Wolfram|Alpha induction proof generator, there are lots of things that are not computation- based in mathematics, and by extension to a lot of other fields. As with the proofs by induction, the assistance provided by a chat-bot in an application does not need to cover 100% of the cases, only the 80% of the most common problems.

So, for this project, there are two stages:

Implement Eliza using Wolfram Language

Apply chat-bot technology to a specific educational app.

For the latter case, I chose a program that will teach species counterpoint. Unfortunately, it took longer than expected to complete stage 1.

Implementing Eliza in Wolfram Language

Overall Design considerations

Eliza was originally written as a command-line program. I found an implementation in the Wolfram Library from 1992 by Matthew Markert that only really worked as a command-line program. However, I wanted to use a modern UI — to make it resemble a modern chat program like Messages — so a command line was out of the question.

The program has two basic parts: the UI and the natural language parser. The UI part is relatively easy to implement in Wolfram Language:

Framed[Column[{makeConversationRowFromText["How can I help you?", False, 500] ,makeConversationRowFromText["I'm very depressed", True, 500] ,makeConversationRowFromText["Sorry to hear that. Can you tell me more?", False, 500] ,makeConversationRowFromText["My research is going nowhere. No one understands what I am doing, and I am in danger of losing my funding!", True, 500] ,makeConversationRowFromText["That's too bad.", False, 500] ,makeConversationRowFromText["Is that the best you can do?", True, 500] }]]

However, one key part of the UI is the section where the user enters text. This part of the user interface proved to be much more difficult to implement.
In the program, the program presents a place for the user to enter text (e.g. a field with a prompt), and waits until the user is done (hits the ⌤ key or equivalent), and then processes the entered text. The program then returns to the prompt. We need to maintain this structure, while also allowing the entire chat-bot to be implemented as a panel in a larger program.

Three options for interactive text input

InputString[]

InputString[] is the most primitive of the three functions, introduced in version 1.0 (revised in version 10). This is the proper choice for a command-line program. When used with a GUI, the input field appears in a separate dialog window. However, with a little work, a passable UI could be implemented

Module[{myString="", conversationPod = makeConversationRowFromText["How can I help you", False, 500],
conversations ,nPods,imDone = False},

conversations = {conversationPod};
While[!imDone,
nPods = Min[6, Length[conversations]];
myString =InputString[Column[Take[conversations, -nPods]], WindowSize->550, WindowTitle->"Eliza]["];
If [(myString ≠ "bye"),
(
conversationPod = makeConversationRowFromText[myString, True, 500];

AppendTo[conversations, conversationPod];
conversationPod = makeConversationRowFromText["Clever response from Eliza here", False, 500];

AppendTo[conversations, conversationPod];
),
(
imDone = True;
conversationPod = makeConversationRowFromText[myString, True, 500];
AppendTo[conversations, conversationPod];
conversationPod = makeConversationRowFromText["It was a pleasure chatting with you.", False, 500];
AppendTo[conversations, conversationPod];

)];
]
]

The problem with this is that the dialog closes and re-opens with every input, and the final input never appears.

FormFunction[]

This function was introduced in Version 10, and is intended to be used primarily with web-forms. However, it does meet the formal requirements for program flow.

getMyString = FormFunction[{"entry" -> "String"},#[[1]]& ]

testString = getMyString[];

testString

The only problem with this solution fact that it can’t be simplified to eliminate the “submit” button in favor of just using return or tab. It probably would not do for commercial code, but for research purposes, it is sufficient. We can make a prototype application:

Module[{myString="", conversationPod = makeConversationRowFromText["How can I help you", False, 500],imDone = False},

Print[conversationPod];
While[!imDone,
myString =getInputFancy[];
If [(myString ≠ "bye"),
(
conversationPod = makeConversationRowFromText[myString, True, 500];
Print[conversationPod];
conversationPod = makeConversationRowFromText["Clever response from Eliza here", False, 500];
Print[conversationPod];
),
imDone = True];
]
]

InputField[]

InputField was initially almost impossible to implement successfully. However, due to some excellent spelunking by Kyle Martin of the Wolfram Technology group, we have the code needed to implement a chat-bot using Wolfram Language.
The key functionality is to use EventHandler[] to capture the return-key-pressed event to trigger the action. When the return key is pressed, the code takes the contents of the field and processes it, then resets the contents of the field. Unlike the implementations using InputString or FormFunction, there is no While loop, and you can see that the cell bracket is not continuously active.

Here is the basic code:

demoConversations = {makeConversationRowFromText["How can I help you", False, 500];};
DynamicModule[{in = Null},
Panel[
Column[{Text[Style["Your Message:", Medium]],
EventHandler[InputField[Dynamic[in], String],
{"ReturnKeyDown" :> (AppendTo[demoConversations,makeConversationRowFromText[in, True, 500]];in = Null)}],
Dynamic[Column@demoConversations]
}]
]
]

Working Chat-bot Code

The code for this example is given at the end of this notebook. For a real implementation, the code would be stored in a Package. The package is provide with the materials for this talk.

chatBot[mySimpleProcessor, "Hello ☺", 350]

Eliza ][

Now that we have a user interface, we can add the artificial intelligence — replacing the “Clever response from Eliza” pods of the UI prototypes. The implementation is in the notes. It was non-trivial.

chatBot[elizResponse]

AI design in original paper by Weizenbaum

I found the original paper on the internet. While it does not have any code, it has enough information to write an implementation. This paper is available with the materials for this talk, along with a BASIC implementation

Modern Ideas

Wolfram Language has its own sophisticated language processing functionality that permits us to go beyond the original intent of the program.

TextStructure[]  is very powerful, not yet ready for prime time. It is experimental.

LanguageIdentify[]  is used to make sure that the user is typing English. Defaults to English too easily, and is confused by short inputs.

TextWords[] works better than StringSplit[] to separate a string into English words. Can be confused by punctuation.

TextSentences[] will be used in the next revision to see if the user typed in more than once sentence. Each sentence will be processed in turn.

Associations will be used in the next iteration. The current script structure is hard to maintain or extend.

Applications: Design Considerations

Use Associations in the Script and write a function to create and maintain scripts

This is what the script looks like, formatted to show the structure:

Panel[Framed[Pane[Grid[Table[{Column[workingScript[[i,1]]], workingScript[[i,2]],Column[Table[{Grid[{{workingScript[[i,3,j,1]],Column[workingScript[[i,3,j,2]], Dividers->All]}}, Dividers->Center]},{j,1,Length[workingScript[[i,3]]]}],Dividers->Center]},{i, 1, Length[workingScript]}] , Frame->All, Alignment->Left], {540,300}]],ImageSize->{580, 330}]

As it is, it is difficult to edit or maintain. Since the goal of the project is to make it easy for a non-expert to create a chat-bot to use as an interface to another, more important, program, it is critical that the steps involved in making the script be as easy as possible.

Generalize the script for other types of interaction

Eliza is a special case of the chat-bot. It allows free-form input and does not know anything about the ‘real world’. By contrast, the chat-bots that may be used in the classroom need to know a great deal about the real world. It needs to know the ‘rules’ of the system, and the current ‘state’ of the program. For example, in a counterpoint program, it needs to know about the rules of counterpoint as well as the notes of the exercise being done by the student.

The script will not contain this knowledge at all, but it needs to have patterns that are useful. For example, it will need a pattern “what is the note you used in measure 6”, or “why do you think that the c# in measure 4 is a good choice”. These scripts can become very involved without some sort meta-script elements.

Another feature of the system is the need to do assessment. For the Eliza program, the computer simply responded to a user’s input. For an educational program, the computer will need to ask the user questions. There is also the need to determine what constitutes mastery. We do not want to make a score based on Q&A or a series of responses that would generate a percentage score. While it would be possible to design such a system, my philosophy is that the aim of the system is mastery by the student. How long it takes to get there is relatively unimportant.

Implement scripts in other languages

One application of the chat-bot could be to learn a second language. Other than the ‘stunt’ code that tries to restrict Eliza to English, the scripts are constructed in such a way that they could be constructed in any language that can be typed into the computer.

I can see an application where the student and a computer chat about a picture or video. The model for the ‘state’ would know various facts about the picture. It would be a feat of programming to use ImageIdentify to detect objects in a scene and construct relationships between them. (“La plume de mon tante est sure le bureau de mon oncle.”)

Speaking and listening

Depending on the hardware, it would be easy enough to have the computer speak its side of the conversation. Whether this would be of more than novelty value remains to be seen. However, if it were possible to synthesize speech of a quality sufficient for language instruction (without specialized coding or hardware), it would be very valuable indeed.

On some systems, there is built-in dictation functionality, and on other systems, add-on software is available. This might be useful even if the quality was only marginal, but the real test would be to see of the speech recognition software could understand a language student.

What next?

Collaboration

I think that this project is potentially very useful for a lot of disciplines. I can’t do all the work myself.

I will continue the project on the Wolfram Community website.

Feel free to contact me directly to work on this project: georgevw3@mac.com

Cloud Deployment

I think it would be easy enough to deploy Eliza to the cloud.
Implementing a chat-bot to provide instruction for an area where a more sophisticated model is required will be a challenge.

New Technology

TextStructure[] — needs to work for complex sentences and in different languages
Neural Nets — abandon context-free bots

Implementation

Implementation

Simple Chat-bot code

chatBot[stringInterpreter_, initialPrompt_String:"How may I help you?", width_:500] :=
DynamicModule[{in = "",theTalk = {makeConversationRowFromText[initialPrompt, False, width - 20]}},
Deploy[Panel[
Column[{Pane[Dynamic[Column[theTalk =Take[theTalk,-Min[6, Length[theTalk]]]]],ImageSize->{width, 200},Scrollbars->{False, Automatic},ScrollPosition->{0, -1}, AppearanceElements->None],
Row[{Text[Style["🐺: ", 16, Blue, Bold]],
EventHandler[InputField[Dynamic[in], String, ImageSize->width-40,FieldHint->"Type here!", FieldHintStyle->{Blue}, BaseStyle->{"Output"}, FieldSize->{Infinity, 1}],
{"ReturnKeyDown" :> (AppendTo[theTalk,makeConversationRowFromText[in, True, width-20]];
AppendTo[theTalk,makeConversationRowFromText[stringInterpreter[in], False, width-20]];
in = "")}]}]

}, Dividers->All, Spacings->1.5, Background->{White, LightGray}],Background->White
]
]
]

makeConversationRowFromText[s_String, userQ_, theWidth_] := Row[{Framed[Text[Style[s, "Output", If[userQ,White,Black]]],Background->If[userQ,RGBColor[0.243,0.662,0.976],LightGray], RoundingRadius->7, FrameStyle->None]}, ImageSize->theWidth, Alignment->If[userQ, Right, Left]]

This code is a dummy function that ‘processes’ the string input.

mySimpleProcessor[aString_String] := "My default response"

Working Eliza code

Constant and array definitions:

workingScript = makeWorkingScript[elizaScript];

allKeywords =findAllKeywords[workingScript];

This function meets the requirements for chatBot[]. It takes a string input and returns a string.

elizResponse[theString_String] := Module[{theResponse},

(* default response *)
theResponse = "Please continue";
(* find the appropriate response. We check for short response and non-english first *)
Which[
shortInputQ[theString], theResponse = shortInputResponse[theString],
notEnglishQ[theString], theResponse = notEnglishResponse[theString],
containsKeywordQ[theString,allKeywords], theResponse  = makeResponseString[theString,workingScript],
True, theResponse = handleDefaultResponse[]
];
theResponse

]

We will put copies of the ‘final’ functions here. The cells will have initialization flags

shortInputQ[theString_String] := If[StringLength[theString] < 2, True, False]
shortInputResponse[theString_String] := RandomChoice[{"You need to type something.","That was very short. Can you elaborate.", "Don't be so terse."}]

Added length test because WL thinks “no” is Spanish.

notEnglishQ[theString_String] := If[(StringLength[theString] < 3 )||(LanguageIdentify[theString][EntityProperty["Language","Name"]] == Entity["Language", "English"][EntityProperty["Language","Name"]]), False, True, True]
notEnglishResponse[theString_String] := Module[{theLanguage, myResponse},
theLanguage = LanguageIdentify[theString][EntityProperty["Language","Name"]];
(* We know it is not English *)
myResponse = RandomChoice[{StringTemplate["I'm sorry, I don't speak ``. Please use English."][theLanguage], StringTemplate["`` is greek to me. Please use English."][theLanguage],"I'm sorry, I speak only English."}]
]

containsKeywordQ[theString_String, keywordList_] := ContainsAny[ToLowerCase[TextWords[preProcessString[theString]]], keywordList]

This is code to do the pattern matching. It assumes that there is a list words from the input string.

patternMatchesQ[stringWordList_, testPattern_] := Module[{wordPos, testMatch, diffs, theLength,wordsInStr, patternWords, ltm, lpat, patInts},
patternWords = Cases[testPattern, _String];
theLength = Length[stringWordList];
(* make a list of the first indices where the words match what is in the pattern. We need to delete any empty list. A more complex program would find all matches and then permute the indices to get all the patterns. We do not need to do this here. *)
wordPos = Flatten[Map[First,DeleteCases[Map[Position[stringWordList, #]&, patternWords], {} ]]];
(* get the differences in position in case we need them. If they are out of order, the difference will be negative for that pair. If there is only one matching word, diffs will be an empty list. *)
diffs  = Differences[wordPos];
(* We need to sort the indices so that the words appear in the order they are in the string word list *)
wordsInStr = stringWordList[[Sort[wordPos]]];

If[wordsInStr == patternWords, 
(
(* we are resdy for more testing. We know that there are words in the input that match the words in the template. However, the template may have information on the number of extra words that appear around these words. *)
(* We take care of the easy case first. If the pattern has only words, and all of the words are in the pattern, then we know we have a match, since there is nothing else to check.*)
If[(Length[ testPattern] == Length[patternWords])∧ (theLength == Length[patternWords]),
(
(* We are done. *)
True
)
,
(
(* We have a more complicated pattern. We need to look at the numbers. *)
(* If the first element of the pattern is a number, put the index of the first match in front *)
(* Then put diffs, leaving out any with a value of 1 -- ignore adjacent entries *)
(* If the last element in the pattern is a number, put the difference between the length of the list and the position of the last word, plus 1 *)
(* Then subtract 1 from everything *)
(* BUG FIX: for the If statements, we need to put in {} in case the case fails. *)
testMatch = Flatten[{If[IntegerQ[testPattern[[1]]],wordPos[[1]], {}], Select[diffs, #>1&], If[IntegerQ[testPattern[[-1]]],theLength - wordPos[[-1]]+ 1, {}]}] -1;
(* We check to see if there is a match for all the words in the list. *)
(* If there is, we see if the non-zero numbers match *)
ltm = Length[testMatch];
patInts =Cases[testPattern, _Integer];
lpat = Length[patInts];

(* see if we have the same pattern. If so, compare. myComparison returns True if the test is 0, since it matches anything. *)
If[(ltm == lpat),
Apply[And,Map[myComparison, Transpose[{patInts, testMatch}]]],False]

)
]
),
( 
(* we are done. The words in the input string are not an exact match to the words in the pattern. Any further testing is unnecessary. *)
False
)]

]

This function makes a response string from the input string and the work script. If we get to the end of the script without a match, it means that the keyword match was very weak. For now, we use the default response for when the input string had no keyword match at all. This will be fixed in a future revision.

makeResponseString[inStr_String, workScript_] := Module[{theResponse, index, lastEntry, imDoneQ, wrkStr},
imDoneQ = False;
lastEntry = Length[workScript];
theResponse = puntMarker;
wrkStr= preProcessString[inStr];
index = 1;
While[!imDoneQ,
If[containsKeywordQ[wrkStr, workScript[[index, 1]]],
(
(* we found a keyword *)
theResponse = synthesizeResponseString[wrkStr, workScript[[index]]];
If[theResponse ≠ puntMarker, imDoneQ = True];
),
(
(* we did not find a keyword *)
index++;
If[index > lastEntry, 
(
imDoneQ = True;
If[theResponse == puntMarker, theResponse = handleDefaultResponse[]];
)
]
)
];
];
theResponse
]

This function synthesizes a response string given the working string (already pre-processed) and the script element where we found the match. We know that there will be a new string -- not ‘punt’.

synthesizeResponseString[wrkingStr_, scriptElement_] := Module[{
inputStrList, myKeyword,nPatterns,indexOfPats,patterns, myPattern, parts,response, imDone },
inputStrList =ToLowerCase[TextWords[wrkingStr]];
(* first, we need to get the exact keyword we found. Right now, we just know that there is a macth in the list *)
myKeyword = First[Intersection[scriptElement[[1]], inputStrList]];
(* Isolate the list of patterns to make indexins easier *) 
patterns = scriptElement[[3]];
nPatterns = Length[patterns];
indexOfPats = 1;
imDone = False;
(* Go through the list of patterns in order. 
Replace kwMarker with the keyword we found.
See if the input matches the pattern.
If it does, then pick one of the associated responses at random.
This response may be a template so we will need to deal with this possibility.
*)
While[!imDone,
(
myPattern = patterns[[indexOfPats, 1]]/. kwMarker-> myKeyword;
If[patternMatchesQ[inputStrList, myPattern],
(
(* we have a match *)
imDone = True;
(* The response may be a simple string or a template *)
response = RandomChoice[patterns[[indexOfPats, 2]]];
(* The response may be a simple string or a template.
Many of the responses are not templates, so we check here.
 *)
If[StringContainsQ[response, "`"],
(
parts = assembleParts[inputStrList,myPattern] ;
response = Apply[StringTemplate[response], parts];
)];

),
(
indexOfPats++;
If[indexOfPats > nPatterns, imDone = True];
)];
)
];
response
]

For response strings that are string templates, we use this function.

assembleParts[stringList_,thePattern_] := Module[{indices, theParts,idex,subStr},
indices = Flatten[Map[First,DeleteCases[Map[Position[stringList, #]&, Cases[thePattern,_String]], {} ]]];
AppendTo[indices, Length[stringList]];
theParts = {};
If[indices[[1]] > 1, 
(
subStr = stringList[[1;;indices[[1]]-1]];
AppendTo[theParts,StringRiffle[postProcessString[subStr]]];
),
(
(* we have no words before the first pattern word.
If the pattern has a 0 in the first place, we need to 
put something in.
*)
If[thePattern[[1]] == 0, AppendTo[theParts,""]]
)
];
For[idex = 1, idex < Length[indices], idex++,
(
 AppendTo[theParts,stringList[[indices[[idex]]]]];
If[indices[[idex +1]]- indices[[idex]] > 1,
 (
subStr = stringList[[indices[[idex]] +1;;indices[[idex+1]]]];
AppendTo[theParts,StringRiffle[postProcessString[subStr]]]
)
];
)
];
theParts

]

This is a helper function to do the comparison. It may need to be expanded if we have other non-word markers in the pattern. This may be necessary if, for example, we need to guarantee that part of the input string is something that can be reduced to a number (or even specifically an integer).

myComparison[{pattern_,result_}] := 
Which[pattern == 0, True,
pattern == -1,If[result>0, True, False],
 pattern > 0, If[pattern == result, True, False]
]

Here are the definitions, etc. for the script.

kwMarker = "«𝒦»";

puntMarker = "𝒫𝒰𝒩𝒯";

Eliza Script

Here is the prototype script. We will probably need to rejigger the weights, and perhaps ad “i’ve” as a keyword.
SEPTEMBER 19: update ‘why’

elizaScript = {
(* Keyword SORRY *)
{{"sorry"}, 2, {(* patterns and responses *)
(* pattern 1*)
{{0, kwMarker,0}, 
{(* responses *)
"Please don't apologize.",
 "Apologies are not necessary.", "What feelings do you have when you apologize?", "I've told you that apologies are not required."
}
}
}
},
(* Keyword REMEMBER *)
{{"remember"}, 5, {(* patterns and responses *)
(*pat 1  I remember <something> *)
 {{0, "i", kwMarker, -1}, 
{(* responses *)
"Do you often think of `4`.", "Does thinking of `4` bring anything else to mind?", "What else do you remember?",
"Why do you remember `4` just now?",
"What, in the present situation reminds you of `4`?",
"What is the connection between me and `4`?"
}
},
(*pat 1a  I remember *)
 {{0, "i", kwMarker}, 
{(* responses *)
"What do you remember?", 
"What else do you remember?",
}
},
(*pat 2 do you remember *)
 {{0, "do", "you", kwMarker, -1},
{(* responses *)
"Did you think I would forget `5`?",
"Why do you think I should recall `5` now?",
"What about `5`?",
"You mentioned `5`."
}
},
(* Pat 3 -- Generic   ---- PUNT ---- *) 
{{0, kwMarker, 0},
{(* responses *)
"Please, go on.",
"Interesting....",
puntMarker,
puntMarker
}
}
}
},
(* Keyword IF *)
{{"if"}, 3, {(* patterns and responses *)
{{0, kwMarker, -1}, 
{ (* responses *)
"Do you think it is likely that `3`?",
"Do you wish that `3`?",
"What do you think about `3`?"
}
}, (* end of pattern *)
{{0, kwMarker, 0}, 
{ (* responses *)
puntMarker
}
} (* end of pattern *)
}
},
(* Keyword DREAMT, DREAMED *)
{{"dreamt", "dreamed"}, 4, {(* patterns and responses *)
(* pattern 1 I dreamt *)
{{0, "i", kwMarker,"about", -1},
{(* responses *)
"really, about `5`?",
"Have you ever fantisied about `5` while you were awake?",
"Have you dreamt about `5` before?"
}
}, (* end of pattern *)
(* pattern 2 default *)
{{0, "i", kwMarker,0},
{(* responses *)
"Do youy think dreams are significant?",
"Have you ever fantisied about something while you were awake?",
"Tell me more about your dreams."
}
} (* end of pattern *)
}
},
(* keyword HELLO *)
{ {"hello", "hi"}, 0, {(* patterns and responses *)
{{0, kwMarker,0}, 
{(* responses *)
"Hello. Please state your problem."
}
}}
},
(* Keyword COMPUTER *)
{{"computer", "computers", "machines", "machine"}, 10, {(* patterns and responses *)
{{0, kwMarker,0},
{(* responses *)
"Do computers worry you?",
"Why do you mention computers?",
"What do you think machines have to do with your problem?",
"Don't you think that computers can help people?",
"What is it about machines that worries you?",
"What do you think about computers?"
}
}
}
},
(* Keyword FAMILY *)
{{"mother", "father", "sister", "brother", "mom", "sisters", "brothers", "children"}, 14, {(* patterns and responses*)
(* Pattern my <family> -1  1*) 
{{0, "my", 0, kwMarker, -1, "me"},
{(* responses *)
"Does your `4` really `5` you?",
"Who else in your family `5` you?"
}
},
(* Pattern my <family> 2*) 
{{0, "my", 0, kwMarker, 0},
{(* responses *)
"Tell me more about your family.",
"What else comes to mind when you think of your `4`?"
}
},
(* Pattern 3  default *)
 {{0, kwMarker, 0}, 
{(* responses *)
"Tell me about your `2`."
}
}
}
},
(* keyword SPOUSE *)
{{"husband", "wife","spouse", "partner"},11, {(* patterns and responses*)
(* Pattern 1 love my <spouse>*)
{{0, "love", "my",kwMarker, 0},
{(* responses *)
"That's great. Tell me more.",
"How long have you felt this way?",
"Does your `4` feel the same way as you do?"
}
},
(* Pattern 2 hate my <spouse> *)
{{0, "hate", "my",kwMarker, 0},
{(* responses *)
"That must be distressing. Tell me more.",
"How long have you felt this way?",
"Does your `4` feel this way, too?"
}
},
(* Pattern 3 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Tell me more about your `2`."
}
}
}

},
(* keyword YES *)
{{"yes", "certainly","definitely"},1, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"You seem to be quite positive.",
"How can you be so sure?",
"I see. Tell me more.",
"I understand. What's significant about that?",
"Are you sure?"
}
}}
},
(* keyword NO *)
{{"no", "nope"},1, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Are you saying ‘no’ just to be negative?",
"You are being a bit negative.",
"Why not?",
"Are you sure?"
}
}}
},
(* keyword MAYBE *)
{{"maybe", "perhaps"},2, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"You don't seem to be quite certain.",
"Why the uncertain tone?",
"Can't you be more positive?",
"You aren't sure?",
"Don't you know?"
}
}}
},
(* keyword NAME *)
{{"name"},5, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"I am not interested in names.",
"I told you, I don't care about names.",
"Please continue."
}
}}
},
(* keyword FRIEND *)
{{"friend", "friends"},13, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Why do you bring up the topic of friends?",
"Do your friends worry you?",
"Do your friends pick on you?",
"Are you sure you have any friends?",
"Do you impose on your friends?",
"Perhaps your love for your friends worries you?"
}
}}
},
(* keyword ALWAYS *)
{{"always"},3, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Can you think of a specific example?",
"When?",
"What are you thinking of?",
"Really, always?"
}
}}
},
(* keyword NEVER *)
{{"never"},3, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"I", kwMarker, -1},
{(* responses *)
"Maybe you just didn't notice when you `4`.",
"How can you be sure that you never `4`?",
"Really, never?"
}
},
(* Pattern 2 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Maybe you just didn't notice.",
"Really, never?"
}
}}
},
(* keyword EVERYONE *)
{{"everyone", "everybody","nobody"},9, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Really, `2`?",
"Surely not `2`?",
"Can you think of anyone in particular?",
"Who, for example?",
"Someone special, perhaps?",
"You have a particular person in mind, don't you?",
"Who do you think you are talking about?"
}
} (* Pattern end *)
}
},
(* keyword CAN -- can not already processed to can't  *)
{{"can"},6, {(* patterns and responses*)
(* Pattern 1  *)
{{0, "i",kwMarker, -1},
{(* responses *)
"What makes you think that you can `4`?",
"Very good. What do you do for an encore?",
"Does that make you feel special?",
"You can `4`. Is that significant?"
}
},
(* Pattern 2  *)
{{0, kwMarker,"i", -1},
{(* responses *)
"Perhaps you don't want to `4`,?",
"Do you want to be able to `4`?",
"Do you need permission to `4`?",
"Who, for example?",
"Someone special, perhaps?",
"You have a particular person in mind, don't you?",
"Who do you think you are talking about?"
}
},
(* Pattern 3  *)
{{0, "you",kwMarker, -1},
{(* responses *)
"How do you know this?",
"Does it please you that I can `4`?",
"Do you wish you could `4`?"
}
},
(* Pattern 4  *)
{{0, kwMarker,"you", -1},
{(* responses *)
"Don't you believe that I can `4`?",
"Perhaps you would like to be able to `4`?",
"You want me to be able to `4`?"
}
},
(* Pattern 5 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"I'm not sure I understand. Can you elaborate?",
"Interesting....",
"Tell me more."
}
}}
},
(* keyword DON'T -- do not already transformed to don't *)
{{"don't"},5, {(* patterns and responses*)
(* Pattern 1 *)
{{0,"i", kwMarker, -1},
{(* responses *)
"Don't you really `4`?",
"Why don't you `4`?",
"Do you wish you were able to `4`?",
"Does that trouble you?"
}
},
(* Pattern 2 *)
{{0, "you",kwMarker, -1},
{(* responses *)
"Why do you think that I don't `4`?",
"How do you know this?",
"Can you `4`?"
}
},
(* Pattern 3 *)
{{0, "why",kwMarker,"you", -1},
{(* responses *)
"Do you really believe that I don't `5`?",
"Perhaps in good time, I will.",
"Do you want me to `5`?"
}
},
(* Pattern 4 -- default --  'PUNT' ---- *)
{{0,kwMarker, 0},
{(* responses *)
puntMarker
}
}}
},
(* keyword I'M -- I am already transformed  -- NEEDS FLESHING OUT -- *)
{{"i'm"},2, {(* patterns and responses*)
(* Pattern 1  *)
{{0, kwMarker, -1},
{(* responses *)
"Did you come to me because you are `3`?",
"How long have you been `3`?",
"Do you believe it is normal to be `3`?",
"Do you enjoy being `3`?"
}
}, (* pattern end *)
(* Pattern 2 -- default -- PUNT *)
{{0, kwMarker,0},
{(* responses *)
puntMarker
}
} (* pattern end *)
}
},
(* keyword CAN'T -- can not and cannot already transformed *)
{{"can't"},8, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"why", kwMarker,"i", -1},
{(* responses *)
"Do you think you should be able to `5`?",
"Why can't you `5`?"
}
} ,(* pattern end *)
(* Pattern 2 *)
{{0, "i",kwMarker, -1},
{(* responses *)
"How do you know that you can't `4`?",
"Have you tried?",
"Perhaps, you can now `4`?"
}
} ,(* pattern end *)
(* Pattern 2a *)
{{0, "i",kwMarker},
{(* responses *)
"You can't what?",
"Have you tried?",
"Are you afraid to try?"
}
} ,(* pattern end *)
(* Pattern 3 -- default --  'PUNT' ---- *)
{{0, kwMarker, 0},
{(* responses *)
puntMarker
}
} (* pattern end *)
}
},
(* keyword YOU -- paper has more complex interaction with 'i' = you *)
{{"you"},3, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"are", kwMarker, -1},
{(* responses *)
"Why are you interested in whether or not I am `4`?",
"Would you prefer if I were not `4`?",
"Perhaps in your fantasies I am `4`."
}
} ,(* pattern end *)
(* Pattern 2  Pattern requires more words *)
{{0, kwMarker,"remind", "me", "of", -1},
{(* responses *)
"In what way?",
"What resemblance do you see?",
"What does that similarity suggest to you?",
"What other connections do you see?",
"What do you suppose that resemblance means?",
"What is the connection, do you suppose?",
"Could there really be some connection?",
"How do I remind you of `6`?"
}
} ,(* pattern end *)
(* Pattern 3 *)
{{0, kwMarker,-1, "me",  0},
{(* responses *)
"Why do you think that I `3` you?",
"You like to think that I `3` you -- don't you?",
"What makes you think that I `3` you?",
"Really, I `3` you.",
"Do you wish to believe that I `3` you?",
"Suppose that I did `3` you -- what would that mean?",
"Does someone else believe that I `3` you?"
}
} ,(* pattern end *)
(* Pattern 4 -- paper has these under 'i' but our precedence rules require them here *)
{{0, "i",1,kwMarker,0},
{(* responses *)
"Why do you `3` me?",
"You like to think that you `3` me -- don't you?",
"What makes you think that you `3` me?",
"Really, you `3` me.",
"Do you wish to believe that you `3` me?",
"Suppose that you did `3` me -- what would that mean?",
"Does someone else believe that you `3` me?"
}
} ,(* pattern end *)
(* Pattern 4  *)
{{0, kwMarker, -1},
{(* responses *)
"We were discussing you, not me.",
"Oh, I `3`.",
"Does it matter that I `3`?"
}
} ,(* pattern end *)
(* Pattern 5  *)
{{0, kwMarker, 0},
{(* responses *)
"We were discussing you, not me.",
"You're not really talking about me, are you?"
}
} (* pattern end *)}
},
(* keyword YOU'RE -- you are preprocessed to you're *)
{{"you're"},3, {(* patterns and responses*)
(* Pattern 1  *)
{{0,kwMarker, -1},
{(* responses *)
"What makes you think that I am `3`?",
"Does it please you to believe that I am `3`?",
"Perhaps, you would like to be `3`",
"Does that trouble you?"
}
}, (* pattern end *)
(* Pattern 2 Default *)
{{0,kwMarker, 0},
{(* responses *)
puntMarker
}
} (* pattern end *)
}
},
(* keyword WANT  *)
{{"want","need","desire"},7, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i",kwMarker, -1},
{(* responses *)
"What would it mean to you if you got `4`?",
"Why do you `3` `4`?",
"Suppose you got `4` soon. What then?",
"What if you never got `4`?",
"I sometimes also `3` `4`."
}
} ,(* pattern end *)
(* Pattern 2  *)
{{0,"i",kwMarker},
{(* responses *)
"What is it that you `3`?",
"Can you be more explicit?"
}
} ,(* pattern end *)
(* Pattern 2 -- default --  'PUNT' ---- *)
{{0,kwMarker, 0},
{(* responses *)
puntMarker
}
} (* pattern end *)
}
},
(* keyword SAD  *)
{{"sad","unhappy","depressed", "angry"},6, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i'm",kwMarker,"about", -1},
{(* responses *)
"Did you come to me because you are `3` about `5`?",
"How long have you been `3` about `5`?",
"I am sorry to hear that you are `3` about `5`. Can you tell me why.",
"Do you think that coming here will make you less `3` about `5`?",
"I am sometimes also `3` about `5`."
}
} ,(* pattern end *)
(* Pattern 2  *)
{{1,"makes","me",kwMarker,0},
{(* responses *)
"How does `1` make you `4`?",
"Does `1` make you `4` all the time?",
"I am sorry to hear that `1` makes you `4`. Can you tell me why.",
"Sometimes, `1` makes me `4`, too. Why do you think that is?"
}
} ,(* pattern end *)
(* Pattern 3  *)
{{0,"i'm",kwMarker},
{(* responses *)
"Did you come to me because you are `3`?",
"How long have you been `3`?",
"I am sorry to hear that you are `3`. Tell me why you are `3`."
}
} ,(* pattern end *)
(* Pattern 4 -- default *)
{{0,kwMarker, 0},
{(* responses *)
"I am sorry to hear you may be `2`. Can you tell me why."
}
} (* pattern end *)
}
},
(* keyword SICK  *)
{{"sick","unwell", "ill"},6, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i'm",kwMarker,"about", -1},
{(* responses *)
"Did you come to me because you are `3` about `5`?",
"How long have you been `3` about `5`?",
"I am sorry to hear that you are `3` about `5`. Can you tell me why.",
"Do you think that coming here will make you less `3` about `5`?",
"I am sometimes also `3` about `5`."
}
} ,(* pattern end *)
(* Pattern 2  *)
{{1,"makes","me",kwMarker,0},
{(* responses *)
"How does `1` make you `4`?",
"Does `1` make you `4` all the time?",
"I am sorry to hear that `1` makes you `4`. Can you tell me why.",
"Sometimes, `1` makes me `4`, too. Why do you think that is?"
}
} ,(* pattern end *)
(* Pattern 3  *)
{{0,"i'm",kwMarker},
{(* responses *)
"Did you come to me because you are `3`?",
"How long have you been `3`?",
"Can you explain what made you `3`."
}
} ,(* pattern end *)
(* Pattern 3  *)
{{0,"you're",kwMarker},
{(* responses *)
"I don't think so.",
"What makes you think that I am `3`?",
"Just because you are `3` doesn't mean that I am."
}
} ,(* pattern end *)
(* Pattern 4 -- default *)
{{0,kwMarker, 0},
{(* responses *)
"I am sorry to hear you may be `2`. Can you tell me why."
}
} (* pattern end *)
}
},
(* keyword HAPPY  *)
{{"happy","elated","glad"},6, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i'm",kwMarker,"about", -1},
{(* responses *)
"Did you come to me because you are `3` about `5`?",
"How long have you been `3` about `5`?",
"I am happy to hear that you are `3` about `5`. Can you tell me why.",
"I am sometimes also `3` about `5`."
}
} ,(* pattern end *)
(* Pattern 2  *)
{{1,"makes","me",kwMarker,0},
{(* responses *)
"How does `1` make you `4`?",
"Does `1` make you `4` all the time?",
"I am glad to hear that `1` makes you `4`. Can you tell me why.",
"Sometimes, `1` makes me `4`, too. Why do you think that is?",
"Do you make `1` `4` as well?"
}
} ,(* pattern end *)
(* Pattern 3  *)
{{0,"i'm",kwMarker},
{(* responses *)
"You come to me because you are `3`?, That's good.",
"How long have you been `3`?",
"I am happy to hear that you are `3`. Tell me why you are `3`."
}
} ,(* pattern end *)
(* Pattern 4 -- default *)
{{0,kwMarker, 0},
{(* responses *)
"I am happy to hear you may be `2`. Can you tell me why."
}
} (* pattern end *)
}
},
(* keyword BETTER  *)
{{"better"},5, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i'm",kwMarker,"than","you", 0},
{(* responses *)
"Why do you think that you are better than me`?",
"It must be important to you that you are better than me. Tell me why?",
"Are you sure about this?"
}
} ,(* pattern end *)
(* Pattern 1  *)
{{0,"i'm",kwMarker,"than", -1},
{(* responses *)
"Why do you think that you are better than `5`?",
"It must be important to you that you are better than `5`. Tell me why?",
"Are you sure about this?",
"Are you being competitive? Tell me why."
}
} ,(* pattern end *)
(* Pattern 2  *)
{{1,"makes","me",kwMarker,0},
{(* responses *)
"How does `1` make you `4`?",
"Does `1` make you `4` all the time?",
"I am glad to hear that `1` makes you `4`. Can you tell me why.",
"Sometimes, `1` makes me `4`, too. Why do you think that is?",
"Do you make `1` `4` as well?"
}
} ,(* pattern end *)
(* Pattern 3  *)
{{0,"i'm",kwMarker},
{(* responses *)
"You come to me because you are `3`?, That's good.",
"How long have you been `3`?",
"I am happy to hear that you are `3`. Tell me why."
}
} ,(* pattern end *)
(* Pattern 4 -- default *)
{{0,kwMarker, 0},
{(* responses *)
puntMarker
}
} (* pattern end *)
}
},
(* keyword FEEL *)
{{"feel"},5, {(* patterns and responses*)
(* Pattern 1  *)
{{0,"i", kwMarker, -1},
{(* responses *)
"Tell me more about these feelings.",
"Do you often feel `4`?",
"Do you enjoy feeling `4`?",
"Of what does feeling `4` remind you?"
}
} ,(* Pattern end *)
(* Pattern 2  *)
{{0,"i", kwMarker},
{(* responses *)
"Tell me more about these feelings.",
"What is it that you feel?"
}
} ,(* Pattern end *)
(* Pattern n -- default *)
{{0, kwMarker, 0},
{(* responses *)
"What do you think of when you feel this way?",
"Tell me more."
}
} 
}
},
(* keyword BECAUSE *)
{{"because", "cause"},0, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Is that the real reason?",
"Don't any other reasons come to mind?",
"Does that reason seem to explain anything else?",
"What other reasons might there be?"
}
} 
}
},
(* keyword WHY *)
{{"why"},3, {(* patterns and responses*)
(* Pattern 1 *)
{{0, kwMarker,"don't","you", -1},
{(* responses *)
"Do you believe I don't `5`?",
"Perhaps I will `5` in good time.",
"Should you `5` yourself?",
"You want me to `5`?"
}
} , (* pattern end *)
(* Pattern 1a *)
{{0, kwMarker,"don't","you"},
{(* responses *)
"What don't I?"
}
} , (* pattern end *)
(* Pattern 2 *)
{{0, kwMarker,"can't","i", -1},
{(* responses *)
"Do you thing you should be able to `5`?",
"Do you want to be able to `5`?",
"Do you believe that this will help you to `5`?",
"Have you any idea why you can't `5`?"
}
} , (* pattern end *)
(* Pattern 2a *)
{{0, kwMarker,"can't","i"},
{(* responses *)
"Why can't you *what*?",
"Because...."
}
} , (* pattern end *)
(* Pattern 3 *)
{{kwMarker},
{(* responses *)
"Why not?",
"Please, don't go all existential on me."
}
} , (* pattern end *)
(* Pattern n -- default --  'PUNT' ---- *)
{{0, kwMarker, 0},
{(* responses *)
puntMarker
}
}  (* pattern end *)
}
},
(* keyword ALIKE *)
{{"alike"},2, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"In what way?",
"What resemblance do you see?",
"What does the similarity suggest to you?",
"What other connections do you see?",
"Could there really be some connection?",
"How?"
}
}  (* pattern end *)
}
},
(* keyword THINK *)
{{"think"},4, {(* patterns and responses*)
(* Pattern 1 *)
{{0,"i", kwMarker, -1},
{(* responses *)
"But, you are not sure you `4`?",
"Do you doubt you `4`?"
}
} , (* pattern end *)
(* Pattern 1a *)
{{0,"i", kwMarker},
{(* responses *)
"Do you really think so?",
}
} , (* pattern end *)
(* Pattern 2 *)
{{0,"you", kwMarker, 0},
{(* responses *)
"What do you care about what other people think?",
"Tell me what you think I am thinking."
}
},  (* pattern end *)
(* Pattern 3 -- default --  'PUNT' ---- *)
{{0, kwMarker, 0},
{(* responses *)
puntMarker
}
}  (* pattern end *)
}
},
(* keyword MY *)
{{"my"},0, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, -1},
{(* responses *)
"Your `3`?",
"Why do you say your `3`?"
}
},  (* pattern end *)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Does that suggest anything else that belongs to you?"
}
}  (* pattern end *)
}
},
(* keyword WHAT etc. -- general responses to questions must be lowest priority *)
{{"what", "how", "who","where", "when"},0, {(* patterns and responses*)
(* Pattern 1 -- default *)
{{0, kwMarker, 0},
{(* responses *)
"Why do you ask?",
"Does that question interest you?",
"What answer would please you most?",
"What do you think?",
"Are such questions on your mind often?",
"What is it that you really want to know?",
"Have you asked anyone else?",
"Have you asked such questions before?",
"What else comes to mind when you ask that?"
}
}  (* pattern end *)
}
}


(* keyword ??? *)
};

parsing functions

makeWorkingScript[theScript_] := Sort[theScript,#1[[2]] > #2[[2]]&]

findAllKeywords[theScript_] := Flatten[theScript[[All,1]]]

This function acts on a string to make everything lower case and strip out punctuation, replacing the symbols with a space. This avoids an error that can happen when using the TextWords[] function.

preProcessString[myStr_String] := Module[{outStr = myStr},
outStr = ToLowerCase[outStr];
outStr = StringReplace[outStr,{"," -> " ", "." -> " " , ":" -> " ", ";" -> " ", "?"-> " "}];
outStr = StringReplace[outStr, {"cannot" -> "can't", "can not" -> "can't", "do not" -> "don't", "i am" -> "i'm", "you are" -> "you're", "i have"-> " i've"}]
]

Post-processing is done on the part of the input string that has been turned into a lost of words and will be incorporated into a string template. This code assumes that the part of the original sentence is in the verb part, and this may need to be refined for more complicated uses.

postProcessString[stringList_] :=stringList /. {"me" -> "you", "you" -> "me", "i've" -> "you have", "my" -> "your", "your" -> "my", "i'm" -> "you are", "you're" -> "I am"}

‘temporary’ code

This code is in place for the presentation. It works, but does not have anywhere close to the final functionality.

handleDefaultResponse[] := RandomChoice[{
"Go on...",
"Please explain that.",
"Tell me about something important to you.",
"Let's talk about your family.",
"Tell me something else about your problem.",
"There is nothing to fear about opening up a bit."
}]

Other code

StringInputExample is a prototype that uses the StringInput[] dialog as the UI

Module[{myString="", conversationPod = makeConversationRowFromText["How can I help you", False, 500],
conversations ,nPods,imDone = False},

conversations = {conversationPod};
While[!imDone,
nPods = Min[6, Length[conversations]];
myString =InputString[Column[Take[conversations, -nPods]], WindowSize->550, WindowTitle->"Eliza]["];
If [(myString ≠ "bye"),
(
conversationPod = makeConversationRowFromText[myString, True, 500];

AppendTo[conversations, conversationPod];
conversationPod = makeConversationRowFromText["Clever response from Eliza here", False, 500];

AppendTo[conversations, conversationPod];
),
(
imDone = True;
conversationPod = makeConversationRowFromText[myString, True, 500];
AppendTo[conversations, conversationPod];
conversationPod = makeConversationRowFromText["It was a pleasure chatting with you.", False, 500];
AppendTo[conversations, conversationPod];

)];
]
]

GetInputFancy

getInputFancy =FormFunction[{{"x",} -><|"Interpreter" -> "String", "Control" -> InputField, "Help" -> Style["type 'bye' when done", Italic], "Hint" -> "Tell me your problem."|>}, #[[1]]&]