[add] neo commands ( 50% )
This commit is contained in:
parent
04ca2aebdc
commit
1f51bdaf8f
16 changed files with 863 additions and 16 deletions
20
aigc/load.go
20
aigc/load.go
|
|
@ -2,6 +2,7 @@ package aigc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/yaoapp/gou/application"
|
"github.com/yaoapp/gou/application"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
|
|
@ -11,15 +12,30 @@ import (
|
||||||
// Load load AIGC
|
// Load load AIGC
|
||||||
func Load(cfg config.Config) error {
|
func Load(cfg config.Config) error {
|
||||||
exts := []string{"*.ai.yml", "*.ai.yaml"}
|
exts := []string{"*.ai.yml", "*.ai.yaml"}
|
||||||
return application.App.Walk("aigcs", func(root, file string, isdir bool) error {
|
messages := []string{}
|
||||||
|
err := application.App.Walk("aigcs", func(root, file string, isdir bool) error {
|
||||||
if isdir {
|
if isdir {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
id := share.ID(root, file)
|
id := share.ID(root, file)
|
||||||
_, err := LoadFile(file, id)
|
_, err := LoadFile(file, id)
|
||||||
return err
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}, exts...)
|
}, exts...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFile load AIGC by file
|
// LoadFile load AIGC by file
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package connector
|
package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/yaoapp/gou/application"
|
"github.com/yaoapp/gou/application"
|
||||||
"github.com/yaoapp/gou/connector"
|
"github.com/yaoapp/gou/connector"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
|
|
@ -10,11 +13,24 @@ import (
|
||||||
// Load load store
|
// Load load store
|
||||||
func Load(cfg config.Config) error {
|
func Load(cfg config.Config) error {
|
||||||
exts := []string{"*.yao", "*.json", "*.jsonc"}
|
exts := []string{"*.yao", "*.json", "*.jsonc"}
|
||||||
return application.App.Walk("connectors", func(root, file string, isdir bool) error {
|
messages := []string{}
|
||||||
|
err := application.App.Walk("connectors", func(root, file string, isdir bool) error {
|
||||||
if isdir {
|
if isdir {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := connector.Load(file, share.ID(root, file))
|
_, err := connector.Load(file, share.ID(root, file))
|
||||||
return err
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}, exts...)
|
}, exts...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
neo/command/command.go
Normal file
61
neo/command/command.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/connector"
|
||||||
|
"github.com/yaoapp/yao/aigc"
|
||||||
|
"github.com/yaoapp/yao/neo/command/driver"
|
||||||
|
"github.com/yaoapp/yao/openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultStore the default store driver
|
||||||
|
var DefaultStore Store
|
||||||
|
|
||||||
|
// SetStore the driver interface
|
||||||
|
func SetStore(store Store) {
|
||||||
|
DefaultStore = store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Command) save() error {
|
||||||
|
if DefaultStore == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []map[string]interface{}{}
|
||||||
|
for _, arg := range cmd.Args {
|
||||||
|
args = append(args, map[string]interface{}{
|
||||||
|
"name": arg.Name,
|
||||||
|
"description": arg.Description,
|
||||||
|
"type": arg.Type,
|
||||||
|
"required": arg.Required,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultStore.Set(cmd.ID, driver.Command{
|
||||||
|
ID: cmd.ID,
|
||||||
|
Description: cmd.Description,
|
||||||
|
Args: args,
|
||||||
|
Stack: cmd.Stack,
|
||||||
|
Path: cmd.Path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAI create a new AI
|
||||||
|
func (cmd *Command) newAI() (aigc.AI, error) {
|
||||||
|
|
||||||
|
if cmd.Connector == "" {
|
||||||
|
return nil, fmt.Errorf("%s connector is required", cmd.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := connector.Select(cmd.Connector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.Is(connector.OPENAI) {
|
||||||
|
return openai.New(cmd.Connector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%s connector %s not support, should be a openai", cmd.ID, cmd.Connector)
|
||||||
|
}
|
||||||
49
neo/command/context.go
Normal file
49
neo/command/context.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewContext create a new context
|
||||||
|
func NewContext(sid, payload string) Context {
|
||||||
|
ctx := Context{Context: context.Background(), Sid: sid}
|
||||||
|
if payload == "" {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
err := jsoniter.Unmarshal([]byte(payload), &ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%s", err.Error())
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextWithCancel create a new context with cancel
|
||||||
|
func NewContextWithCancel(sid, payload string) (Context, context.CancelFunc) {
|
||||||
|
ctx := NewContext(sid, payload)
|
||||||
|
return ContextWithCancel(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextWithTimeout create a new context with timeout
|
||||||
|
func NewContextWithTimeout(sid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
|
||||||
|
ctx := NewContext(sid, payload)
|
||||||
|
return ContextWithTimeout(ctx, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithCancel create a new context
|
||||||
|
func ContextWithCancel(parent Context) (Context, context.CancelFunc) {
|
||||||
|
new, cancel := context.WithCancel(parent.Context)
|
||||||
|
parent.Context = new
|
||||||
|
return parent, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithTimeout create a new context
|
||||||
|
func ContextWithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {
|
||||||
|
new, cancel := context.WithTimeout(parent.Context, timeout)
|
||||||
|
parent.Context = new
|
||||||
|
return parent, cancel
|
||||||
|
}
|
||||||
191
neo/command/driver/memory.go
Normal file
191
neo/command/driver/memory.go
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou/connector"
|
||||||
|
"github.com/yaoapp/yao/aigc"
|
||||||
|
"github.com/yaoapp/yao/openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commands = sync.Map{}
|
||||||
|
var requests = sync.Map{}
|
||||||
|
|
||||||
|
// Memory the memory driver
|
||||||
|
type Memory struct {
|
||||||
|
model string
|
||||||
|
ai aigc.AI
|
||||||
|
prompts []aigc.Prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemory create a new memory driver
|
||||||
|
func NewMemory(model string, prompts []aigc.Prompt) (*Memory, error) {
|
||||||
|
|
||||||
|
if prompts == nil || len(prompts) == 0 {
|
||||||
|
prompts = []aigc.Prompt{
|
||||||
|
{
|
||||||
|
Role: "system",
|
||||||
|
Content: `
|
||||||
|
- Answer my question follow this rules:
|
||||||
|
- If it can match the "name" or "description" given to you, reply the "ID" of the matched command;
|
||||||
|
- reply the "ID" only, and do not explain your answer, and do not use punctuation.
|
||||||
|
- If no matching command is found, reply me <no related command found>. <No relevant command found>, don't answer redundantly.
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mem := &Memory{model: model, prompts: prompts}
|
||||||
|
ai, err := mem.newAI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mem.ai = ai
|
||||||
|
return mem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match match the command data
|
||||||
|
func (driver *Memory) Match(query Query, content string) (string, error) {
|
||||||
|
prompts := append([]aigc.Prompt{}, driver.prompts...)
|
||||||
|
has := false
|
||||||
|
commands.Range(func(key, value interface{}) bool {
|
||||||
|
cmd, ok := value.(Command)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if query.MatchAny(cmd.Stack, cmd.Path) {
|
||||||
|
has = true
|
||||||
|
bytes, err := jsoniter.Marshal(map[string]interface{}{
|
||||||
|
"id": cmd.ID,
|
||||||
|
"name": cmd.Name,
|
||||||
|
"description": cmd.Description,
|
||||||
|
"args": cmd.Args,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
prompts = append(prompts, aigc.Prompt{
|
||||||
|
Role: "system",
|
||||||
|
Content: string(bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
return "", fmt.Errorf("no related command found")
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []map[string]interface{}{}
|
||||||
|
for _, prompt := range prompts {
|
||||||
|
messages = append(messages, map[string]interface{}{
|
||||||
|
"role": prompt.Role,
|
||||||
|
"content": prompt.Content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, map[string]interface{}{
|
||||||
|
"role": "user",
|
||||||
|
"content": content,
|
||||||
|
})
|
||||||
|
|
||||||
|
prompts = append([]aigc.Prompt{}, driver.prompts...)
|
||||||
|
res, ex := driver.ai.ChatCompletions(messages, nil, nil)
|
||||||
|
if ex != nil {
|
||||||
|
return "", fmt.Errorf(ex.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := jsoniter.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Choices []struct{ Message struct{ Content string } }
|
||||||
|
}
|
||||||
|
err = jsoniter.Unmarshal(bytes, &data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Choices) == 0 {
|
||||||
|
return "", fmt.Errorf("no related command found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Choices[0].Message.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Set the command data
|
||||||
|
func (driver *Memory) Set(id string, cmd Command) error {
|
||||||
|
commands.Store(id, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del delete the command data
|
||||||
|
func (driver *Memory) Del(id string) {
|
||||||
|
commands.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the command data
|
||||||
|
func (driver *Memory) Get(id string) (Command, bool) {
|
||||||
|
v, ok := commands.Load(id)
|
||||||
|
if !ok {
|
||||||
|
return Command{}, false
|
||||||
|
}
|
||||||
|
cmd, ok := v.(Command)
|
||||||
|
if !ok {
|
||||||
|
return Command{}, false
|
||||||
|
}
|
||||||
|
return cmd, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequest set the command request
|
||||||
|
func (driver *Memory) SetRequest(sid, id, cid string) error {
|
||||||
|
requests.Store(sid, Request{
|
||||||
|
ID: id,
|
||||||
|
Cid: cid,
|
||||||
|
Sid: sid,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequest get the command request
|
||||||
|
func (driver *Memory) GetRequest(sid string) (string, string, bool) {
|
||||||
|
v, ok := requests.Load(sid)
|
||||||
|
if !ok {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
r, ok := v.(Request)
|
||||||
|
if !ok {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.ID, r.Cid, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelRequest delete the command request
|
||||||
|
func (driver *Memory) DelRequest(sid string) {
|
||||||
|
requests.Delete(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAI create a new AI
|
||||||
|
func (driver *Memory) newAI() (aigc.AI, error) {
|
||||||
|
|
||||||
|
if driver.model == "" {
|
||||||
|
return nil, fmt.Errorf("%s connector is required", driver.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := connector.Select(driver.model)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.Is(connector.OPENAI) {
|
||||||
|
return openai.New(driver.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("connector %s not support, should be a openai", driver.model)
|
||||||
|
}
|
||||||
88
neo/command/driver/memory_test.go
Normal file
88
neo/command/driver/memory_test.go
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemorySetGetDel(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
mem := prepare(t)
|
||||||
|
err := mem.Set("table.delete", Command{
|
||||||
|
ID: "table.delete",
|
||||||
|
Name: "Generate test data for the table",
|
||||||
|
Description: "Generate test data for the table",
|
||||||
|
Stack: "Table.*",
|
||||||
|
Path: "*",
|
||||||
|
Args: []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"type": "Array",
|
||||||
|
"description": "The data sets to generate",
|
||||||
|
"required": true,
|
||||||
|
"default": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, has := mem.Get("table.delete")
|
||||||
|
if !has {
|
||||||
|
t.Fatal("table.delete not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "table.delete", cmd.ID)
|
||||||
|
mem.Del("table.delete")
|
||||||
|
|
||||||
|
_, has = mem.Get("table.delete")
|
||||||
|
assert.False(t, has)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemoryMatch(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
mem := prepare(t)
|
||||||
|
id, err := mem.Match(Query{}, "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")
|
||||||
|
assert.ErrorContains(t, err, "no related command found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepare(t *testing.T) *Memory {
|
||||||
|
mem, err := NewMemory("gpt-3_5-turbo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.Set("table.data", Command{
|
||||||
|
ID: "table.data",
|
||||||
|
Name: "Generate test data for the table",
|
||||||
|
Description: "Generate test data for the table",
|
||||||
|
Stack: "Table.*",
|
||||||
|
Path: "*",
|
||||||
|
Args: []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"type": "Array",
|
||||||
|
"description": "The data sets to generate",
|
||||||
|
"required": true,
|
||||||
|
"default": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return mem
|
||||||
|
}
|
||||||
48
neo/command/driver/query.go
Normal file
48
neo/command/driver/query.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MatchStack match the stack
|
||||||
|
func (query Query) MatchStack(stack string) bool {
|
||||||
|
|
||||||
|
if query.Stack == "" || query.Stack == "*" || stack == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Stack == stack {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, _ := regexp.MatchString(strings.ReplaceAll(query.Stack, "*", ".*"), stack)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPath match the path
|
||||||
|
func (query Query) MatchPath(path string) bool {
|
||||||
|
if query.Path == "" || query.Path == "*" || path == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Path == path {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, _ := regexp.MatchString(strings.ReplaceAll(query.Path, "*", ".*"), path)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny match the stack or path
|
||||||
|
func (query Query) MatchAny(stack, path string) bool {
|
||||||
|
if query.Path == "" || query.Path == "-" {
|
||||||
|
return query.MatchStack(stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Stack == "" || query.Stack == "-" {
|
||||||
|
return query.MatchPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.MatchStack(stack) || query.MatchPath(path)
|
||||||
|
}
|
||||||
1
neo/command/driver/redis.go
Normal file
1
neo/command/driver/redis.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package driver
|
||||||
24
neo/command/driver/types.go
Normal file
24
neo/command/driver/types.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
// Request the command request
|
||||||
|
type Request struct {
|
||||||
|
ID string
|
||||||
|
Sid string
|
||||||
|
Cid string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command the command struct
|
||||||
|
type Command struct {
|
||||||
|
ID string `json:"-" yaml:"-"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Args []map[string]interface{} `json:"args,omitempty"`
|
||||||
|
Stack string `json:"stack,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the query struct
|
||||||
|
type Query struct {
|
||||||
|
Stack string `json:"stack,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
1
neo/command/driver/weaviate.go
Normal file
1
neo/command/driver/weaviate.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package driver
|
||||||
106
neo/command/load.go
Normal file
106
neo/command/load.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands the commands
|
||||||
|
var Commands = map[string]*Command{}
|
||||||
|
|
||||||
|
// Autopilots the autopilots
|
||||||
|
var Autopilots = []string{}
|
||||||
|
|
||||||
|
// Load load AIGC
|
||||||
|
func Load(cfg config.Config) error {
|
||||||
|
exts := []string{"*.cmd.yml", "*.cmd.yaml"}
|
||||||
|
messages := []string{}
|
||||||
|
|
||||||
|
err := application.App.Walk("neo", func(root, file string, isdir bool) error {
|
||||||
|
if isdir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id := share.ID(root, file)
|
||||||
|
_, err := LoadFile(file, id)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, exts...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf("%s", strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile load AIGC by file
|
||||||
|
func LoadFile(file string, id string) (*Command, error) {
|
||||||
|
|
||||||
|
data, err := application.App.Read(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return LoadSource(data, file, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSource load AIGC
|
||||||
|
func LoadSource(data []byte, file, id string) (*Command, error) {
|
||||||
|
|
||||||
|
cmd := Command{
|
||||||
|
ID: id,
|
||||||
|
Prepare: Prepare{
|
||||||
|
Option: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
Optional: Optional{
|
||||||
|
Autopilot: false,
|
||||||
|
Confirm: false,
|
||||||
|
MaxAttempts: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := application.Parse(file, data, &cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Process == "" {
|
||||||
|
return nil, fmt.Errorf("%s process is required", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Prepare.Prompts == nil || len(cmd.Prepare.Prompts) == 0 {
|
||||||
|
return nil, fmt.Errorf("%s prompts is required", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create AI interface
|
||||||
|
cmd.AI, err = cmd.newAI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to autopilots
|
||||||
|
if cmd.Optional.Autopilot {
|
||||||
|
Autopilots = append(Autopilots, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save
|
||||||
|
err = cmd.save()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to AIGCs
|
||||||
|
Commands[id] = &cmd
|
||||||
|
return Commands[id], nil
|
||||||
|
}
|
||||||
43
neo/command/load_test.go
Normal file
43
neo/command/load_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/neo/command/driver"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
Commands = map[string]*Command{}
|
||||||
|
Load(config.Conf)
|
||||||
|
check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadWithStore(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
Commands = map[string]*Command{}
|
||||||
|
mem, err := driver.NewMemory("gpt-3_5-turbo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStore(mem)
|
||||||
|
Load(config.Conf)
|
||||||
|
check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(t *testing.T) {
|
||||||
|
ids := map[string]bool{}
|
||||||
|
for id := range Commands {
|
||||||
|
ids[id] = true
|
||||||
|
}
|
||||||
|
assert.True(t, ids["table.data"])
|
||||||
|
assert.GreaterOrEqual(t, len(Autopilots), 1)
|
||||||
|
}
|
||||||
84
neo/command/request.go
Normal file
84
neo/command/request.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
)
|
||||||
|
|
||||||
|
var requests = sync.Map{}
|
||||||
|
|
||||||
|
// Run the command
|
||||||
|
func (req *Request) Run(cb func(data []byte) int) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest create a new request
|
||||||
|
func (cmd *Command) NewRequest(ctx Context, messages []map[string]interface{}) (*Request, error) {
|
||||||
|
|
||||||
|
v, ok := requests.Load(ctx.Sid)
|
||||||
|
if !ok {
|
||||||
|
v = map[string]string{
|
||||||
|
"id": uuid.New().String(),
|
||||||
|
"cmd": cmd.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Request{
|
||||||
|
Command: cmd,
|
||||||
|
messages: messages,
|
||||||
|
sid: ctx.Sid,
|
||||||
|
id: req["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
|
||||||
|
}
|
||||||
82
neo/command/types.go
Normal file
82
neo/command/types.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/aigc"
|
||||||
|
"github.com/yaoapp/yao/neo/command/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request the command request
|
||||||
|
type Request struct {
|
||||||
|
id string
|
||||||
|
sid string
|
||||||
|
ctx Context
|
||||||
|
messages []map[string]interface{}
|
||||||
|
*Command
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command the command struct
|
||||||
|
type Command struct {
|
||||||
|
ID string `json:"-" yaml:"-"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Connector string `json:"connector"`
|
||||||
|
Process string `json:"process"`
|
||||||
|
Prepare Prepare `json:"prepare"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Optional Optional `json:"optional,omitempty"`
|
||||||
|
Args []Arg `json:"args,omitempty"`
|
||||||
|
Stack string `json:"stack,omitempty"` // query stack
|
||||||
|
Path string `json:"path,omitempty"` // query path
|
||||||
|
AI aigc.AI `json:"-" yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg the argument
|
||||||
|
type Arg struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Default interface{} `json:"default,omitempty"`
|
||||||
|
Required bool `json:"required,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the prepare struct
|
||||||
|
type Prepare struct {
|
||||||
|
Before string `json:"before,omitempty"`
|
||||||
|
After string `json:"after,omitempty"`
|
||||||
|
Prompts []Prompt `json:"prompts"`
|
||||||
|
Option map[string]interface{} `json:"option"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt a prompt
|
||||||
|
type Prompt struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional optional
|
||||||
|
type Optional struct {
|
||||||
|
Autopilot bool `json:"autopilot,omitempty"`
|
||||||
|
Confirm bool `json:"confirm,omitempty"`
|
||||||
|
MaxAttempts int `json:"maxAttempts,omitempty"` // default 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context the context
|
||||||
|
type Context struct {
|
||||||
|
Sid string `json:"-" yaml:"-"`
|
||||||
|
Stack string `json:"stack,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
context.Context `json:"-" yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the command driver
|
||||||
|
type Store interface {
|
||||||
|
Match(query driver.Query, content string) (string, error)
|
||||||
|
Set(id string, cmd driver.Command) error
|
||||||
|
Get(id string) (driver.Command, bool)
|
||||||
|
Del(id string)
|
||||||
|
SetRequest(sid, id, cid string) error
|
||||||
|
GetRequest(sid string) (string, string, bool)
|
||||||
|
DelRequest(sid string)
|
||||||
|
}
|
||||||
48
neo/neo.go
48
neo/neo.go
|
|
@ -1,10 +1,8 @@
|
||||||
package neo
|
package neo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -16,6 +14,7 @@ import (
|
||||||
"github.com/yaoapp/gou/process"
|
"github.com/yaoapp/gou/process"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
"github.com/yaoapp/yao/helper"
|
"github.com/yaoapp/yao/helper"
|
||||||
|
"github.com/yaoapp/yao/neo/command"
|
||||||
"github.com/yaoapp/yao/neo/conversation"
|
"github.com/yaoapp/yao/neo/conversation"
|
||||||
"github.com/yaoapp/yao/openai"
|
"github.com/yaoapp/yao/openai"
|
||||||
)
|
)
|
||||||
|
|
@ -66,8 +65,8 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
||||||
messages = append(messages, map[string]interface{}{"role": "user", "content": content, "name": sid})
|
messages = append(messages, map[string]interface{}{"role": "user", "content": content, "name": sid})
|
||||||
// utils.Dump(messages)
|
// utils.Dump(messages)
|
||||||
|
|
||||||
// reply the content
|
// set the context
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := command.NewContextWithCancel(sid, c.GetString("context"))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = neo.Answer(ctx, c, messages)
|
err = neo.Answer(ctx, c, messages)
|
||||||
|
|
@ -82,17 +81,43 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Answer the message
|
// Answer the message
|
||||||
func (neo *DSL) Answer(ctx context.Context, c *gin.Context, messages []map[string]interface{}) error {
|
func (neo *DSL) Answer(ctx command.Context, answer Answer, messages []map[string]interface{}) error {
|
||||||
|
|
||||||
chanStream := make(chan []byte, 1)
|
chanStream := make(chan []byte, 1)
|
||||||
chanError := make(chan error, 1)
|
chanError := make(chan error, 1)
|
||||||
|
|
||||||
|
// check the command
|
||||||
|
// cmd, isCommand := neo.Command.Match(ctx, messages)
|
||||||
|
isCommand := false
|
||||||
|
cmd := command.Command{}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
close(chanStream)
|
close(chanStream)
|
||||||
close(chanError)
|
close(chanError)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// execute the command
|
||||||
|
if isCommand {
|
||||||
|
|
||||||
|
req, err := cmd.NewRequest(ctx, messages)
|
||||||
|
if err != nil {
|
||||||
|
chanError <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = req.Run(func(data []byte) int {
|
||||||
|
chanStream <- data
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
chanError <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// chat with AI
|
||||||
_, ex := neo.AI.ChatCompletionsWith(ctx, messages, neo.Option, func(data []byte) int {
|
_, ex := neo.AI.ChatCompletionsWith(ctx, messages, neo.Option, func(data []byte) int {
|
||||||
chanStream <- data
|
chanStream <- data
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -106,7 +131,7 @@ func (neo *DSL) Answer(ctx context.Context, c *gin.Context, messages []map[strin
|
||||||
// save the history
|
// save the history
|
||||||
content := []byte{}
|
content := []byte{}
|
||||||
defer func() {
|
defer func() {
|
||||||
sid := c.GetString("__sid")
|
sid := answer.GetString("__sid")
|
||||||
if len(content) > 0 && sid != "" && len(messages) > 0 {
|
if len(content) > 0 && sid != "" && len(messages) > 0 {
|
||||||
err := neo.Conversation.SaveHistory(
|
err := neo.Conversation.SaveHistory(
|
||||||
sid,
|
sid,
|
||||||
|
|
@ -122,13 +147,14 @@ func (neo *DSL) Answer(ctx context.Context, c *gin.Context, messages []map[strin
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
answer.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||||
ok := c.Stream(func(w io.Writer) bool {
|
ok := answer.Stream(func(w io.Writer) bool {
|
||||||
select {
|
select {
|
||||||
case err := <-chanError:
|
case err := <-chanError:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(), "code": 500})
|
w.Write([]byte(fmt.Sprintf(`data: {"text":"%s"}%s`, err.Error(), "\n\n")))
|
||||||
}
|
}
|
||||||
|
w.Write([]byte(fmt.Sprintf("data: %s\n\n", `{"done":true}`)))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case msg := <-chanStream:
|
case msg := <-chanStream:
|
||||||
|
|
@ -163,11 +189,11 @@ func (neo *DSL) Answer(ctx context.Context, c *gin.Context, messages []map[strin
|
||||||
})
|
})
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
c.Status(500)
|
answer.Status(500)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(200)
|
answer.Status(200)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
neo/types.go
13
neo/types.go
|
|
@ -1,7 +1,10 @@
|
||||||
package neo
|
package neo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/yaoapp/yao/aigc"
|
"github.com/yaoapp/yao/aigc"
|
||||||
|
"github.com/yaoapp/yao/neo/command"
|
||||||
"github.com/yaoapp/yao/neo/conversation"
|
"github.com/yaoapp/yao/neo/conversation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,5 +31,13 @@ type Conversation interface {
|
||||||
|
|
||||||
// Command the command interface
|
// Command the command interface
|
||||||
type Command interface {
|
type Command interface {
|
||||||
Match(messages []map[string]interface{}) (bool, error)
|
Match(ctx command.Context, messages []map[string]interface{}) (*command.Command, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer the answer interface
|
||||||
|
type Answer interface {
|
||||||
|
GetString(key string) (s string)
|
||||||
|
Stream(func(w io.Writer) bool) bool
|
||||||
|
Status(code int)
|
||||||
|
Header(key, value string)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue