123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- // Author: Matthew Shiel
- // Code adapted from https://github.com/kennysong/goeliza/
- package eliza
- import (
- "bytes"
- "fmt"
- "html/template"
- "math/rand"
- "regexp"
- "strings"
- "time"
- )
- // InteractiveBot This is the interface to comunicate with the bot
- type InteractiveBot interface {
- ReplyTo(statement string) (string, *Personality)
- }
- // Chatbot Defines data to be used by a chatbot personality - essentially
- // Its personality script and execution context
- type Chatbot struct {
- Personality *Personality
- Context *ChatbotContext
- }
- // ChatbotContext is the white board structure
- type ChatbotContext struct {
- EngineVersion string
- Session SessionData
- }
- // ChatbotInteraction defines a individual question/answer interaction with a caller
- type ChatbotInteraction struct {
- Time string `json:"time,omitempty" yaml:"time,omitempty"`
- Question string `json:"question,omitempty" yaml:"question,omitempty"`
- Answer string `json:"answer,omitempty" yaml:"answer,omitempty"`
- }
- // SessionData defines information about the current bot and its interaction within a specific session
- type SessionData struct {
- SessionID string `json:"sessionID" yaml:"sessionID"`
- StartTime string `json:"startTime,omitempty" yaml:"startTime,omitempty"`
- User string `json:"user,omitempty" yaml:"user,omitempty"`
- Bot string `json:"bot,omitempty" yaml:"bot,omitempty"`
- BotVersion string `json:"botVersion,omitempty" yaml:"botVersion,omitempty"`
- Conversation []ChatbotInteraction `json:"conversation,omitempty" yaml:"conversation,omitempty"`
- }
- // NewBotPersonality is a utility method to create a bot instance from a personality and its context
- func NewBotPersonality(personality *Personality, context *ChatbotContext) *Chatbot {
- return &Chatbot{personality, context}
- }
- // Greetings will return a random introductory sentence for ELIZA.
- func (p *Chatbot) Greetings() string {
- return p.randChoice(p.Personality.Introductions)
- }
- // GoodbyeResponse will return a random goodbye sentence for ELIZA.
- func (p *Chatbot) GoodbyeResponse() string {
- return p.randChoice(p.Personality.Goodbyes)
- }
- // ReplyTo will construct a reply for a given statement using ELIZA's rules.
- func (p *Chatbot) ReplyTo(statement string) string {
- // First, preprocess the statement for more effective matching
- statement = p.preprocess(statement)
- // Then, we check if this is a quit statement
- if p.IsQuitStatement(statement) {
- return p.GoodbyeResponse()
- }
- // Next, we try to match the statement to a statement that ELIZA can
- // recognize, and construct a pre-determined, appropriate response.
- for _, similarQuestionResponse := range p.Personality.Psychobabble {
- for _, question := range similarQuestionResponse.SimilarQuestions {
- re := regexp.MustCompile(question)
- matches := re.FindStringSubmatch(statement)
- // If the statement matched any recognizable statements.
- if len(matches) > 0 {
- // If we matched a regex group in parentheses, get the first match.
- // The matched regex group will match a "fragment" that will form
- // part of the response, for added realism.
- var fragment string
- if len(matches) > 1 {
- fragment = p.reflect(matches[1])
- }
- // Choose a random appropriate response, and format it with the
- // fragment, if needed.
- response := p.randChoice(similarQuestionResponse.Responses)
- if strings.Contains(response, "%s") {
- response = fmt.Sprintf(response, fragment)
- }
- // fmt.Printf("For Statement \"%s\" got a hit with pattern \"%s\" Responded With \"%s\"\n", statement, pattern, response)
- return p.replacePlaceHolders(response)
- }
- }
- }
- // If no patterns were matched, return a default response.
- return p.replacePlaceHolders(p.randChoice(p.Personality.DefaultResponses))
- }
- // IsQuitStatement returns if the statement is a quit statement
- func (p *Chatbot) IsQuitStatement(statement string) bool {
- statement = p.preprocess(statement)
- for _, quitResponse := range p.Personality.QuitResponses {
- if statement == quitResponse {
- return true
- }
- }
- return false
- }
- // preprocess will do some normalization on a statement for better regex matching
- func (p *Chatbot) preprocess(statement string) string {
- statement = strings.TrimRight(statement, "\n.!")
- statement = strings.ToLower(statement)
- return statement
- }
- // reflect flips a few words in an input fragment (such as "I" -> "you").
- func (p *Chatbot) reflect(fragment string) string {
- words := strings.Split(fragment, " ")
- for i, word := range words {
- if reflectedWord, ok := p.Personality.ReflectedWords[word]; ok {
- words[i] = reflectedWord
- }
- }
- return strings.Join(words, " ")
- }
- // randChoice returns a random element in an (string) array.
- func (p *Chatbot) randChoice(list []string) string {
- // Added for truly random generation of numbers with seeds
- if len(list) == 0 {
- return ""
- }
- rand.Seed(time.Now().UnixNano())
- randIndex := rand.Intn(len(list))
- return list[randIndex]
- }
- /**
- * Replaces conversation templates (names etc) in reply with
- */
- func (p *Chatbot) replacePlaceHolders(answer string) string {
- var tBuffer bytes.Buffer
- t := template.Must(template.New("answer").Parse(answer))
- t.Execute(&tBuffer, p.Context)
- return tBuffer.String()
- }
|