diff --git a/neo/command/command.go b/neo/command/command.go index b9149174..59cafc85 100644 --- a/neo/command/command.go +++ b/neo/command/command.go @@ -17,6 +17,23 @@ func SetStore(store Store) { DefaultStore = store } +// Match the command from the content +func Match(sid string, query driver.Query, input string) (string, error) { + + if DefaultStore == nil { + return "", fmt.Errorf("command store is not set") + } + + // Check the command from the store + if id, cid, has := DefaultStore.GetRequest(sid); has { + fmt.Println("Match Requst:", id) + return cid, nil + } + + return DefaultStore.Match(query, input) +} + +// save the command to the store func (cmd *Command) save() error { if DefaultStore == nil { return nil diff --git a/neo/command/driver/memory_test.go b/neo/command/driver/memory_test.go index b1bd3f2a..ec57e053 100644 --- a/neo/command/driver/memory_test.go +++ b/neo/command/driver/memory_test.go @@ -51,13 +51,13 @@ func TestMemoryMatch(t *testing.T) { defer test.Clean() mem := prepare(t) - id, err := mem.Match(Query{}, "Generate table test data") + id, err := mem.Match(Query{Stack: "Table.Page.pet"}, "Generate table test data") if err != nil { t.Fatal(err) } assert.Equal(t, "table.data", id) - id, err = mem.Match(Query{Stack: "Form"}, "Generate table test data") + id, err = mem.Match(Query{Stack: "Form.Page.pet", Path: "/Form/pet"}, "Generate table test data") assert.ErrorContains(t, err, "no related command found") } @@ -72,7 +72,7 @@ func prepare(t *testing.T) *Memory { Name: "Generate test data for the table", Description: "Generate test data for the table", Stack: "Table.*", - Path: "*", + Path: "Table.*", Args: []map[string]interface{}{ { "name": "data", diff --git a/neo/command/driver/query.go b/neo/command/driver/query.go index 19817a6f..039c738d 100644 --- a/neo/command/driver/query.go +++ b/neo/command/driver/query.go @@ -8,39 +8,40 @@ import ( // MatchStack match the stack func (query Query) MatchStack(stack string) bool { - if query.Stack == "" || query.Stack == "*" || stack == "" { + if stack == "" || stack == "*" || query.Stack == "" { return true } - if query.Stack == stack { + if stack == query.Stack { return true } - matched, _ := regexp.MatchString(strings.ReplaceAll(query.Stack, "*", ".*"), stack) + matched, _ := regexp.MatchString(strings.ReplaceAll(stack, "*", ".*"), query.Stack) return matched } // MatchPath match the path func (query Query) MatchPath(path string) bool { - if query.Path == "" || query.Path == "*" || path == "" { + if path == "" || path == "*" || query.Path == "" { return true } - if query.Path == path { + if path == query.Path { return true } - matched, _ := regexp.MatchString(strings.ReplaceAll(query.Path, "*", ".*"), path) + matched, _ := regexp.MatchString(strings.ReplaceAll(path, "*", ".*"), query.Path) return matched } // MatchAny match the stack or path func (query Query) MatchAny(stack, path string) bool { - if query.Path == "" || query.Path == "-" { + + if path == "" || path == "-" { return query.MatchStack(stack) } - if query.Stack == "" || query.Stack == "-" { + if stack == "" || stack == "-" { return query.MatchPath(path) } diff --git a/neo/command/request.go b/neo/command/request.go index 9647f1af..aad045af 100644 --- a/neo/command/request.go +++ b/neo/command/request.go @@ -1,84 +1,76 @@ package command import ( - "context" "fmt" - "sync" + "time" "github.com/google/uuid" - "github.com/yaoapp/kun/exception" ) -var requests = sync.Map{} +func output(format string, args ...interface{}) []byte { + content := fmt.Sprintf(format, args...) + return []byte(fmt.Sprintf(`{"id":"chatcmpl-7Atx502nGBuYcvoZfIaWU4FREI1mT","object":"chat.completion.chunk","created":1682832715,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"%s"},"index":0,"finish_reason":null}]}`, content)) +} // Run the command -func (req *Request) Run(cb func(data []byte) int) (interface{}, error) { +func (req *Request) Run(messages []map[string]interface{}, cb func(data []byte) int) (interface{}, error) { + + cb(output("- Command: %s\\n", req.Command.ID)) + time.Sleep(200 * time.Millisecond) + + cb(output("- Session: %s\\n", req.sid)) + time.Sleep(200 * time.Millisecond) + + cb(output("- Request: %s\\n", req.id)) + time.Sleep(200 * time.Millisecond) + + cb([]byte(`[DONE]`)) return nil, nil } // NewRequest create a new request -func (cmd *Command) NewRequest(ctx Context, messages []map[string]interface{}) (*Request, error) { +func (cmd *Command) NewRequest(ctx Context) (*Request, error) { - v, ok := requests.Load(ctx.Sid) - if !ok { - v = map[string]string{ - "id": uuid.New().String(), - "cmd": cmd.ID, + if DefaultStore == nil { + return nil, fmt.Errorf("command store is not set") + } + + if ctx.Sid == "" { + return nil, fmt.Errorf("context sid is request") + } + + // continue the request + id, cid, has := DefaultStore.GetRequest(ctx.Sid) + if has { + if cid != cmd.ID { + return nil, fmt.Errorf("request id is not match") } + return &Request{ + Command: cmd, + sid: ctx.Sid, + id: id, + ctx: ctx, + }, nil } - req, ok := v.(map[string]string) - if !ok { - return nil, fmt.Errorf("request id is not string") - } - - if req["id"] == "" { - return nil, fmt.Errorf("request id is request") - } - - if req["cmd"] != cmd.ID { - defer requests.Delete(ctx.Sid) - return nil, fmt.Errorf("request id is not match") + // create a new request + id = uuid.New().String() + err := DefaultStore.SetRequest(ctx.Sid, id, cmd.ID) + if err != nil { + return nil, err } return &Request{ - Command: cmd, - messages: messages, - sid: ctx.Sid, - id: req["id"], - ctx: ctx, + Command: cmd, + sid: ctx.Sid, + id: id, + ctx: ctx, }, nil } // Done the request done func (req *Request) Done() { - requests.Delete(req.sid) -} - -// prepare the command -func (req *Request) prepare(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (int, *exception.Exception) { - return 1, nil -} - -// before the process -func (req *Request) before(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { - return nil, nil -} - -// after the process -func (req *Request) after(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { - return nil, nil -} - -// run the process -func (req *Request) process(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { - return nil, nil -} - -func (req *Request) saveConversation(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { - return nil, nil -} - -func (req *Request) saveData(ctx context.Context, data []map[string]interface{}, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { - return nil, nil + if DefaultStore == nil { + DefaultStore.DelRequest(req.sid) + } } diff --git a/neo/command/types.go b/neo/command/types.go index 1a9cfcbe..1a415430 100644 --- a/neo/command/types.go +++ b/neo/command/types.go @@ -9,10 +9,9 @@ import ( // Request the command request type Request struct { - id string - sid string - ctx Context - messages []map[string]interface{} + id string + sid string + ctx Context *Command } @@ -66,7 +65,7 @@ type Optional struct { type Context struct { Sid string `json:"-" yaml:"-"` Stack string `json:"stack,omitempty"` - Path string `json:"path,omitempty"` + Path string `json:"pathname,omitempty"` context.Context `json:"-" yaml:"-"` } diff --git a/neo/load.go b/neo/load.go index 7f9679d5..5742e12c 100644 --- a/neo/load.go +++ b/neo/load.go @@ -4,8 +4,11 @@ import ( "path/filepath" "github.com/yaoapp/gou/application" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/aigc" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/neo/command" + "github.com/yaoapp/yao/neo/command/driver" "github.com/yaoapp/yao/neo/conversation" ) @@ -41,18 +44,31 @@ func Load(cfg config.Config) error { } Neo = &setting + + // AI Setting err = Neo.newAI() if err != nil { return err } + // Conversation Setting err = Neo.newConversation() if err != nil { return err } + // Command Setting + store, err := driver.NewMemory("gpt-3_5-turbo", nil) + if err != nil { + return err + } + command.SetStore(store) + + // Load the commands + err = command.Load(cfg) + if err != nil { + log.Error("Command Load Error: %s", err.Error()) + } + return nil } - -// LoadCommands load the commands -func (neo *DSL) LoadCommands() {} diff --git a/neo/neo.go b/neo/neo.go index 8b34caca..ff12bd62 100644 --- a/neo/neo.go +++ b/neo/neo.go @@ -15,6 +15,7 @@ import ( "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/neo/command" + "github.com/yaoapp/yao/neo/command/driver" "github.com/yaoapp/yao/neo/conversation" "github.com/yaoapp/yao/openai" ) @@ -66,7 +67,7 @@ func (neo *DSL) API(router *gin.Engine, path string) error { // utils.Dump(messages) // set the context - ctx, cancel := command.NewContextWithCancel(sid, c.GetString("context")) + ctx, cancel := command.NewContextWithCancel(sid, c.Query("context")) defer cancel() err = neo.Answer(ctx, c, messages) @@ -87,9 +88,13 @@ func (neo *DSL) Answer(ctx command.Context, answer Answer, messages []map[string chanError := make(chan error, 1) // check the command - // cmd, isCommand := neo.Command.Match(ctx, messages) - isCommand := false - cmd := command.Command{} + var cmd *command.Command + var isCommand = false + input := messages[len(messages)-1]["content"].(string) + name, err := command.Match(ctx.Sid, driver.Query{Stack: ctx.Stack, Path: ctx.Path}, input) + if err == nil && name != "" { + cmd, isCommand = command.Commands[name] + } go func() { defer func() { @@ -100,13 +105,13 @@ func (neo *DSL) Answer(ctx command.Context, answer Answer, messages []map[string // execute the command if isCommand { - req, err := cmd.NewRequest(ctx, messages) + req, err := cmd.NewRequest(ctx) if err != nil { chanError <- err return } - _, err = req.Run(func(data []byte) int { + _, err = req.Run(messages, func(data []byte) int { chanStream <- data return 1 }) @@ -114,6 +119,7 @@ func (neo *DSL) Answer(ctx command.Context, answer Answer, messages []map[string if err != nil { chanError <- err } + return } diff --git a/neo/neo_test.go b/neo/neo_test.go index 3daffa65..d992e539 100644 --- a/neo/neo_test.go +++ b/neo/neo_test.go @@ -15,6 +15,7 @@ import ( httpTest "github.com/yaoapp/gou/http" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/neo/command" "github.com/yaoapp/yao/test" _ "github.com/yaoapp/yao/utils" ) @@ -104,11 +105,18 @@ func testServer(t *testing.T, router *gin.Engine) (string, func()) { func testRouter(t *testing.T) *gin.Engine { + // Load Config err := Load(config.Conf) if err != nil { t.Fatal(err) } + // Load Commands + err = command.Load(config.Conf) + // if err != nil { + // t.Fatal(err) + // } + router := gin.New() gin.SetMode(gin.ReleaseMode) return router diff --git a/neo/types.go b/neo/types.go index c5ddaf20..4ab97d74 100644 --- a/neo/types.go +++ b/neo/types.go @@ -4,7 +4,6 @@ import ( "io" "github.com/yaoapp/yao/aigc" - "github.com/yaoapp/yao/neo/command" "github.com/yaoapp/yao/neo/conversation" ) @@ -20,7 +19,6 @@ type DSL struct { Allows []string `json:"allows,omitempty"` AI aigc.AI `json:"-" yaml:"-"` Conversation Conversation `json:"-" yaml:"-"` - Command Command `json:"-" yaml:"-"` } // Conversation the store interface @@ -29,11 +27,6 @@ type Conversation interface { SaveHistory(sid string, messages []map[string]interface{}) error } -// Command the command interface -type Command interface { - Match(ctx command.Context, messages []map[string]interface{}) (*command.Command, bool) -} - // Answer the answer interface type Answer interface { GetString(key string) (s string)