commit
8a8314a9a2
8 changed files with 400 additions and 0 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/yaoapp/yao/model"
|
||||
"github.com/yaoapp/yao/neo"
|
||||
"github.com/yaoapp/yao/pack"
|
||||
"github.com/yaoapp/yao/pipe"
|
||||
"github.com/yaoapp/yao/plugin"
|
||||
"github.com/yaoapp/yao/query"
|
||||
"github.com/yaoapp/yao/runtime"
|
||||
|
|
@ -210,6 +211,12 @@ func Load(cfg config.Config) (err error) {
|
|||
printErr(cfg.Mode, "Moapi", err)
|
||||
}
|
||||
|
||||
// Load Pipe
|
||||
err = pipe.Load(cfg)
|
||||
if err != nil {
|
||||
printErr(cfg.Mode, "Pipe", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
5
pipe/README.md
Normal file
5
pipe/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Pipe
|
||||
|
||||
**Warning: This function under development and is not yet publicly available.**
|
||||
|
||||
A new workflow orchestration engine designed to solve complex workflow orchestration problems. This is an alternative to **Flow**.
|
||||
76
pipe/context.go
Normal file
76
pipe/context.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package pipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
)
|
||||
|
||||
var contexts = sync.Map{}
|
||||
|
||||
// Create create new context
|
||||
func (pipe *Pipe) Create() *Context {
|
||||
id := uuid.NewString()
|
||||
ctx := &Context{Pipe: pipe, id: uuid.NewString()}
|
||||
ctx.current = 0
|
||||
contexts.Store(id, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Open the context
|
||||
func Open(id string) *Context {
|
||||
ctx, ok := contexts.Load(id)
|
||||
if !ok {
|
||||
exception.New("pipe: %s not found", 404, id).Throw()
|
||||
}
|
||||
return ctx.(*Context)
|
||||
}
|
||||
|
||||
// Close the context
|
||||
func Close(id string) {
|
||||
contexts.Delete(id)
|
||||
}
|
||||
|
||||
// Run the pipe
|
||||
func (ctx *Context) Run(args ...any) any {
|
||||
v, err := ctx.Exec(args...)
|
||||
if err != nil {
|
||||
exception.New("pipe: %s %s", 500, ctx.Name, err).Throw()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ID the context id
|
||||
func (ctx *Context) ID() string {
|
||||
return ctx.id
|
||||
}
|
||||
|
||||
// Exec and return error
|
||||
func (ctx *Context) Exec(args ...any) (any, error) {
|
||||
fmt.Printf("name: %v\n", ctx.Name)
|
||||
fmt.Printf("global: %v\n", ctx.global)
|
||||
fmt.Printf("sid: %v\n", ctx.sid)
|
||||
fmt.Printf("whitelist: %v\n", ctx.Whitelist)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// With with the context
|
||||
func (ctx *Context) With(context context.Context) *Context {
|
||||
ctx.context = context
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithGlobal with the global data
|
||||
func (ctx *Context) WithGlobal(data map[string]interface{}) *Context {
|
||||
ctx.global = data
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithSid with the sid
|
||||
func (ctx *Context) WithSid(sid string) *Context {
|
||||
ctx.sid = sid
|
||||
return ctx
|
||||
}
|
||||
1
pipe/interface.go
Normal file
1
pipe/interface.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package pipe
|
||||
105
pipe/json.go
Normal file
105
pipe/json.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package pipe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// UnmarshalJSON Custom JSON unmarshal function
|
||||
func (whitelist *Whitelist) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var list any
|
||||
err := jsoniter.Unmarshal(data, &list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := list.(type) {
|
||||
case []string:
|
||||
list := map[string]bool{}
|
||||
for _, name := range v {
|
||||
list[name] = true
|
||||
}
|
||||
*whitelist = list
|
||||
|
||||
case []interface{}:
|
||||
list := map[string]bool{}
|
||||
for _, name := range v {
|
||||
list[fmt.Sprint(name)] = true
|
||||
}
|
||||
*whitelist = list
|
||||
|
||||
case map[string]interface{}:
|
||||
list := map[string]bool{}
|
||||
for name := range v {
|
||||
list[name] = true
|
||||
}
|
||||
*whitelist = list
|
||||
|
||||
default:
|
||||
return fmt.Errorf("whitelist type error: %#v", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON Custom JSON unmarshal function
|
||||
func (input Input) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var res any
|
||||
err := jsoniter.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := res.(type) {
|
||||
case []string:
|
||||
input = []any{}
|
||||
for _, name := range v {
|
||||
input = append(input, name)
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
input = v
|
||||
|
||||
case string:
|
||||
input = []any{v}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("input type error: %#v", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// UnmarshalJSON Custom JSON unmarshal function
|
||||
func (args Args) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var res any
|
||||
err := jsoniter.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := res.(type) {
|
||||
case []string:
|
||||
args = []any{}
|
||||
for _, name := range v {
|
||||
args = append(args, name)
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
args = v
|
||||
|
||||
case string:
|
||||
args = []any{v}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("input type error: %#v", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
87
pipe/pipe.go
Normal file
87
pipe/pipe.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package pipe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/yaoapp/gou/application"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
var pipes = map[string]*Pipe{}
|
||||
|
||||
// Load the pipe
|
||||
func Load(cfg config.Config) error {
|
||||
|
||||
exts := []string{"*.pip.yao", "*.pipe.yao"}
|
||||
errs := []error{}
|
||||
err := application.App.Walk("pipes", func(root, file string, isdir bool) error {
|
||||
if isdir {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := share.ID(root, file)
|
||||
pipe, err := NewFile(file, root)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return err
|
||||
}
|
||||
|
||||
Set(id, pipe)
|
||||
return err
|
||||
}, exts...)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// New create Pipe
|
||||
func New(source []byte) (*Pipe, error) {
|
||||
pipe := Pipe{}
|
||||
err := application.Parse("<source>", source, &pipe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pipe, nil
|
||||
}
|
||||
|
||||
// NewFile create pipe from file
|
||||
func NewFile(file string, root string) (*Pipe, error) {
|
||||
source, err := application.App.Read(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := share.ID(root, file)
|
||||
pipe := Pipe{ID: id}
|
||||
err = application.Parse(file, source, &pipe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pipe, nil
|
||||
}
|
||||
|
||||
// Set pipe to
|
||||
func Set(id string, pipe *Pipe) {
|
||||
pipes[id] = pipe
|
||||
}
|
||||
|
||||
// Remove the pipe
|
||||
func Remove(id string) {
|
||||
if _, has := pipes[id]; has {
|
||||
delete(pipes, id)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the pipe
|
||||
func Get(id string) (*Pipe, error) {
|
||||
if pipe, has := pipes[id]; has {
|
||||
return pipe, nil
|
||||
}
|
||||
return nil, fmt.Errorf("pipe %s not found", id)
|
||||
}
|
||||
42
pipe/pipe_test.go
Normal file
42
pipe/pipe_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package pipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/gou/session"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/test"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
prepare(t)
|
||||
defer test.Clean()
|
||||
translator, err := Get("translator")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sid := session.ID()
|
||||
context, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ctx := translator.
|
||||
Create().
|
||||
With(context).
|
||||
WithGlobal(map[string]interface{}{"foo": "bar"}).
|
||||
WithSid(sid)
|
||||
defer Close(ctx.ID())
|
||||
|
||||
assert.NotPanics(t, func() { ctx.Run() })
|
||||
}
|
||||
|
||||
func prepare(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
err := Load(config.Conf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
77
pipe/types.go
Normal file
77
pipe/types.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package pipe
|
||||
|
||||
import "context"
|
||||
|
||||
// Pipe the pipe
|
||||
type Pipe struct {
|
||||
ID string
|
||||
Name string `json:"name"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Hooks *Hooks `json:"hooks,omitempty"`
|
||||
Output any `json:"output,omitempty"` // $output
|
||||
Input Input `json:"input,omitempty"` // $input
|
||||
Whitelist Whitelist `json:"whitelist,omitempty"` // the process whitelist
|
||||
|
||||
}
|
||||
|
||||
// Context the Context
|
||||
type Context struct {
|
||||
*Pipe
|
||||
id string
|
||||
context context.Context
|
||||
global map[string]interface{} // $global
|
||||
sid string // $sid
|
||||
current int // current position
|
||||
}
|
||||
|
||||
// Hooks the Hooks
|
||||
type Hooks struct {
|
||||
Progress string `json:"progress,omitempty"`
|
||||
}
|
||||
|
||||
// Node the pip node
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type,omitempty"` // user-input, ai, process, switch, request
|
||||
Label string `json:"label,omitempty"` // Display
|
||||
Process *Process `json:"process,omitempty"` // Yao Process
|
||||
Prompts []Prompt `json:"prompts,omitempty"` // AI prompts
|
||||
Request *Request `json:"request,omitempty"` // Http Request
|
||||
Interface string `json:"interface,omitempty"` // User Interface command-line, web, app, wxapp ...
|
||||
Case map[string]CaseSection `json:"case,omitempty"` // Switch
|
||||
Input Input `json:"input,omitempty"` // $in
|
||||
Output any `json:"output,omitempty"` // $out
|
||||
}
|
||||
|
||||
// Whitelist the Whitelist
|
||||
type Whitelist map[string]bool
|
||||
|
||||
// Input the input
|
||||
type Input []any
|
||||
|
||||
// Args the args
|
||||
type Args []any
|
||||
|
||||
// CaseSection the switch case section
|
||||
type CaseSection struct {
|
||||
Input Input `json:"input,omitempty"` // $in
|
||||
Output any `json:"output,omitempty"` // $out
|
||||
Nodes []Node `json:"nodes,omitempty"` // $out
|
||||
Goto string `json:"goto,omitempty"` // goto node name / EOF
|
||||
}
|
||||
|
||||
// Prompt the switch
|
||||
type Prompt struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Process the switch
|
||||
type Process struct {
|
||||
Name string `json:"name"`
|
||||
Args Args `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// Request the request
|
||||
type Request struct{}
|
||||
Loading…
Add table
Reference in a new issue