[add] neo commands ( 50% )

This commit is contained in:
Max 2023-05-02 19:36:02 +08:00
parent 04ca2aebdc
commit 1f51bdaf8f
16 changed files with 863 additions and 16 deletions

View file

@ -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

View 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
View 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
View 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
}

View 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)
}

View 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
}

View 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)
}

View file

@ -0,0 +1 @@
package driver

View 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"`
}

View file

@ -0,0 +1 @@
package driver

106
neo/command/load.go Normal file
View 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
View 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
View 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
View 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)
}

View file

@ -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
} }

View file

@ -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)
} }