[add] Neo command prepare hook
This commit is contained in:
parent
2a04074a82
commit
f620ddfc60
4 changed files with 211 additions and 3 deletions
4
go.mod
4
go.mod
|
|
@ -22,6 +22,8 @@ require (
|
||||||
github.com/yaoapp/kun v0.9.0
|
github.com/yaoapp/kun v0.9.0
|
||||||
github.com/yaoapp/xun v0.9.0
|
github.com/yaoapp/xun v0.9.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
rogchap.com/v8go v0.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -106,8 +108,6 @@ require (
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // 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/*
|
// go env -w GOPRIVATE=github.com/yaoapp/*
|
||||||
|
|
|
||||||
49
neo/command/prompt.go
Normal file
49
neo/command/prompt.go
Normal 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
|
||||||
|
}
|
||||||
39
neo/command/prompt_test.go
Normal file
39
neo/command/prompt_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,30 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"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"
|
"github.com/yaoapp/yao/neo/message"
|
||||||
|
"rogchap.com/v8go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run the command
|
// Run the command
|
||||||
func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.JSON) int) (interface{}, error) {
|
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)))
|
cb(req.msg().Text(fmt.Sprintf("- Command: %s\n", req.Command.Name)))
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
|
@ -24,6 +39,111 @@ func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.
|
||||||
return nil, nil
|
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 {
|
func (req *Request) msg() *message.JSON {
|
||||||
return message.New().Command(req.Command.Name, req.Command.ID, req.id)
|
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
|
// Done the request done
|
||||||
func (req *Request) Done() {
|
func (req *Request) Done() {
|
||||||
if DefaultStore == nil {
|
if DefaultStore != nil {
|
||||||
DefaultStore.DelRequest(req.sid)
|
DefaultStore.DelRequest(req.sid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue