Эх сурвалжийг харах

Eliza-010 Distributed Redis cache

gremlin 4 жил өмнө
parent
commit
5f896929c9

+ 4 - 0
bots/eliza.json

@@ -1,5 +1,9 @@
 {
     "name": "Eliza",
+    "version": "v0.0.1",
+    "commands": {
+       "version":[""]
+    },
     "introductions": [
         "Hello, How are you feeling today?",
         "How do you do. Are you seeking help today?",

+ 4 - 0
bots/ivanka.json

@@ -1,5 +1,9 @@
 {
     "name": "Ivanka",
+    "version": "v0.0.1",
+    "commands": {
+       "version":[""]
+    },
     "introductions": [
         "Hello, How are you feeling today?",
         "How do you do. Are you seeking help today?",

+ 2 - 0
eliza/personality.go

@@ -2,6 +2,8 @@ package eliza
 
 type Personality struct {
 	Name             string              `json:"name" yaml:"name"`
+	Version          string              `json:"version,omitempty" yaml:"version,omitempty"`
+	Commands         map[string][]string `json:"commands,omitempty" yaml:"commands,omitempty"`
 	Introductions    []string            `json:"introductions,omitempty" yaml:"introductions,omitempty"`
 	Goodbyes         []string            `json:"goodbyes,omitempty" yaml:"goodbyes,omitempty"`
 	Psychobabble     map[string][]string `json:"psychobabble,omitempty" yaml:"psychobabble,omitempty"`

+ 90 - 0
facades/cache/cache.go

@@ -0,0 +1,90 @@
+package cache
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/shomali11/xredis"
+)
+
+type BotCache interface {
+	Store(key, data string, ttl int) (err error)
+	Retrieve(key string, remove bool) (value string, found bool, err error)
+	Remove(key string) (err error)
+}
+
+type RedisCache struct {
+	cacheManager *xredis.Client
+	defaultTTL   int
+	databaseId   int
+}
+
+func NewRedisCache(redisCondig string) RedisCache {
+
+	// Need to split based on :
+	hostParts := strings.Split(redisCondig, ":")
+	port := 6379
+	if len(hostParts) > 1 {
+		// port is second param
+		var err error
+		if port, err = strconv.Atoi(hostParts[1]); err != nil {
+			fmt.Println("Defaulting to redis port ", port)
+		}
+	}
+	databaseId := 1 // default other than 0
+	if len(hostParts) > 2 {
+		// redis db id is third param
+		var err error
+		if databaseId, err = strconv.Atoi(hostParts[2]); err != nil {
+			fmt.Println("Something wrong with redis dbId .. Defaulting to redis db ", databaseId)
+		}
+	}
+
+	options := &xredis.Options{
+		Host:     hostParts[0],
+		Port:     port,
+		Database: databaseId,
+	}
+
+	// Default to an hour
+	defaultTTL := 3600
+
+	c := RedisCache{xredis.SetupClient(options), defaultTTL, databaseId}
+	//	defer c.cacheManager.Close()
+
+	return c
+}
+
+/**
+We always assume data is a strings*/
+
+func (c RedisCache) Store(key, value string, ttl int) (err error) {
+	_, err = c.cacheManager.Set(key, value)
+	if err != nil {
+		return err
+	}
+	// Set TTL - default an hour
+	_, err = c.cacheManager.Expire(key, int64(ttl))
+	return err
+}
+
+func (c RedisCache) Retrieve(key string, delete bool) (value string, found bool, err error) {
+	found = false
+	value, got, reterr := c.cacheManager.Get(key)
+	if !got || reterr != nil {
+		err = errors.New("Cache Key Not Found")
+		return
+	}
+	found = true
+	if delete {
+		c.Remove(key)
+	}
+	return
+}
+
+func (c RedisCache) Remove(key string) error {
+	_, err := c.cacheManager.Del(key)
+	return err
+}

+ 1 - 0
go.mod

@@ -6,6 +6,7 @@ require (
 	github.com/gofrs/uuid v4.0.0+incompatible
 	github.com/jaffee/commandeer v0.5.0
 	github.com/labstack/echo/v4 v4.2.0
+	github.com/shomali11/xredis v0.0.0-20190608143638-0b54a6bbf40b
 	github.com/stretchr/testify v1.5.1 // indirect
 	gopkg.in/yaml.v2 v2.2.7 // indirect
 )

+ 9 - 0
go.sum

@@ -1,5 +1,7 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/FZambia/go-sentinel v0.0.0-20171204085413-76bd05e8e22f h1:Cw8+PWqu3OTXtFUPb6TzFTbYUXrd2EYSM4ZMNwHpvvQ=
+github.com/FZambia/go-sentinel v0.0.0-20171204085413-76bd05e8e22f/go.mod h1:Gmudsni9xSECr+W+WXj5+LydMIQ1sVJ69gVswhqFbAc=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -20,6 +22,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
+github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -82,7 +86,11 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rafaeljusto/redigomock v0.0.0-20170720131524-7ae0511314e9 h1:AgFSzGRVSy1kZ8EBHycQc6qK9gVqhJnVI2H/dk2cY/Y=
+github.com/rafaeljusto/redigomock v0.0.0-20170720131524-7ae0511314e9/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/shomali11/xredis v0.0.0-20190608143638-0b54a6bbf40b h1:sh9o9VVQC1aobDUjJehl9Iis8NoKa6gfxZwjHWEUmdg=
+github.com/shomali11/xredis v0.0.0-20190608143638-0b54a6bbf40b/go.mod h1:hp9t41AjbaTDmT3iT6CXSI1r8nbDiSuQ9Mgco91lfhY=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -95,6 +103,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=

+ 70 - 14
server.go → main.go

@@ -10,8 +10,10 @@ import (
 	"log"
 	"net/http"
 	"strings"
+	"time"
 
 	"git.riomhaire.com/gremlin/elizaservice/eliza"
+	"git.riomhaire.com/gremlin/elizaservice/facades/cache"
 	"github.com/gofrs/uuid"
 	"github.com/jaffee/commandeer"
 
@@ -28,12 +30,34 @@ var templates embed.FS
 //go:embed bots/*
 var bots embed.FS
 
+var cacheManager cache.BotCache
+var cacheTTL int
+
+type BotInteraction 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"`
+}
+
+/***********************************************************************************************
+* This is stored as JSON in the cache (redis)
+ ***********************************************************************************************/
+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"`
+	Conversation []BotInteraction `json:"conversation,omitempty" yaml:"conversation,omitempty"`
+}
+
 /***********************************************************************************************
 *
  ***********************************************************************************************/
 type Main struct {
-	BotName string `help:"What This Bot is Called."`
-	Port    int    `help:"What port bot is listening too"`
+	BotName  string `help:"What This Bot is Called."`
+	Port     int    `help:"What port bot is listening too"`
+	Cache    string `help:"What Cache configuration to use - redis format <host>:<port>:<db>"`
+	CacheTTL int    `help:"How long cache is allowed to store data in seconds"`
 }
 
 /***********************************************************************************************
@@ -41,11 +65,37 @@ type Main struct {
  ***********************************************************************************************/
 func NewMain() *Main {
 	return &Main{
-		BotName: "Eliza",
-		Port:    8070,
+		BotName:  "Eliza",
+		Port:     8070,
+		Cache:    "empire:6379:1",
+		CacheTTL: 3600,
 	}
 }
 
+/********************************
+This appends to session in cache latest info
+*********************************/
+func addToConversation(sessionID, bot, user, question, answer string) {
+	sessionData := SessionData{}
+	// Lookup session
+	jsonValue, found, _ := cacheManager.Retrieve(sessionID, false)
+
+	if !found {
+		sessionData = SessionData{SessionID: sessionID, Bot: bot, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]BotInteraction, 0)}
+	}
+	if found {
+		if err := json.Unmarshal([]byte(jsonValue), &sessionData); err != nil {
+			sessionData = SessionData{SessionID: sessionID, Bot: bot, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]BotInteraction, 0)}
+		}
+	}
+	// OK addToConversation
+	botInteraction := BotInteraction{time.Now().UTC().String(), question, answer}
+	sessionData.Conversation = append(sessionData.Conversation, botInteraction)
+	// Save it as json
+	j, _ := json.Marshal(sessionData)
+	cacheManager.Store(sessionID, string(j), cacheTTL)
+}
+
 /***********************************************************************************************
 * This at the moment ... but it could be one passed in via LTI if available
  ***********************************************************************************************/
@@ -86,6 +136,7 @@ func findPersonality(name string) (personality eliza.Personality, err error) {
  ***********************************************************************************************/
 func chantboInteractiontEndpoint(c echo.Context) error {
 	// Extract question from GET request
+	user := c.QueryParam("user")
 	question := c.QueryParam("value")
 	botname := c.QueryParam("bot")
 	sessionID := c.QueryParam("session")
@@ -104,7 +155,11 @@ func chantboInteractiontEndpoint(c echo.Context) error {
 	character := eliza.NewBotPersonality(&personality)
 	answer := character.ReplyTo(question)
 
-	fmt.Println("Session ID [", sessionID, "] Question [", question, "] Answer [", answer, "]")
+	msg := fmt.Sprintf("Bot [%s %s] Session ID [%s] Question [%s] Answer [%s]", botname, personality.Version, sessionID, question, answer)
+	fmt.Println(msg)
+
+	// update cache
+	addToConversation(sessionID, botname, user, question, answer)
 
 	// Return Eliza's answer
 	return c.String(http.StatusOK, answer)
@@ -157,15 +212,16 @@ func chantbotEndpoint(c echo.Context) error {
 func (m *Main) Run() error {
 
 	fmt.Println(`
-####### #         ###   #######    #
-#       #          #         #    # #
-#       #          #        #    #   #
-#####   #          #       #    #     #
-#       #          #      #     #######
-#       #          #     #      #     #
-####### #######   ###   ####### #     #
-	`)
-	fmt.Println()
+███████╗██╗     ██╗███████╗ █████╗ 
+██╔════╝██║     ██║╚══███╔╝██╔══██╗
+█████╗  ██║     ██║  ███╔╝ ███████║
+██╔══╝  ██║     ██║ ███╔╝  ██╔══██║
+███████╗███████╗██║███████╗██║  ██║
+╚══════╝╚══════╝╚═╝╚══════╝╚═╝  ╚═╝`)
+
+	// Create Cache Manager (redis)
+	cacheManager = cache.NewRedisCache(m.Cache)
+	cacheTTL = m.CacheTTL
 
 	e := echo.New()
 	e.Use(middleware.CORS())

+ 1 - 0
templates/eliza.html

@@ -37,6 +37,7 @@
                     <input type="text" class="form-control" id="user-input" aria-describedby="user-input" placeholder="Talk to {{.Bot}}">
                     <input type="hidden" class="form-control" id="bot-name" aria-describedby="bot-name" value="{{.Bot}}">
                     <input type="hidden" class="form-control" id="session-id" aria-describedby="session-id" value="{{.SessionID}}">
+                    <input type="hidden" class="form-control" id="user-name" aria-describedby="user-name" value="{{.GivenName}}">
                 </div>
                 
                 <div class="credits">Based on excellent work of https://github.com/mattshiel/eliza-go</div>

+ 2 - 0
templates/ivanka.html

@@ -38,6 +38,8 @@
           <input type="text" class="form-control" id="user-input" aria-describedby="user-input" placeholder="Talk to {{.Bot}}">
           <input type="hidden" class="form-control" id="bot-name" aria-describedby="bot-name" value="{{.Bot}}">
           <input type="hidden" class="form-control" id="session-id" aria-describedby="session-id" value="{{.SessionID}}">
+          <input type="hidden" class="form-control" id="user-name" aria-describedby="user-name" value="{{.GivenName}}">
+
         </div>
         <div class="credits">Based on excellent work of https://github.com/mattshiel/eliza-go</div>
 

+ 3 - 2
web/script.js

@@ -7,7 +7,7 @@ $("#user-input-form").submit(
         var question = $('#user-input').val().trim() // Remove whitespace
         var bot = $('#bot-name').val().trim() // Remove whitespace
         var session = $('#session-id').val().trim() // Remove whitespace
-
+        var user = $('#user-name').val().trim() // Remove whitespace
 
         // Clear the input box
         $('#user-input').val("");
@@ -29,7 +29,8 @@ $("#user-input-form").submit(
         $.get('/user-input', {
                 value: question,
                 bot: bot,
-                session: session
+                session: session,
+                user: user
             })
             .done(function (data) {
                 // Set a timeout to make Eliza seem like she's thinking