[add] Neo command prepare hook

This commit is contained in:
Max 2023-05-05 11:04:54 +08:00
parent 2a04074a82
commit f620ddfc60
4 changed files with 211 additions and 3 deletions

4
go.mod
View file

@ -22,6 +22,8 @@ require (
github.com/yaoapp/kun v0.9.0
github.com/yaoapp/xun v0.9.0
golang.org/x/crypto v0.7.0
gopkg.in/yaml.v3 v3.0.1
rogchap.com/v8go v0.8.0
)
require (
@ -106,8 +108,6 @@ require (
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1
rogchap.com/v8go v0.8.0 // indirect
)
// go env -w GOPRIVATE=github.com/yaoapp/*

49
neo/command/prompt.go Normal file
View file

@ -0,0 +1,49 @@
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
}

View file

@ -0,0 +1,39 @@
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)
}

View file

@ -2,15 +2,30 @@ package command
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/kun/maps"
"github.com/yaoapp/kun/utils"
"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) (interface{}, error) {
prompts, err := req.prepare(messages, cb)
if err != nil {
cb(req.msg().Text(fmt.Sprintf("Prepare Before Error: %s\n", err.Error())))
cb(message.New().Done())
req.Done()
return nil, err
}
utils.Dump(prompts)
cb(req.msg().Text(fmt.Sprintf("- Command: %s\n", req.Command.Name)))
time.Sleep(200 * time.Millisecond)
@ -24,6 +39,111 @@ func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.
return nil, nil
}
// RunPrepare the command
func (req *Request) prepare(messages []map[string]interface{}, cb func(msg *message.JSON) int) ([]Prompt, error) {
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)
}
}
return prompts, 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)
}
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], ".")
script, err := v8.Select(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()
// make a new bridge function ssWrite
ssWriteT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) != 1 {
return v8go.Null(v8ctx.Isolate())
}
text := args[0].String()
cb(req.msg().Text(text))
return v8go.Null(v8ctx.Isolate())
})
// make a new bridge function done
doneT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) == 1 {
text := args[0].String()
cb(req.msg().Text(text))
}
cb(message.New().Done())
req.Done()
return v8go.Null(v8ctx.Isolate())
})
v8ctx.Global().Set("ssWrite", ssWriteT.GetFunction(v8ctx.Context))
v8ctx.Global().Set("done", doneT.GetFunction(v8ctx.Context))
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)
}
@ -70,7 +190,7 @@ func (cmd *Command) NewRequest(ctx Context) (*Request, error) {
// Done the request done
func (req *Request) Done() {
if DefaultStore == nil {
if DefaultStore != nil {
DefaultStore.DelRequest(req.sid)
}
}