|
@@ -13,65 +13,30 @@ import (
|
|
|
"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"`
|
|
|
- PatternGroup string `json:"patternGroup,omitempty" yaml:"patternGroup,omitempty"`
|
|
|
- Pattern string `json:"pattern,omitempty" yaml:"pattern,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}
|
|
|
|
|
|
}
|
|
|
|
|
|
-func NewChatbotInteraction(question, patterngroup, answer string) ChatbotInteraction {
|
|
|
- return ChatbotInteraction{Time: time.Now().UTC().String(), Question: question, PatternGroup: patterngroup, Answer: answer}
|
|
|
+func NewChatbotInteraction(question, patterngroup, rawanswer, answer string) ChatbotInteraction {
|
|
|
+ return ChatbotInteraction{Time: time.Now().UTC().String(), Question: question, PatternGroup: patterngroup, RawAnswer: rawanswer, Answer: answer}
|
|
|
}
|
|
|
|
|
|
-func NewChatbotInteractionWithPattern(question, patterngroup, pattern, answer string) ChatbotInteraction {
|
|
|
- return ChatbotInteraction{Time: time.Now().UTC().String(), Question: question, PatternGroup: patterngroup, Pattern: pattern, Answer: answer}
|
|
|
+func NewChatbotInteractionWithPattern(question, patterngroup, pattern, rawanswer, answer string) ChatbotInteraction {
|
|
|
+ return ChatbotInteraction{Time: time.Now().UTC().String(), Question: question, PatternGroup: patterngroup, Pattern: pattern, RawAnswer: rawanswer, Answer: answer}
|
|
|
}
|
|
|
|
|
|
// ReplyTo will construct a reply for a given statement using ELIZA's rules.
|
|
|
func (p *Chatbot) ReplyTo(statement string) ChatbotInteraction {
|
|
|
// First, preprocess the statement for more effective matching
|
|
|
statement = p.preprocess(statement)
|
|
|
+ var rawResponse string
|
|
|
|
|
|
// Then, we check if this is a quit statement
|
|
|
if p.IsQuitStatement(statement) {
|
|
|
- return NewChatbotInteraction(statement, "QuitResponses", p.GoodbyeResponse())
|
|
|
+ rawResponse = p.GoodbyeResponse()
|
|
|
+ return NewChatbotInteraction(statement, "QuitResponses", rawResponse, rawResponse)
|
|
|
}
|
|
|
|
|
|
// Next, we try to match the statement to a statement that ELIZA can
|
|
@@ -97,13 +62,14 @@ func (p *Chatbot) ReplyTo(statement string) ChatbotInteraction {
|
|
|
response = fmt.Sprintf(response, fragment)
|
|
|
}
|
|
|
// fmt.Printf("For Statement \"%s\" got a hit with pattern \"%s\" Responded With \"%s\"\n", statement, pattern, response)
|
|
|
- return NewChatbotInteractionWithPattern(statement, "Psychobabble", questionPattern, p.replacePlaceHolders(response))
|
|
|
+ return NewChatbotInteractionWithPattern(statement, "Psychobabble", questionPattern, response, p.replacePlaceHolders(response))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If no patterns were matched, return a default response.
|
|
|
- return NewChatbotInteraction(statement, "DefaultResponses", p.replacePlaceHolders(p.randChoice(p.Personality.DefaultResponses)))
|
|
|
+ rawResponse = p.randChoice(p.Personality.DefaultResponses)
|
|
|
+ return NewChatbotInteraction(statement, "DefaultResponses", rawResponse, p.replacePlaceHolders(rawResponse))
|
|
|
}
|
|
|
|
|
|
// Greetings will return a random introductory sentence for ELIZA.
|
|
@@ -152,8 +118,29 @@ func (p *Chatbot) randChoice(list []string) string {
|
|
|
return ""
|
|
|
}
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
- randIndex := rand.Intn(len(list))
|
|
|
- return list[randIndex]
|
|
|
+ maxAttempts := 2 * len(list)
|
|
|
+ // try looking for an unspoken response at random which has not been used recently
|
|
|
+ // 1st hash previous responses
|
|
|
+ previousResponseHash := make(map[uint32]bool)
|
|
|
+ for _, r := range p.Context.Session.Conversation {
|
|
|
+ previousResponseHash[hash(r.RawAnswer)] = true
|
|
|
+ }
|
|
|
+
|
|
|
+ var candidate string
|
|
|
+ for i := 1; i <= maxAttempts; i++ {
|
|
|
+
|
|
|
+ randIndex := rand.Intn(len(list))
|
|
|
+ candidate = list[randIndex]
|
|
|
+ h := hash(candidate)
|
|
|
+ _, known := previousResponseHash[h]
|
|
|
+ // if not known - use it
|
|
|
+ // fmt.Println(randIndex, known, candidate)
|
|
|
+ if !known {
|
|
|
+ return candidate
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if here then use default
|
|
|
+ return candidate
|
|
|
}
|
|
|
|
|
|
/**
|