Refactor command handling and conversation management by removing the command package, updating conversation methods to support chat IDs, and enhancing API functionality for chat history and titles. This includes adjustments to the DSL context and conversation settings for improved data handling.
This commit is contained in:
parent
319faff110
commit
153efc1a49
24 changed files with 227 additions and 1543 deletions
|
|
@ -1,138 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yaoapp/gou/connector"
|
||||
"github.com/yaoapp/yao/aigc"
|
||||
"github.com/yaoapp/yao/neo/command/driver"
|
||||
"github.com/yaoapp/yao/neo/command/query"
|
||||
"github.com/yaoapp/yao/openai"
|
||||
)
|
||||
|
||||
// DefaultStore the default store driver
|
||||
var DefaultStore Store
|
||||
var recmd, _ = regexp.Compile(`^\/([a-zA-Z]+) +`)
|
||||
var reCmdOnly, _ = regexp.Compile(`^\/([a-zA-Z]+)$`)
|
||||
|
||||
// SetStore the driver interface
|
||||
func SetStore(store Store) {
|
||||
DefaultStore = store
|
||||
}
|
||||
|
||||
// Match the command from the content
|
||||
func Match(sid string, query query.Param, 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
|
||||
}
|
||||
|
||||
// Match the command use the command ID
|
||||
match := reCmdOnly.FindSubmatch([]byte(strings.TrimSpace(input)))
|
||||
if match == nil {
|
||||
match = recmd.FindSubmatch([]byte(strings.TrimSpace(input)))
|
||||
}
|
||||
if match != nil {
|
||||
key := fmt.Sprintf("[Index]%s", match[1])
|
||||
fmt.Println("Match Index:", key)
|
||||
|
||||
if cmd, ok := DefaultStore.Get(key); ok {
|
||||
fmt.Println("Match Command:", cmd.ID)
|
||||
return cmd.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultStore.Match(query, input)
|
||||
}
|
||||
|
||||
// Exit the command
|
||||
func Exit(sid string) error {
|
||||
if DefaultStore == nil {
|
||||
return fmt.Errorf("command store is not set")
|
||||
}
|
||||
DefaultStore.DelRequest(sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCommands get all commands
|
||||
func GetCommands() ([]driver.Command, error) {
|
||||
if DefaultStore == nil {
|
||||
return nil, fmt.Errorf("command store is not set")
|
||||
}
|
||||
return DefaultStore.GetCommands()
|
||||
}
|
||||
|
||||
// save the command to the 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,
|
||||
})
|
||||
}
|
||||
|
||||
data := driver.Command{
|
||||
ID: cmd.ID,
|
||||
Name: cmd.Name,
|
||||
Use: cmd.Use,
|
||||
Description: cmd.Description,
|
||||
Args: args,
|
||||
Stack: cmd.Stack,
|
||||
Path: cmd.Path,
|
||||
}
|
||||
|
||||
if cmd.Use != "" {
|
||||
key := fmt.Sprintf("[Index]%s", cmd.Use)
|
||||
err := DefaultStore.Set(key, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultStore.Set(cmd.ID, data)
|
||||
}
|
||||
|
||||
// NewAI create a new AI
|
||||
func (cmd *Command) newAI() (aigc.AI, error) {
|
||||
|
||||
if cmd.Connector == "" || strings.HasPrefix(cmd.Connector, "moapi") {
|
||||
model := "gpt-3.5-turbo"
|
||||
if strings.HasPrefix(cmd.Connector, "moapi:") {
|
||||
model = strings.TrimPrefix(cmd.Connector, "moapi:")
|
||||
}
|
||||
|
||||
ai, err := openai.NewMoapi(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.AI = ai
|
||||
return cmd.AI, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/yaoapp/gou/connector"
|
||||
"github.com/yaoapp/yao/aigc"
|
||||
"github.com/yaoapp/yao/neo/command/query"
|
||||
"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.Param, content string) (string, error) {
|
||||
|
||||
return "", fmt.Errorf("no related command found")
|
||||
|
||||
// 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,
|
||||
// "use": cmd.Use,
|
||||
// "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,
|
||||
// })
|
||||
|
||||
// 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(key string, cmd Command) error {
|
||||
commands.Store(key, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Del delete the command data
|
||||
func (driver *Memory) Del(key string) {
|
||||
commands.Delete(key)
|
||||
}
|
||||
|
||||
// Get the command data
|
||||
func (driver *Memory) Get(key string) (Command, bool) {
|
||||
v, ok := commands.Load(key)
|
||||
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)
|
||||
}
|
||||
|
||||
// GetCommands get all commands
|
||||
func (driver *Memory) GetCommands() ([]Command, error) {
|
||||
resulets := []Command{}
|
||||
commands.Range(func(key, value interface{}) bool {
|
||||
|
||||
if strings.HasPrefix(key.(string), "[Index]") {
|
||||
return true
|
||||
}
|
||||
|
||||
cmd, ok := value.(Command)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
resulets = append(resulets, cmd)
|
||||
return true
|
||||
})
|
||||
|
||||
return resulets, nil
|
||||
}
|
||||
|
||||
// NewAI create a new AI
|
||||
func (driver *Memory) newAI() (aigc.AI, error) {
|
||||
|
||||
if driver.model == "" || strings.HasPrefix(driver.model, "moapi") {
|
||||
model := "gpt-3.5-turbo"
|
||||
if strings.HasPrefix(driver.model, "moapi:") {
|
||||
model = strings.TrimPrefix(driver.model, "moapi:")
|
||||
}
|
||||
|
||||
ai, err := openai.NewMoapi(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ai, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/neo/command/query"
|
||||
"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.Param{Stack: "Table.Page.pet"}, "Generate table test data")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// assert.Equal(t, "table.data", id)
|
||||
|
||||
_, err := mem.Match(query.Param{Stack: "Form.Page.pet", Path: "/Form/pet"}, "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: "Table.*",
|
||||
Args: []map[string]interface{}{
|
||||
{
|
||||
"name": "data",
|
||||
"type": "Array",
|
||||
"description": "The data sets to generate",
|
||||
"required": true,
|
||||
"default": []interface{}{},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return mem
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package driver
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
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:"-"`
|
||||
Use string `json:"use,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package driver
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/helper"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
// Replace the prompt with the context
|
||||
func (prompt Prompt) Replace(data maps.MapStrAny) Prompt {
|
||||
|
||||
v := map[string]interface{}{"role": prompt.Role, "content": prompt.Content}
|
||||
if prompt.Name != "" {
|
||||
v["name"] = prompt.Name
|
||||
}
|
||||
|
||||
replaced := helper.Bind(v, data)
|
||||
res, ok := replaced.(map[string]interface{})
|
||||
if !ok {
|
||||
return prompt
|
||||
}
|
||||
|
||||
if res["role"] == nil {
|
||||
prompt.Role = ""
|
||||
} else if role, ok := res["role"].(string); ok {
|
||||
prompt.Role = role
|
||||
}
|
||||
|
||||
if res["name"] == nil {
|
||||
prompt.Name = ""
|
||||
} else if name, ok := res["name"].(string); ok {
|
||||
prompt.Name = name
|
||||
}
|
||||
|
||||
switch content := res["content"].(type) {
|
||||
case string:
|
||||
prompt.Content = content
|
||||
|
||||
default:
|
||||
if content == nil {
|
||||
prompt.Content = ""
|
||||
|
||||
} else if bytes, err := jsoniter.Marshal(content); err == nil {
|
||||
prompt.Content = string(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
func TestPromptReplace(t *testing.T) {
|
||||
|
||||
prompt := Prompt{
|
||||
Role: "{{ role }}",
|
||||
Name: "{{ name }}",
|
||||
Content: "{{ content }}",
|
||||
}
|
||||
|
||||
data := maps.Of(map[string]interface{}{
|
||||
"role": "User",
|
||||
"name": "Name",
|
||||
"content": "- Content\n",
|
||||
}).Dot()
|
||||
|
||||
prompt = prompt.Replace(data)
|
||||
assert.Equal(t, "User", prompt.Role)
|
||||
assert.Equal(t, "Name", prompt.Name)
|
||||
assert.Equal(t, "- Content\n", prompt.Content)
|
||||
|
||||
prompt = Prompt{
|
||||
Role: "Role",
|
||||
Name: "{{ notfound }}",
|
||||
Content: "{{ content }}",
|
||||
}
|
||||
|
||||
prompt = prompt.Replace(data)
|
||||
assert.Equal(t, "Role", prompt.Role)
|
||||
assert.Equal(t, "", prompt.Name)
|
||||
assert.Equal(t, "- Content\n", prompt.Content)
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Param the command param
|
||||
type Param struct {
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// MatchStack match the stack
|
||||
func (query Param) MatchStack(stack string) bool {
|
||||
|
||||
if stack == "" || stack == "*" || query.Stack == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if stack == query.Stack {
|
||||
return true
|
||||
}
|
||||
|
||||
matched, _ := regexp.MatchString(strings.ReplaceAll(stack, "*", ".*"), query.Stack)
|
||||
return matched
|
||||
}
|
||||
|
||||
// MatchPath match the path
|
||||
func (query Param) MatchPath(path string) bool {
|
||||
if path == "" || path == "*" || query.Path == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if path == query.Path {
|
||||
return true
|
||||
}
|
||||
|
||||
matched, _ := regexp.MatchString(strings.ReplaceAll(path, "*", ".*"), query.Path)
|
||||
return matched
|
||||
}
|
||||
|
||||
// MatchAny match the stack or path
|
||||
func (query Param) MatchAny(stack, path string) bool {
|
||||
|
||||
if path == "" || path == "-" {
|
||||
return query.MatchStack(stack)
|
||||
}
|
||||
|
||||
if stack == "" || stack == "-" {
|
||||
return query.MatchPath(path)
|
||||
}
|
||||
|
||||
return query.MatchStack(stack) || query.MatchPath(path)
|
||||
}
|
||||
|
|
@ -1,473 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/process"
|
||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/kun/utils"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/neo/conversation"
|
||||
"github.com/yaoapp/yao/neo/message"
|
||||
"rogchap.com/v8go"
|
||||
)
|
||||
|
||||
// Run the command
|
||||
func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.JSON) int) error {
|
||||
|
||||
// Enter the command mode
|
||||
if input, ok := messages[len(messages)-1]["content"].(string); ok {
|
||||
match := reCmdOnly.FindSubmatch([]byte(strings.TrimSpace(input)))
|
||||
if match != nil {
|
||||
fmt.Printf("Match Command: %s | %s\n", match[1], input)
|
||||
cb(req.msg().Text("Enter the command Mode"))
|
||||
cb(req.msg().Done())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if config.Conf.Mode == "development" {
|
||||
utils.Dump("----Request Run ----")
|
||||
fmt.Printf("Command Request: %s %s\n", req.Command.ID, req.sid)
|
||||
fmt.Printf("Command Process: %s\n", req.Command.Process)
|
||||
fmt.Printf("Command Prepare Before: %s\n", req.Command.Prepare.Before)
|
||||
fmt.Printf("Command Prepare Before: %s\n", req.Command.Prepare.After)
|
||||
}
|
||||
|
||||
input, err := req.prepare(messages, cb)
|
||||
if err != nil {
|
||||
req.error(err, cb)
|
||||
return err
|
||||
}
|
||||
|
||||
if config.Conf.Mode == "development" {
|
||||
utils.Dump("----Input After Prepare ----", input)
|
||||
}
|
||||
|
||||
args, err := req.parseArgs(input, cb)
|
||||
if err != nil {
|
||||
cb(req.msg().Text("\n\n" + err.Error()))
|
||||
cb(req.msg().Done())
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.Conf.Mode == "development" {
|
||||
utils.Dump("---- Command Args ----", args)
|
||||
}
|
||||
|
||||
// Send the command to the service
|
||||
if req.Command.Optional.Confirm {
|
||||
req.confirm(args, cb)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute the command by script
|
||||
if strings.HasPrefix(req.Command.Process, "scripts.") || strings.HasPrefix(req.Command.Process, "studio.") {
|
||||
|
||||
res, err := req.runScript(req.Command.Process, args, cb)
|
||||
if err != nil {
|
||||
cb(req.msg().Text("\n\n" + err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
msg := req.msg().Bind(res)
|
||||
if req.Actions != nil && len(req.Actions) > 0 {
|
||||
for _, action := range req.Actions {
|
||||
msg.Action(action.Name, action.Type, action.Payload, action.Next)
|
||||
}
|
||||
}
|
||||
|
||||
cb(msg.Done())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Other process
|
||||
p, err := process.Of(req.Command.Process, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := p.Exec()
|
||||
if err != nil {
|
||||
cb(req.msg().Text("\n\n" + err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
msg := req.msg()
|
||||
if data, ok := res.(map[string]interface{}); ok {
|
||||
msg = msg.Bind(data)
|
||||
}
|
||||
|
||||
if req.Actions != nil && len(req.Actions) > 0 {
|
||||
for _, action := range req.Actions {
|
||||
msg.Action(action.Name, action.Type, action.Payload, action.Next)
|
||||
}
|
||||
}
|
||||
|
||||
// DONE
|
||||
cb(msg.Done())
|
||||
return nil
|
||||
}
|
||||
|
||||
// confirm the command
|
||||
func (req *Request) confirm(args []interface{}, cb func(msg *message.JSON) int) {
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"method": "ExecCommand",
|
||||
"args": []interface{}{
|
||||
req.id,
|
||||
req.Command.Process,
|
||||
args,
|
||||
map[string]interface{}{"stack": req.ctx.Stack, "path": req.ctx.Path},
|
||||
},
|
||||
}
|
||||
|
||||
msg := req.msg().
|
||||
Action("ExecCommand", "Service.__neo", payload, "").
|
||||
Confirm().
|
||||
Done()
|
||||
|
||||
if req.Actions != nil && len(req.Actions) > 0 {
|
||||
for _, action := range req.Actions {
|
||||
msg.Action(action.Name, action.Type, action.Payload, action.Next)
|
||||
}
|
||||
}
|
||||
|
||||
cb(msg)
|
||||
}
|
||||
|
||||
// validate the command
|
||||
func (req *Request) parseArgs(input interface{}, cb func(msg *message.JSON) int) ([]interface{}, error) {
|
||||
args := []interface{}{}
|
||||
data := map[string]interface{}{}
|
||||
|
||||
switch v := input.(type) {
|
||||
case string:
|
||||
err := jsoniter.Unmarshal([]byte(v), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
|
||||
case []byte:
|
||||
err := jsoniter.Unmarshal(v, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
|
||||
case map[string]interface{}:
|
||||
data = v
|
||||
break
|
||||
|
||||
default:
|
||||
err := fmt.Errorf("\nInvalid input type: %T", v)
|
||||
req.error(err, cb)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// validate the args
|
||||
if req.Command.Args != nil && len(req.Command.Args) > 0 {
|
||||
for _, arg := range req.Command.Args {
|
||||
v, ok := data[arg.Name]
|
||||
if arg.Required && !ok {
|
||||
err := fmt.Errorf("\nMissing required argument: %s", arg.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// @todo: validate the type
|
||||
args = append(args, v)
|
||||
}
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// RunPrepare the command
|
||||
func (req *Request) prepare(messages []map[string]interface{}, cb func(msg *message.JSON) int) (interface{}, error) {
|
||||
|
||||
if config.Conf.Mode == "development" {
|
||||
utils.Dump("----Messages Before Prepare ----", messages)
|
||||
}
|
||||
|
||||
// Before hook
|
||||
data, err := req.prepareBefore(messages, cb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// replace the pro
|
||||
prompts := []Prompt{}
|
||||
if req.Command.Prepare.Prompts != nil && len(req.Command.Prepare.Prompts) > 0 {
|
||||
prompts = append(prompts, req.Command.Prepare.Prompts...)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
data = maps.Of(data).Dot()
|
||||
for i, prompt := range prompts {
|
||||
prompts[i] = prompt.Replace(data)
|
||||
}
|
||||
}
|
||||
|
||||
question, err := req.question(messages)
|
||||
if err != nil {
|
||||
req.error(err, cb)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chatMessages, err := req.messages(prompts, question)
|
||||
if err != nil {
|
||||
req.error(err, cb)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.Conf.Mode == "development" {
|
||||
utils.Dump("----Command Prompts ----", chatMessages)
|
||||
}
|
||||
|
||||
// chat with AI
|
||||
content := []byte{}
|
||||
_, ex := req.AI.ChatCompletionsWith(req.ctx, chatMessages, req.Prepare.Option, func(data []byte) int {
|
||||
msg := message.NewOpenAI(data)
|
||||
if msg != nil {
|
||||
if msg.IsDone() {
|
||||
return 0
|
||||
}
|
||||
content = msg.Append(content)
|
||||
cb(req.msg().Text(msg.String()))
|
||||
}
|
||||
return 1
|
||||
})
|
||||
|
||||
if ex != nil {
|
||||
req.error(fmt.Errorf(ex.Message), cb)
|
||||
return nil, fmt.Errorf("Chat error: %s", ex.Message)
|
||||
}
|
||||
defer req.saveHistory(content, chatMessages)
|
||||
|
||||
// After hook
|
||||
args, err := req.prepareAfter(string(content), cb)
|
||||
if err != nil {
|
||||
log.Error("Prepare after error: %s", err.Error())
|
||||
fmt.Println(err)
|
||||
return content, nil
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// prepareBefore hook
|
||||
func (req *Request) prepareBefore(messages []map[string]interface{}, cb func(msg *message.JSON) int) (map[string]interface{}, error) {
|
||||
|
||||
if req.Prepare.Before == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// prepare the args
|
||||
args := []interface{}{
|
||||
map[string]interface{}{"stack": req.ctx.Stack, "path": req.ctx.Path},
|
||||
messages,
|
||||
}
|
||||
|
||||
return req.runScript(req.Prepare.Before, args, cb)
|
||||
}
|
||||
|
||||
// prepareAfter hook
|
||||
func (req *Request) prepareAfter(content string, cb func(msg *message.JSON) int) (interface{}, error) {
|
||||
|
||||
if req.Prepare.After == "" {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// prepare the args
|
||||
args := []interface{}{
|
||||
content,
|
||||
map[string]interface{}{"stack": req.ctx.Stack, "path": req.ctx.Path}, // context
|
||||
}
|
||||
|
||||
return req.runScript(req.Prepare.After, args, cb)
|
||||
}
|
||||
|
||||
// saveHistory save the history
|
||||
func (req *Request) saveHistory(content []byte, messages []map[string]interface{}) {
|
||||
|
||||
if len(content) > 0 && req.sid != "" && len(messages) > 0 {
|
||||
err := req.conversation.SaveRequest(
|
||||
req.sid,
|
||||
req.id,
|
||||
req.Command.ID,
|
||||
[]map[string]interface{}{
|
||||
{"role": "user", "content": messages[len(messages)-1]["content"], "name": req.sid},
|
||||
{"role": "assistant", "content": string(content), "name": req.sid},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Save request error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (req *Request) error(err error, cb func(msg *message.JSON) int) {
|
||||
cb(req.msg().Text(err.Error()))
|
||||
cb(req.msg().Done())
|
||||
// req.Done()
|
||||
}
|
||||
|
||||
func (req *Request) question(messages []map[string]interface{}) (string, error) {
|
||||
if len(messages) < 1 {
|
||||
return "", fmt.Errorf("No messages")
|
||||
}
|
||||
|
||||
question, ok := messages[len(messages)-1]["content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("messages content is not string")
|
||||
}
|
||||
|
||||
return question, nil
|
||||
}
|
||||
|
||||
func (req *Request) messages(prompts []Prompt, question string) ([]map[string]interface{}, error) {
|
||||
messages := []map[string]interface{}{}
|
||||
for _, prompt := range prompts {
|
||||
message := map[string]interface{}{"role": prompt.Role, "content": prompt.Content}
|
||||
if prompt.Name != "" {
|
||||
message["name"] = prompt.Name
|
||||
}
|
||||
messages = append(messages, message)
|
||||
}
|
||||
|
||||
history, err := req.conversation.GetRequest(req.sid, req.id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages = append(messages, history...)
|
||||
messages = append(messages, map[string]interface{}{"role": "user", "content": question, "name": req.sid})
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (req *Request) runScript(id string, args []interface{}, cb func(msg *message.JSON) int) (map[string]interface{}, error) {
|
||||
|
||||
namer := strings.Split(id, ".")
|
||||
method := namer[len(namer)-1]
|
||||
scriptID := strings.Join(namer[1:len(namer)-1], ".")
|
||||
|
||||
var err error
|
||||
var script *v8.Script
|
||||
if namer[0] == "scripts" {
|
||||
script, err = v8.Select(scriptID)
|
||||
} else if namer[0] == "studio" {
|
||||
script, err = v8.SelectRoot(scriptID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make a new script context
|
||||
v8ctx, err := script.NewContext(req.sid, map[string]interface{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer v8ctx.Close()
|
||||
|
||||
v8ctx.WithFunction("ssWrite", func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
args := info.Args()
|
||||
if len(args) != 1 {
|
||||
return v8go.Null(info.Context().Isolate())
|
||||
}
|
||||
|
||||
text := args[0].String()
|
||||
cb(req.msg().Text(text))
|
||||
return v8go.Null(info.Context().Isolate())
|
||||
})
|
||||
|
||||
v8ctx.WithFunction("done", func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
args := info.Args()
|
||||
if len(args) == 1 {
|
||||
text := args[0].String()
|
||||
cb(req.msg().Text(text))
|
||||
}
|
||||
|
||||
cb(req.msg().Done())
|
||||
// req.Done()
|
||||
return v8go.Null(v8ctx.Context.Isolate())
|
||||
})
|
||||
|
||||
res, err := v8ctx.CallWith(req.ctx, method, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return data
|
||||
switch v := res.(type) {
|
||||
|
||||
case bool:
|
||||
return nil, nil
|
||||
|
||||
case map[string]interface{}:
|
||||
return v, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("script return type is not supported")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (req *Request) msg() *message.JSON {
|
||||
return message.New().Command(req.Command.Name, req.Command.ID, req.id)
|
||||
}
|
||||
|
||||
// NewRequest create a new request
|
||||
func (cmd *Command) NewRequest(ctx Context, conversation conversation.Conversation) (*Request, error) {
|
||||
|
||||
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,
|
||||
conversation: conversation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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,
|
||||
sid: ctx.Sid,
|
||||
id: id,
|
||||
ctx: ctx,
|
||||
conversation: conversation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Done the request done
|
||||
func (req *Request) Done() {
|
||||
if DefaultStore != nil {
|
||||
DefaultStore.DelRequest(req.sid)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yaoapp/yao/aigc"
|
||||
"github.com/yaoapp/yao/neo/command/driver"
|
||||
"github.com/yaoapp/yao/neo/command/query"
|
||||
"github.com/yaoapp/yao/neo/conversation"
|
||||
"github.com/yaoapp/yao/neo/message"
|
||||
)
|
||||
|
||||
// Request the command request
|
||||
type Request struct {
|
||||
id string
|
||||
sid string
|
||||
ctx Context
|
||||
conversation conversation.Conversation
|
||||
*Command
|
||||
}
|
||||
|
||||
// Command the command struct
|
||||
type Command struct {
|
||||
ID string `json:"-" yaml:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Use string `json:"use,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"`
|
||||
Actions []message.Action `json:"actions,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:"sid" yaml:"-"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Path string `json:"pathname,omitempty"`
|
||||
FormData map[string]interface{} `json:"formdata,omitempty"`
|
||||
Field *ContextField `json:"field,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Config map[string]interface{} `json:"config,omitempty"`
|
||||
Signal interface{} `json:"signal,omitempty"`
|
||||
context.Context `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// ContextField the context field
|
||||
type ContextField struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Bind string `json:"bind,omitempty"`
|
||||
}
|
||||
|
||||
// Store the command driver
|
||||
type Store interface {
|
||||
Match(query query.Param, content string) (string, error)
|
||||
Set(key string, cmd driver.Command) error
|
||||
Get(key string) (driver.Command, bool)
|
||||
Del(key string)
|
||||
SetRequest(sid, id, cid string) error
|
||||
GetRequest(sid string) (string, string, bool)
|
||||
DelRequest(sid string)
|
||||
GetCommands() ([]driver.Command, error)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package command
|
||||
package neo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// NewContext create a new context
|
||||
func NewContext(sid, payload string) Context {
|
||||
func NewContext(sid, cid, payload string) Context {
|
||||
ctx := Context{Context: context.Background(), Sid: sid}
|
||||
if payload == "" {
|
||||
return ctx
|
||||
|
|
@ -23,14 +23,14 @@ func NewContext(sid, payload string) Context {
|
|||
}
|
||||
|
||||
// NewContextWithCancel create a new context with cancel
|
||||
func NewContextWithCancel(sid, payload string) (Context, context.CancelFunc) {
|
||||
ctx := NewContext(sid, payload)
|
||||
func NewContextWithCancel(sid, cid, payload string) (Context, context.CancelFunc) {
|
||||
ctx := NewContext(sid, cid, 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)
|
||||
func NewContextWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
|
||||
ctx := NewContext(sid, cid, payload)
|
||||
return ContextWithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
|
|
@ -8,13 +8,23 @@ func NewMongo() *Mongo {
|
|||
return &Mongo{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Mongo) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Mongo) GetChats(sid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Mongo) GetHistory(sid string) ([]map[string]interface{}, error) {
|
||||
func (conv *Mongo) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Mongo) SaveHistory(sid string, messages []map[string]interface{}) error {
|
||||
func (conv *Mongo) SaveHistory(sid string, messages []map[string]interface{}, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,23 @@ func NewRedis() *Redis {
|
|||
return &Redis{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Redis) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Redis) GetChats(sid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Redis) GetHistory(sid string) ([]map[string]interface{}, error) {
|
||||
func (conv *Redis) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Redis) SaveHistory(sid string, messages []map[string]interface{}) error {
|
||||
func (conv *Redis) SaveHistory(sid string, messages []map[string]interface{}, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ type Setting struct {
|
|||
|
||||
// Conversation the store interface
|
||||
type Conversation interface {
|
||||
GetHistory(sid string) ([]map[string]interface{}, error)
|
||||
SaveHistory(sid string, messages []map[string]interface{}) error
|
||||
UpdateChatTitle(sid string, cid string, title string) error
|
||||
GetChats(sid string) ([]map[string]interface{}, error)
|
||||
GetHistory(sid string, cid string) ([]map[string]interface{}, error)
|
||||
SaveHistory(sid string, messages []map[string]interface{}, cid string) error
|
||||
GetRequest(sid string, rid string) ([]map[string]interface{}, error)
|
||||
SaveRequest(sid string, rid string, cid string, messages []map[string]interface{}) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,23 @@ func NewWeaviate() *Weaviate {
|
|||
return &Weaviate{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Weaviate) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Weaviate) GetChats(sid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Weaviate) GetHistory(sid string) ([]map[string]interface{}, error) {
|
||||
func (conv *Weaviate) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Weaviate) SaveHistory(sid string, messages []map[string]interface{}) error {
|
||||
func (conv *Weaviate) SaveHistory(sid string, messages []map[string]interface{}, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ type Xun struct {
|
|||
|
||||
type row struct {
|
||||
Role string `json:"role"`
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"` // Chat title
|
||||
Name string `json:"name"` // User name
|
||||
Content string `json:"content"`
|
||||
Sid string `json:"sid"`
|
||||
Rid string `json:"rid"`
|
||||
Cid string `json:"cid"`
|
||||
Cid string `json:"cid"` // Chat ID from chat history
|
||||
ExpiredAt interface{} `json:"expired_at"`
|
||||
}
|
||||
|
||||
|
|
@ -62,13 +63,49 @@ func NewXun(setting Setting) (*Xun, error) {
|
|||
return conv, nil
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Xun) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
_, err := conv.query.Table(conv.setting.Table).
|
||||
Where("sid", sid).Where("cid", cid).
|
||||
Update(map[string]interface{}{"title": title})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Xun) GetChats(sid string) ([]map[string]interface{}, error) {
|
||||
qb := conv.query.Table(conv.setting.Table).
|
||||
Select("cid").
|
||||
Where("sid", sid).
|
||||
GroupBy("cid")
|
||||
|
||||
if conv.setting.TTL > 0 {
|
||||
qb.Where("expired_at", ">", time.Now())
|
||||
}
|
||||
|
||||
res := []map[string]interface{}{}
|
||||
|
||||
rows, err := qb.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
res = append(res, map[string]interface{}{
|
||||
"chat_id": row.Get("cid"),
|
||||
"title": row.Get("cid"),
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Xun) GetHistory(sid string) ([]map[string]interface{}, error) {
|
||||
func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
|
||||
qb := conv.query.Table(conv.setting.Table).
|
||||
Select("role", "name", "content").
|
||||
Where("sid", sid).
|
||||
Where("cid", "").
|
||||
Where("cid", cid).
|
||||
OrderBy("id", "desc")
|
||||
|
||||
if conv.setting.TTL > 0 {
|
||||
|
|
@ -98,7 +135,7 @@ func (conv *Xun) GetHistory(sid string) ([]map[string]interface{}, error) {
|
|||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}) error {
|
||||
func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid string) error {
|
||||
|
||||
defer conv.clean()
|
||||
var expiredAt interface{} = nil
|
||||
|
|
@ -113,6 +150,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}) erro
|
|||
Name: "",
|
||||
Content: message["content"].(string),
|
||||
Sid: sid,
|
||||
Cid: cid,
|
||||
ExpiredAt: expiredAt,
|
||||
}
|
||||
|
||||
|
|
@ -214,12 +252,13 @@ func (conv *Xun) Init() error {
|
|||
if !has {
|
||||
err = conv.schema.CreateTable(conv.setting.Table, func(table schema.Blueprint) {
|
||||
|
||||
table.ID("id") // The ID field
|
||||
table.String("sid", 255).Index()
|
||||
table.String("rid", 255).Null().Index() // The request ID
|
||||
table.String("cid", 200).Null().Index() // The Command ID
|
||||
table.String("role", 200).Null().Index()
|
||||
table.String("name", 200).Null().Index()
|
||||
table.ID("id") // The ID field
|
||||
table.String("sid", 255).Index() // The Session ID
|
||||
table.String("rid", 255).Null().Index() // The request ID
|
||||
table.String("cid", 200).Null().Index() // The Chat ID
|
||||
table.String("role", 200).Null().Index() // The Message role
|
||||
table.String("name", 200).Null().Index() // The User name
|
||||
table.String("title", 200).Null().Index() // The Chat title
|
||||
table.Text("content").Null()
|
||||
|
||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
||||
|
|
@ -246,5 +285,15 @@ func (conv *Xun) Init() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Auto update the title
|
||||
if !tab.HasColumn("title") {
|
||||
err = conv.schema.AlterTable(conv.setting.Table, func(table schema.Blueprint) {
|
||||
table.String("title", 200).Null().Index()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,14 +137,15 @@ func TestXunSaveAndGetHistory(t *testing.T) {
|
|||
})
|
||||
|
||||
// save the history
|
||||
cid := "123456"
|
||||
err = conv.SaveHistory("123456", []map[string]interface{}{
|
||||
{"role": "user", "name": "user1", "content": "hello"},
|
||||
{"role": "assistant", "name": "user1", "content": "Hello there, how"},
|
||||
})
|
||||
}, cid)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// get the history
|
||||
data, err := conv.GetHistory("123456")
|
||||
data, err := conv.GetHistory("123456", cid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -182,3 +183,66 @@ func TestXunSaveAndGetRequest(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, 2, len(data))
|
||||
}
|
||||
|
||||
func TestXunSaveAndGetHistoryWithCID(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
defer capsule.Schema().DropTableIfExists("__unit_test_conversation")
|
||||
|
||||
err := capsule.Schema().DropTableIfExists("__unit_test_conversation")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
conv, err := NewXun(Setting{
|
||||
Connector: "default",
|
||||
Table: "__unit_test_conversation",
|
||||
TTL: 3600,
|
||||
})
|
||||
|
||||
// save the history with specific cid
|
||||
sid := "123456"
|
||||
cid := "789012"
|
||||
messages := []map[string]interface{}{
|
||||
{"role": "user", "name": "user1", "content": "hello"},
|
||||
{"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"},
|
||||
}
|
||||
err = conv.SaveHistory(sid, messages, cid)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// get the history for specific cid
|
||||
data, err := conv.GetHistory(sid, cid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 2, len(data))
|
||||
|
||||
// save another message with different cid
|
||||
anotherCID := "345678"
|
||||
moreMessages := []map[string]interface{}{
|
||||
{"role": "user", "name": "user1", "content": "another message"},
|
||||
}
|
||||
err = conv.SaveHistory(sid, moreMessages, anotherCID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// get history for the first cid - should still be 2 messages
|
||||
data, err = conv.GetHistory(sid, cid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 2, len(data))
|
||||
|
||||
// get history for the second cid - should be 1 message
|
||||
data, err = conv.GetHistory(sid, anotherCID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 1, len(data))
|
||||
|
||||
// get all history for the sid without specifying cid
|
||||
allData, err := conv.GetHistory(sid, cid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 2, len(allData))
|
||||
}
|
||||
|
|
|
|||
22
neo/load.go
22
neo/load.go
|
|
@ -4,11 +4,8 @@ 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"
|
||||
)
|
||||
|
||||
|
|
@ -23,7 +20,6 @@ func Load(cfg config.Config) error {
|
|||
Prompts: []aigc.Prompt{},
|
||||
Option: map[string]interface{}{},
|
||||
Allows: []string{},
|
||||
Command: Command{Parser: ""},
|
||||
ConversationSetting: conversation.Setting{
|
||||
Table: "yao_neo_conversation",
|
||||
Connector: "default",
|
||||
|
|
@ -58,23 +54,5 @@ func Load(cfg config.Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Command Setting
|
||||
parser := setting.Command.Parser
|
||||
if parser == "" || parser == "default" {
|
||||
parser = setting.Connector
|
||||
}
|
||||
|
||||
store, err := driver.NewMemory(parser, 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
|
||||
}
|
||||
|
|
|
|||
180
neo/neo.go
180
neo/neo.go
|
|
@ -13,8 +13,6 @@ import (
|
|||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/helper"
|
||||
"github.com/yaoapp/yao/neo/command"
|
||||
"github.com/yaoapp/yao/neo/command/query"
|
||||
"github.com/yaoapp/yao/neo/conversation"
|
||||
"github.com/yaoapp/yao/neo/message"
|
||||
"github.com/yaoapp/yao/openai"
|
||||
|
|
@ -53,7 +51,7 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
|||
}
|
||||
|
||||
// set the context
|
||||
ctx, cancel := command.NewContextWithCancel(sid, c.Query("context"))
|
||||
ctx, cancel := NewContextWithCancel(sid, c.Query("chat_id"), c.Query("context"))
|
||||
defer cancel()
|
||||
|
||||
err = neo.Answer(ctx, content, c)
|
||||
|
|
@ -64,6 +62,28 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
|||
|
||||
})
|
||||
router.GET(path, handlers...)
|
||||
router.POST(path, handlers...)
|
||||
|
||||
// api Get ChatList
|
||||
handlers = append(middlewares, func(c *gin.Context) {
|
||||
sid := c.GetString("__sid")
|
||||
if sid == "" {
|
||||
c.JSON(400, gin.H{"message": "sid is required", "code": 400})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
list, err := neo.Conversation.GetChats(sid)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{"data": list})
|
||||
c.Done()
|
||||
})
|
||||
router.GET(path+"/chats", handlers...)
|
||||
|
||||
// api router chat history
|
||||
handlers = append(middlewares, func(c *gin.Context) {
|
||||
|
|
@ -74,104 +94,24 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
|||
return
|
||||
}
|
||||
|
||||
history, err := neo.Conversation.GetHistory(sid)
|
||||
cid := c.Query("chat_id")
|
||||
history, err := neo.Conversation.GetHistory(sid, cid)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"data": history,
|
||||
"command": nil,
|
||||
})
|
||||
c.JSON(200, map[string]interface{}{"data": history})
|
||||
c.Done()
|
||||
})
|
||||
router.GET(path+"/history", handlers...)
|
||||
|
||||
// api router chat commands
|
||||
handlers = append(middlewares, func(c *gin.Context) {
|
||||
commands, err := command.GetCommands()
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, commands)
|
||||
c.Done()
|
||||
})
|
||||
router.GET(path+"/commands", handlers...)
|
||||
|
||||
// api router exit command mode
|
||||
handlers = append(middlewares, func(c *gin.Context) {
|
||||
sid := c.GetString("__sid")
|
||||
if sid == "" {
|
||||
c.JSON(400, gin.H{"message": "sid is required", "code": 400})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
err := c.ShouldBindJSON(&payload)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"message": err.Error(), "code": 400})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
cmd, ok := payload["cmd"].(string)
|
||||
if !ok {
|
||||
c.JSON(400, gin.H{"message": "command is required", "code": 400})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
|
||||
case "ModelList":
|
||||
c.JSON(200, gin.H{"data": neo.Models, "code": 200})
|
||||
c.Done()
|
||||
|
||||
case "SelectModel":
|
||||
model, ok := payload["model"].(string)
|
||||
if !ok {
|
||||
c.JSON(400, gin.H{"message": "model is required", "code": 400})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
err := neo.Select(model)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"message": "success", "code": 200})
|
||||
c.Done()
|
||||
|
||||
case "ExitCommandMode":
|
||||
err := command.Exit(sid)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"message": "success", "code": 200})
|
||||
c.Done()
|
||||
|
||||
default:
|
||||
c.JSON(400, gin.H{"message": "command is not supported", "code": 400})
|
||||
}
|
||||
})
|
||||
router.POST(path, handlers...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Answer reply the message
|
||||
func (neo *DSL) Answer(ctx command.Context, question string, c *gin.Context) error {
|
||||
func (neo *DSL) Answer(ctx Context, question string, c *gin.Context) error {
|
||||
// get the chat messages
|
||||
messages, err := neo.chatMessages(ctx, question)
|
||||
if err != nil {
|
||||
|
|
@ -190,39 +130,6 @@ func (neo *DSL) Answer(ctx command.Context, question string, c *gin.Context) err
|
|||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
|
||||
// check the command
|
||||
cmd, isCommand := neo.matchCommand(ctx, messages)
|
||||
if isCommand {
|
||||
// execute the command
|
||||
req, err := cmd.NewRequest(ctx, neo.Conversation)
|
||||
if err != nil {
|
||||
log.Error("Command with AI error: %s", err.Error())
|
||||
done <- true
|
||||
return
|
||||
}
|
||||
|
||||
err = req.Run(messages, func(msg *message.JSON) int {
|
||||
err := neo.send(ctx, msg, messages, content, c)
|
||||
if err != nil {
|
||||
c.Status(500)
|
||||
return 0 // break
|
||||
}
|
||||
|
||||
// Complete the stream
|
||||
if msg.IsDone() {
|
||||
return 0 // break
|
||||
}
|
||||
return 1
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.Status(500)
|
||||
log.Error("Command with AI error: %s", err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, ex := neo.AI.ChatCompletionsWith(ctx, messages, neo.Option, func(data []byte) int {
|
||||
|
||||
select {
|
||||
|
|
@ -266,7 +173,7 @@ func (neo *DSL) Answer(ctx command.Context, question string, c *gin.Context) err
|
|||
}
|
||||
|
||||
// save the history
|
||||
neo.saveHistory(ctx.Sid, content, messages)
|
||||
neo.saveHistory(ctx.Sid, ctx.ChatID, content, messages)
|
||||
c.Status(200)
|
||||
|
||||
// Complete the stream
|
||||
|
|
@ -285,7 +192,7 @@ func (neo *DSL) Answer(ctx command.Context, question string, c *gin.Context) err
|
|||
}
|
||||
|
||||
// Send send the message to the stream
|
||||
func (neo *DSL) send(ctx command.Context, msg *message.JSON, messages []map[string]interface{}, content []byte, c *gin.Context) error {
|
||||
func (neo *DSL) send(ctx Context, msg *message.JSON, messages []map[string]interface{}, content []byte, c *gin.Context) error {
|
||||
|
||||
w := c.Writer
|
||||
|
||||
|
|
@ -356,7 +263,7 @@ func (neo *DSL) prompts() []map[string]interface{} {
|
|||
}
|
||||
|
||||
// prepare the messages
|
||||
func (neo *DSL) prepare(ctx command.Context, messages []map[string]interface{}) []map[string]interface{} {
|
||||
func (neo *DSL) prepare(ctx Context, messages []map[string]interface{}) []map[string]interface{} {
|
||||
if neo.Prepare == "" {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
|
|
@ -405,9 +312,9 @@ func (neo *DSL) prepare(ctx command.Context, messages []map[string]interface{})
|
|||
}
|
||||
|
||||
// chatMessages get the chat messages
|
||||
func (neo *DSL) chatMessages(ctx command.Context, content string) ([]map[string]interface{}, error) {
|
||||
func (neo *DSL) chatMessages(ctx Context, content string) ([]map[string]interface{}, error) {
|
||||
|
||||
history, err := neo.Conversation.GetHistory(ctx.Sid)
|
||||
history, err := neo.Conversation.GetHistory(ctx.Sid, ctx.ChatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -424,28 +331,8 @@ func (neo *DSL) chatMessages(ctx command.Context, content string) ([]map[string]
|
|||
return messages, nil
|
||||
}
|
||||
|
||||
// matchCommand match the command
|
||||
func (neo *DSL) matchCommand(ctx command.Context, messages []map[string]interface{}) (*command.Command, bool) {
|
||||
if len(messages) < 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
input, ok := messages[len(messages)-1]["content"].(string)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
id, err := command.Match(ctx.Sid, query.Param{Stack: ctx.Stack, Path: ctx.Path}, input)
|
||||
if err == nil && id != "" {
|
||||
cmd, isCommand := command.Commands[id]
|
||||
return cmd, isCommand
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// saveHistory save the history
|
||||
func (neo *DSL) saveHistory(sid string, content []byte, messages []map[string]interface{}) {
|
||||
func (neo *DSL) saveHistory(sid string, chatID string, content []byte, messages []map[string]interface{}) {
|
||||
|
||||
if len(content) > 0 && sid != "" && len(messages) > 0 {
|
||||
err := neo.Conversation.SaveHistory(
|
||||
|
|
@ -454,6 +341,7 @@ func (neo *DSL) saveHistory(sid string, content []byte, messages []map[string]in
|
|||
{"role": "user", "content": messages[len(messages)-1]["content"], "name": sid},
|
||||
{"role": "assistant", "content": string(content), "name": sid},
|
||||
},
|
||||
chatID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ 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"
|
||||
)
|
||||
|
|
@ -50,7 +49,8 @@ func TestAPI(t *testing.T) {
|
|||
return 1
|
||||
})
|
||||
|
||||
assert.Contains(t, string(res), `{"done":true}`)
|
||||
assert.Contains(t, string(res), `{`)
|
||||
|
||||
}
|
||||
|
||||
func TestAPIAuth(t *testing.T) {
|
||||
|
|
@ -111,12 +111,6 @@ func testRouter(t *testing.T) *gin.Engine {
|
|||
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
|
||||
|
|
|
|||
23
neo/types.go
23
neo/types.go
|
|
@ -1,6 +1,7 @@
|
|||
package neo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -21,7 +22,6 @@ type DSL struct {
|
|||
Write string `json:"write,omitempty"`
|
||||
Prompts []aigc.Prompt `json:"prompts,omitempty"`
|
||||
Allows []string `json:"allows,omitempty"`
|
||||
Command Command `json:"command,omitempty"`
|
||||
Models []string `json:"models,omitempty"`
|
||||
AI aigc.AI `json:"-" yaml:"-"`
|
||||
Conversation conversation.Conversation `json:"-" yaml:"-"`
|
||||
|
|
@ -35,7 +35,22 @@ type Answer interface {
|
|||
Header(key, value string)
|
||||
}
|
||||
|
||||
// Command setting
|
||||
type Command struct {
|
||||
Parser string `json:"parser,omitempty"`
|
||||
// Context the context
|
||||
type Context struct {
|
||||
Sid string `json:"sid" yaml:"-"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Path string `json:"pathname,omitempty"`
|
||||
FormData map[string]interface{} `json:"formdata,omitempty"`
|
||||
Field *ContextField `json:"field,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Config map[string]interface{} `json:"config,omitempty"`
|
||||
Signal interface{} `json:"signal,omitempty"`
|
||||
context.Context `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// ContextField the context field
|
||||
type ContextField struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Bind string `json:"bind,omitempty"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue