Eliza Chatbot Ported to Luau [Open-Source]

The Eliza chatbot basically turns a message into a response But often asking the user to elaborate further. It is portrayed as a psychologist and originally written by a doctor in 1966 it uses pattern matching. The function has been ported to Luau by my and the function is simply this.
I corrected the grammar so it does not look like a terminal and ported this code from a old version of lua. I improved it by making answer mulitple sentences at once.

-- Joseph Weizenbaum's classic Eliza ported to Luau
-- Ported to Luau by Magus_ArtStudios, Original port Kein-Hong Man <khman@users.sf.net> 20060905
------------------------------------------------------------------------
-- Original ELIZA paper: ELIZA--A Computer Program For the Study of Natural Language
--   Joseph Weizenbaum, 1966, Communications of the ACM Volume 9,
--   URL: http://i5.nyu.edu/~mm64/x52.9265/january1966.html
------------------------------------------------------------------------
-- A copy of the original BASIC source of this Lua version of ELIZA can
-- be found at Josep Subirana's ELIZA download page.
------------------------------------------------------------------------
-- NOTES
--*  Ported to Luau by Magus_ArtStudios 10/18/2023
-- * For historical accuracy, functionality is more-or-less identical,
------------------------------------------------------------------------
 function Eliza(text)
    local response = ""
    local user = string.lower(text)
    local userOrig = user

    -- randomly selected replies if no keywords
    local randReplies = {
        "What does that suggest to you?",
        "I see...",
        "I'm not sure I understand you fully.",
        "Can you elaborate on that?",
        "That is quite interesting!",
        "That's so... Please continue...",
        "I understand...", "Well, well... Do go on", 
        "Why are you saying that?",
        "Please explain the background to that remark...", 
        "Could you say that again, in a different way?",
    }
    
    local replies = {
        [" can you"] = {"Perhaps you would like to be able to"},
        [" do you"] = {"Yes, I"},
        [" can i"] = {"Perhaps you don't want to be able to"},
        [" you are"] = {"What makes you think I am"},
        [" you're"] = {"What is your reaction to me being"},
        [" i don't"] = {"Why don't you"},
        [" i feel"] = {"Tell me more about feeling"},
        [" why don't you"] = {"Why would you want me to"},
        [" why can't i"] = {"What makes you think you should be able to"},
        [" are you"] = {"Why are you interested in whether or not I am"},
        [" i can't"] = {"How do you know you can't"},
        [" i am"] = {"How long have you been"},
        [" i'm"] = {"Why are you telling me you're"},
        [" i want"] = {"Why do you want"},
        [" what"] = {"What do you think?"},
        [" how"] = {"What answer would please you the most?"},
        [" who"] = {"How often do you think of such questions?"},
        [" where"] = {"Why did you think of that?"},
        [" when"] = {"What would your best friend say to that question?"},
        [" why"] = {"What is it that you really want to know?"},
        [" perhaps"] = {"You're not very firm on that!"},
        [" drink"] = {"Moderation in all things should be the rule."},
        [" sorry"] = {"Why are you apologizing?",  "Please don't apologize",
                "Apologies are not necessary",
                "What feelings do you have when you apologize",},
        [" dreams"] = {"Why did you bring up the subject of dreams?"},
        [" i like"] = {"Is it good that you like"},
        [" maybe"] = {"Aren't you being a bit tentative?"},
        [" no"] = {"Why are you being negative?"},
        [" your"] = {"Why are you concerned about my"},
        [" always"] = {"Can you think of a specific example?"},
        [" think"] = {"Do you doubt"},
        [" yes"] = {"You seem quite certain. Why is this so?"},
        [" friend"] = {"Why do you bring up the subject of friends?"},
        [" am i"] = {"You are"},
        [" i remember"]= {
                "Do you often think of",
                "What else do you remember?",
                "Why do you recall",
                "What in the present situation reminds you of",
                "What is the connection between me and"
        },    
       
    }

    -- keywords, replies
  

    -- conjugate
    local conjugate = {
        [" i "] = "you",
        [" are "] = "am",
        [" were "] = "was",
        [" you "] = "me",
        [" your "] = "my",
        [" i've "] = "you've",
        [" i'm "] = "you're",
        [" me "] = "you",
        [" am i "] = "you are",
        [" am "] = "are",
    }

    local function createSentences(str)
        local sentences = {} -- create an empty table to store the sentences
        local start = 1 -- initialize the start index of the current sentence
        for i = 1, #str do -- loop through each character in the input string
            local c = str:sub(i, i) -- get the current character
            if c == "!" or c == "?" or c == "." or i==#str then -- check if the current character is a punctuation mark
                local sentence = str:sub(start, i) -- get the current sentence from the start index to the current index
                table.insert(sentences, sentence) -- insert the current sentence into the table
                start = i + 1 -- update the start index to the next character after the punctuation mark
            end
        end
        if sentences[1]==nil then
            return {str}
        end
    -- random replies, no keyword
  
        return sentences -- return the table of sentences
    end
 
    
local function processSentences(user,response)
    -- find keyword, phrase
   local function replyRandomly()
        response = randReplies[math.random(#randReplies)]..""
    end
    local function processInput()
        
        for keyword, reply in pairs(replies) do
            local d, e = string.find(user, keyword, 1, 1)
            if d then
                -- process keywords
               local chr=reply[math.random(1,#reply)]
                response = response..chr.." "
                if string.byte(string.sub(chr, -1)) < 65 then -- "A"
                    response = response..""; return
                end
                local h = string.len(user) - (d + string.len(keyword))
                if h > 0 then
                    user = string.sub(user, -h)
                end
                for cFrom, cTo in pairs(conjugate) do
                    local f, g = string.find(user, cFrom, 1, 1)
                    if f then
                        local j = string.sub(user, 1, f - 1).." "..cTo
                        local z = string.len(user) - (f - 1) - string.len(cTo)
                        response = response..j..""
                        if z > 2 then
                            local l = string.sub(user, -(z - 2))
                            if not string.find(userOrig, l) then return end
                        end
                        if z > 2 then response = response..string.sub(user, -(z - 2)).."" end
                        if z < 2 then response = response.."" end
                        return 
                    end--if f
                end--for
                response = response..user..""
                return response
            end--if d
        end--for
        replyRandomly()
        return response
    end

    -- main()
    -- accept user input
    if string.sub(user, 1, 3) == "bye" then
    response = "Bye, bye for now.See you again some time."
    return response
end
if string.sub(user, 1, 7) == "because" then
    user = string.sub(user, 8)
end
    user = " "..user.." "
    -- process input, print reply
    processInput()
    response = response..""
    return response
    end
    local responsec=""
    local responses={}
    for i,v in pairs(createSentences(user)) do
        local response=nil
        local answ=processSentences(v," ")
        if responses[answ]==nil then
            print(responses)
        responses[answ]=1
        responsec=responsec..answ
      end
    end
    return responsec
end

Here is an example of input to output by running this function with some input text with a module in the command prompt

cm=require(chatbotloc) print(cm.Eliza(" I remember picking flowers. Do you like picking flowers? Goodbye for now"))  -  Studio
  What else do you remember?  Why are you interested in whether or not I am like picking flowers?  Why are you being negative? 

For those interested this is the original Lua source code it was ported from!

https://lua-users.org/wiki/SciteElizaClassic

The way the responses seem to work is they are appended by the rest of the sentence with a direct key lookup table by processing the text. The responses with no punctuation at the end are appended by the users query following the key look up pattern.

If you art interested in using this in conjunction with a more advanced chatbot library check out this related resource! :slight_smile:
AI Chatbot Library [Open Source] Eliza, Emotion Awareness Emojis,Bag of Words,Previous/Next Word Predictor,Text Randomizer, Word Geometry+Math Solver - Resources / Community Resources - Developer Forum | Roblox

15 Likes

very cool bit of history, very cool preservation of said history

It’s a neat function I think it could also be useful for returning a part of the sentence that contains what the user wants to know about or creating a reflection input for a inference model like gpt2 to have it respond in a certain way.

I present a second version of this chatbot. This one returns the part of the sentence with most relevance to a search query. It does this by returning the chunk of the sentence if was going to transform in addition to its response. The result is this.

 local cm=require(game.ReplicatedStorage.GlobalSpells.ChatbotAlgorithm.ChatModule:Clone()) res,imp=cm.Eliza("I like butterflies. do you like butterflies?") print(imp) print(res)  -  Studio
  21:57:56.756  butterflies. like butterflies?   -  Edit
  21:57:56.756   Is it good that you like butterflies.  Yes, I like butterflies? 
local randReplies = {
    "What does that suggest to you?",
    "I see...",
    "I'm not sure I understand you fully.",
    "Can you elaborate on that?",
    "That is quite interesting!",
    "That's so... Please continue...",
    "I understand...", "Well, well... Do go on", 
    "Why are you saying that?",
    "Please explain the background to that remark...", 
    "Could you say that again, in a different way?",
}

local replies = {
    [" can you"] = {"Perhaps you would like to be able to"},
    [" do you"] = {"Yes, I"},
    [" can i"] = {"Perhaps you don't want to be able to"},
    [" you are"] = {"What makes you think I am"},
    [" you're"] = {"What is your reaction to me being"},
    [" i don't"] = {"Why don't you"},
    [" i feel"] = {"Tell me more about feeling"},
    [" why don't you"] = {"Why would you want me to"},
    [" why can't i"] = {"What makes you think you should be able to"},
    [" are you"] = {"Why are you interested in whether or not I am"},
    [" i can't"] = {"How do you know you can't"},
    [" i am"] = {"How long have you been"},
    [" i'm"] = {"Why are you telling me you're"},
    [" i want"] = {"Why do you want"},
    [" what"] = {"What do you think?"},
    [" how"] = {"What answer would please you the most?"},
    [" who"] = {"How often do you think of such questions?"},
    [" where"] = {"Why did you think of that?"},
    [" when"] = {"What would your best friend say to that question?"},
    [" why"] = {"What is it that you really want to know?"},
    [" perhaps"] = {"You're not very firm on that!"},
    [" drink"] = {"Moderation in all things should be the rule."},
    [" sorry"] = {"Why are you apologizing?",  "Please don't apologize",
        "Apologies are not necessary",
        "What feelings do you have when you apologize",},
    [" dreams"] = {"Why did you bring up the subject of dreams?"},
    [" i like"] = {"Is it good that you like"},
    [" maybe"] = {"Aren't you being a bit tentative?"},
    [" no"] = {"Why are you being negative?"},
    [" your"] = {"Why are you concerned about my"},
    [" always"] = {"Can you think of a specific example?"},
    [" think"] = {"Do you doubt"},
    [" yes"] = {"You seem quite certain. Why is this so?"},
    [" friend"] = {"Why do you bring up the subject of friends?"},
    [" am i"] = {"You are"},
    [" i remember"]= {
        "Do you often think of",
        "What else do you remember?",
        "Why do you recall",
        "What in the present situation reminds you of",
        "What is the connection between me and"
    },    

}

-- keywords, replies


-- conjugate
local conjugate = {
    [" i "] = "you",
    [" are "] = "am",
    [" were "] = "was",
    [" you "] = "me",
    [" your "] = "my",
    [" i've "] = "you've",
    [" i'm "] = "you're",
    [" me "] = "you",
    [" am i "] = "you are",
    [" am "] = "are",
}

function Eliza(text)
    local response = ""
    local user = string.lower(text)
    local userOrig = user

    -- randomly selected replies if no keywords


    local function createSentences(str)
        local sentences = {} -- create an empty table to store the sentences
        local start = 1 -- initialize the start index of the current sentence
        for i = 1, #str do -- loop through each character in the input string
            local c = str:sub(i, i) -- get the current character
            if c == "!" or c == "?" or c == "." or i==#str then -- check if the current character is a punctuation mark
                local sentence = str:sub(start, i) -- get the current sentence from the start index to the current index
                table.insert(sentences, sentence) -- insert the current sentence into the table
                start = i + 1 -- update the start index to the next character after the punctuation mark
            end
        end
        if sentences[1]==nil then
            return {str}
        end
        return sentences -- return the table of sentences
    end
    local function IspunctuationSentences(str)
        local sentences = {} -- create an empty table to store the sentences
        local start = 1 -- initialize the start index of the current sentence
        for i = 1, #str do -- loop through each character in the input string
            local c = str:sub(i, i) -- get the current character
            if c == "!" or c == "?" or c == "." then--or i==#str then -- check if the current character is a punctuation mark
                local sentence = str:sub(start, i) -- get the current sentence from the start index to the current index
                table.insert(sentences, sentence) -- insert the current sentence into the table
                start = i + 1 -- update the start index to the next character after the punctuation mark
            end
        end
        if sentences[1]==nil then
            return nil
        end
        sentences=nil
        return true -- return the table of sentences
    end
    local importance=""

    local function processSentences(user,response)
        -- find keyword, phrase
        local function replyRandomly()
            response = ""--randReplies[math.random(#randReplies)]..
        end
        local function processInput()

            for keyword, reply in pairs(replies) do
                local d, e = string.find(user, keyword, 1, 1)
                if d then
                    -- process keywords
                    local chr=reply[cm.mathrandom(1,#reply)]
                    response = response..""..chr.." "
                    if string.byte(string.sub(chr, -1)) < 65 then -- "A"
                        response = response..""; return
                    end
                    local h = string.len(user) - (d + string.len(keyword))
                    if h > 0 then
                        user = string.sub(user, -h)
                        importance=user 
                    end
                    for cFrom, cTo in pairs(conjugate) do
                        local f, g = string.find(user, cFrom, 1, 1)
                        if f then
                            local j = string.sub(user, 1, f - 1).." "..cTo
                            local z = string.len(user) - (f - 1) - string.len(cTo)
                            response = response..j..""
                            importance=j
                            if z > 2 then
                                -- local l = string.sub(user, -(z - 2))
                                -- print(l)
                                local l=""
                                if not string.find(userOrig, l) then return end
                            end
                            if z > 2 then local c= string.sub(user, -(z - 2)) response = response..c.."" importance=c end
                            if z < 2 then response = response.."" end
                            return 
                        end--if f
                    end--for
                    importance=user
                    response = response..user
                    return response,importance
                end--if d
            end--for
            replyRandomly()
            return response,importance
        end

        -- main()
        -- accept user input
        if string.sub(user, 1, 3) == "bye" then
            response = "Bye, bye for now.See you again some time."
            return response--,user
        end
        if string.sub(user, 1, 7) == "because" then
            user = string.sub(user, 8)
        end
        user = " "..user.." "
        -- process input, print reply
        response,importance= processInput()
        response = response..""
        return response,importance
    end
    local responsec=""
    local responses={}
    local called=false
    --  Result1=cm.APIResponse(user,"greetings",context,responses,persona,player,responsec)
    local imp=""
    for i,v in pairs(createSentences(user)) do
        local response=nil
        local answ,imp2=processSentences(v," ")
        if responses[answ]==nil then
            --   print(responses)
            responses[answ]=1
            if imp2 ~="" then
                imp=imp..imp2
            end
      
                responsec=responsec..answ 
      
        end
    end

    if responsec:gmatch("%w+") and responsec~="  "   then
        return responsec,imp
    else return nil,nil   
    end   
end
1 Like

You never fail to amaze the community! And you’re open sourcing a lot of these projects too which I love!

2 Likes

Thank you! I’m glad to share some resources on what I’m working on. It’s only parts of the project that I find to be novel algorithms that I share. I try not to share too much data for the chatbots. Since that’s the real magic. :wink: I updated the chatmodule resource and removes its dependencies. If you wish to test out my chatbot you can try it out here.
Kingdom Hearts: Dragon Quarter - Roblox
It’s in a pretty good now. They have a finely tuned algorithm. I plan to improve the wikipedia search capabilities with this function.

1 Like

Here is a neat tool I made with this Chatbot a Dynamic and Efficient Music player that can play tracks or playlist from the catalogue!
DJ Eliza - Luau Chatbot Dynamic Music Player/(Action Based Chatbot Template) - Resources / Community Resources - Developer Forum | Roblox