refactor: how to add a node type in workflow (#558)

This commit is contained in:
shentongmartin
2025-08-05 14:02:33 +08:00
committed by GitHub
parent 5dafd81a3f
commit bb6ff0026b
96 changed files with 8305 additions and 8717 deletions

View File

@@ -22,7 +22,11 @@ import (
"reflect"
"strings"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
)
@@ -34,42 +38,92 @@ const (
)
type Config struct {
Type Type `json:"type"`
Tpl string `json:"tpl"`
ConcatChar string `json:"concatChar"`
Separators []string `json:"separator"`
FullSources map[string]*nodes.SourceInfo `json:"fullSources"`
Type Type `json:"type"`
Tpl string `json:"tpl"`
ConcatChar string `json:"concatChar"`
Separators []string `json:"separator"`
}
type TextProcessor struct {
config *Config
}
func NewTextProcessor(_ context.Context, cfg *Config) (*TextProcessor, error) {
if cfg == nil {
return nil, fmt.Errorf("config requried")
func (c *Config) Adapt(ctx context.Context, n *vo.Node, opts ...nodes.AdaptOption) (*schema.NodeSchema, error) {
ns := &schema.NodeSchema{
Key: vo.NodeKey(n.ID),
Type: entity.NodeTypeTextProcessor,
Name: n.Data.Meta.Title,
Configs: c,
}
if cfg.Type == ConcatText && len(cfg.Tpl) == 0 {
if n.Data.Inputs.Method == vo.Concat {
c.Type = ConcatText
params := n.Data.Inputs.ConcatParams
for _, param := range params {
if param.Name == "concatResult" {
c.Tpl = param.Input.Value.Content.(string)
} else if param.Name == "arrayItemConcatChar" {
c.ConcatChar = param.Input.Value.Content.(string)
}
}
} else if n.Data.Inputs.Method == vo.Split {
c.Type = SplitText
params := n.Data.Inputs.SplitParams
separators := make([]string, 0, len(params))
for _, param := range params {
if param.Name == "delimiters" {
delimiters := param.Input.Value.Content.([]any)
for _, d := range delimiters {
separators = append(separators, d.(string))
}
}
}
c.Separators = separators
} else {
return nil, fmt.Errorf("not supported method: %s", n.Data.Inputs.Method)
}
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
return nil, err
}
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
return nil, err
}
return ns, nil
}
func (c *Config) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
if c.Type == ConcatText && len(c.Tpl) == 0 {
return nil, fmt.Errorf("config tpl requried")
}
return &TextProcessor{
config: cfg,
typ: c.Type,
tpl: c.Tpl,
concatChar: c.ConcatChar,
separators: c.Separators,
fullSources: ns.FullSources,
}, nil
}
type TextProcessor struct {
typ Type
tpl string
concatChar string
separators []string
fullSources map[string]*schema.SourceInfo
}
const OutputKey = "output"
func (t *TextProcessor) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
switch t.config.Type {
switch t.typ {
case ConcatText:
arrayRenderer := func(i any) (string, error) {
vs := i.([]any)
return join(vs, t.config.ConcatChar)
return join(vs, t.concatChar)
}
result, err := nodes.Render(ctx, t.config.Tpl, input, t.config.FullSources,
result, err := nodes.Render(ctx, t.tpl, input, t.fullSources,
nodes.WithCustomRender(reflect.TypeOf([]any{}), arrayRenderer))
if err != nil {
return nil, err
@@ -86,9 +140,9 @@ func (t *TextProcessor) Invoke(ctx context.Context, input map[string]any) (map[s
if !ok {
return nil, fmt.Errorf("input string field must string type but got %T", valueString)
}
values := strings.Split(valueString, t.config.Separators[0])
values := strings.Split(valueString, t.separators[0])
// Iterate over each delimiter
for _, sep := range t.config.Separators[1:] {
for _, sep := range t.separators[1:] {
var tempParts []string
for _, part := range values {
tempParts = append(tempParts, strings.Split(part, sep)...)
@@ -102,7 +156,7 @@ func (t *TextProcessor) Invoke(ctx context.Context, input map[string]any) (map[s
return map[string]any{OutputKey: anyValues}, nil
default:
return nil, fmt.Errorf("not support type %s", t.config.Type)
return nil, fmt.Errorf("not support type %s", t.typ)
}
}

View File

@@ -21,6 +21,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
)
func TestNewTextProcessorNodeGenerator(t *testing.T) {
@@ -30,10 +32,10 @@ func TestNewTextProcessorNodeGenerator(t *testing.T) {
Type: SplitText,
Separators: []string{",", "|", "."},
}
p, err := NewTextProcessor(ctx, cfg)
p, err := cfg.Build(ctx, &schema2.NodeSchema{})
assert.NoError(t, err)
result, err := p.Invoke(ctx, map[string]any{
result, err := p.(*TextProcessor).Invoke(ctx, map[string]any{
"String": "a,b|c.d,e|f|g",
})
@@ -60,9 +62,9 @@ func TestNewTextProcessorNodeGenerator(t *testing.T) {
ConcatChar: `\t`,
Tpl: "fx{{a}}=={{b.b1}}=={{b.b2[1]}}=={{c}}",
}
p, err := NewTextProcessor(context.Background(), cfg)
p, err := cfg.Build(context.Background(), &schema2.NodeSchema{})
result, err := p.Invoke(ctx, in)
result, err := p.(*TextProcessor).Invoke(ctx, in)
assert.NoError(t, err)
assert.Equal(t, result["output"], `fx1\t{"1":1}\t3==1\t2\t3==2=={"c1":"1"}`)
})