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

@@ -25,6 +25,10 @@ import (
"golang.org/x/exp/maps"
code2 "github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/code"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
"github.com/coze-dev/coze-studio/backend/infra/contract/coderunner"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
@@ -113,50 +117,77 @@ var pythonThirdPartyWhitelist = map[string]struct{}{
}
type Config struct {
Code string
Language coderunner.Language
OutputConfig map[string]*vo.TypeInfo
Runner coderunner.Runner
Code string
Language coderunner.Language
Runner coderunner.Runner
}
type CodeRunner struct {
config *Config
importError error
func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
ns := &schema.NodeSchema{
Key: vo.NodeKey(n.ID),
Type: entity.NodeTypeCodeRunner,
Name: n.Data.Meta.Title,
Configs: c,
}
inputs := n.Data.Inputs
code := inputs.Code
c.Code = code
language, err := convertCodeLanguage(inputs.Language)
if err != nil {
return nil, err
}
c.Language = language
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 NewCodeRunner(ctx context.Context, cfg *Config) (*CodeRunner, error) {
if cfg == nil {
return nil, errors.New("cfg is required")
func convertCodeLanguage(l int64) (coderunner.Language, error) {
switch l {
case 5:
return coderunner.JavaScript, nil
case 3:
return coderunner.Python, nil
default:
return "", fmt.Errorf("invalid language: %d", l)
}
}
if cfg.Language == "" {
return nil, errors.New("language is required")
}
func (c *Config) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
if cfg.Code == "" {
return nil, errors.New("code is required")
}
if cfg.Language != coderunner.Python {
if c.Language != coderunner.Python {
return nil, errors.New("only support python language")
}
if len(cfg.OutputConfig) == 0 {
return nil, errors.New("output config is required")
}
importErr := validatePythonImports(c.Code)
if cfg.Runner == nil {
return nil, errors.New("run coder is required")
}
importErr := validatePythonImports(cfg.Code)
return &CodeRunner{
config: cfg,
importError: importErr,
return &Runner{
code: c.Code,
language: c.Language,
outputConfig: ns.OutputTypes,
runner: code2.GetCodeRunner(),
importError: importErr,
}, nil
}
type Runner struct {
outputConfig map[string]*vo.TypeInfo
code string
language coderunner.Language
runner coderunner.Runner
importError error
}
func validatePythonImports(code string) error {
imports := parsePythonImports(code)
importErrors := make([]string, 0)
@@ -191,11 +222,11 @@ func validatePythonImports(code string) error {
return nil
}
func (c *CodeRunner) RunCode(ctx context.Context, input map[string]any) (ret map[string]any, err error) {
func (c *Runner) Invoke(ctx context.Context, input map[string]any) (ret map[string]any, err error) {
if c.importError != nil {
return nil, vo.WrapError(errno.ErrCodeExecuteFail, c.importError, errorx.KV("detail", c.importError.Error()))
}
response, err := c.config.Runner.Run(ctx, &coderunner.RunRequest{Code: c.config.Code, Language: c.config.Language, Params: input})
response, err := c.runner.Run(ctx, &coderunner.RunRequest{Code: c.code, Language: c.language, Params: input})
if err != nil {
return nil, vo.WrapError(errno.ErrCodeExecuteFail, err, errorx.KV("detail", err.Error()))
}
@@ -203,7 +234,7 @@ func (c *CodeRunner) RunCode(ctx context.Context, input map[string]any) (ret map
result := response.Result
ctxcache.Store(ctx, coderRunnerRawOutputCtxKey, result)
output, ws, err := nodes.ConvertInputs(ctx, result, c.config.OutputConfig)
output, ws, err := nodes.ConvertInputs(ctx, result, c.outputConfig)
if err != nil {
return nil, vo.WrapIfNeeded(errno.ErrCodeExecuteFail, err, errorx.KV("detail", err.Error()))
}
@@ -217,7 +248,7 @@ func (c *CodeRunner) RunCode(ctx context.Context, input map[string]any) (ret map
}
func (c *CodeRunner) ToCallbackOutput(ctx context.Context, output map[string]any) (*nodes.StructuredCallbackOutput, error) {
func (c *Runner) ToCallbackOutput(ctx context.Context, output map[string]any) (*nodes.StructuredCallbackOutput, error) {
rawOutput, ok := ctxcache.Get[map[string]any](ctx, coderRunnerRawOutputCtxKey)
if !ok {
return nil, errors.New("raw output config is required")

View File

@@ -75,30 +75,29 @@ async def main(args:Args)->Output:
mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
ctx := t.Context()
c := &CodeRunner{
config: &Config{
Language: coderunner.Python,
Code: codeTpl,
OutputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": &vo.TypeInfo{Type: vo.DataTypeString},
"key32": &vo.TypeInfo{Type: vo.DataTypeString},
"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": &vo.TypeInfo{Type: vo.DataTypeString},
"key342": &vo.TypeInfo{Type: vo.DataTypeString},
}},
},
},
"key4": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeObject}},
c := &Runner{
language: coderunner.Python,
code: codeTpl,
outputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": {Type: vo.DataTypeString},
"key32": {Type: vo.DataTypeString},
"key33": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": {Type: vo.DataTypeString},
"key342": {Type: vo.DataTypeString},
}},
},
Runner: mockRunner,
},
"key4": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeObject}},
},
runner: mockRunner,
}
ret, err := c.RunCode(ctx, map[string]any{
ret, err := c.Invoke(ctx, map[string]any{
"input": "1123",
})
@@ -145,38 +144,36 @@ async def main(args:Args)->Output:
mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
ctx := t.Context()
c := &CodeRunner{
config: &Config{
Code: codeTpl,
Language: coderunner.Python,
OutputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": &vo.TypeInfo{Type: vo.DataTypeString},
"key32": &vo.TypeInfo{Type: vo.DataTypeString},
"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": &vo.TypeInfo{Type: vo.DataTypeString},
"key342": &vo.TypeInfo{Type: vo.DataTypeString},
}},
c := &Runner{
code: codeTpl,
language: coderunner.Python,
outputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeString}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": {Type: vo.DataTypeString},
"key32": {Type: vo.DataTypeString},
"key33": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": {Type: vo.DataTypeString},
"key342": {Type: vo.DataTypeString},
}},
"key4": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": &vo.TypeInfo{Type: vo.DataTypeString},
"key32": &vo.TypeInfo{Type: vo.DataTypeString},
"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": &vo.TypeInfo{Type: vo.DataTypeString},
"key342": &vo.TypeInfo{Type: vo.DataTypeString},
},
}},
}},
"key4": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": {Type: vo.DataTypeString},
"key32": {Type: vo.DataTypeString},
"key33": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": {Type: vo.DataTypeString},
"key342": {Type: vo.DataTypeString},
},
}},
},
Runner: mockRunner,
},
runner: mockRunner,
}
ret, err := c.RunCode(ctx, map[string]any{
ret, err := c.Invoke(ctx, map[string]any{
"input": "1123",
})
@@ -219,30 +216,28 @@ async def main(args:Args)->Output:
}
mockRunner.EXPECT().Run(gomock.Any(), gomock.Any()).Return(response, nil)
c := &CodeRunner{
config: &Config{
Code: codeTpl,
Language: coderunner.Python,
OutputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": &vo.TypeInfo{Type: vo.DataTypeString},
"key32": &vo.TypeInfo{Type: vo.DataTypeString},
"key33": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": &vo.TypeInfo{Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": &vo.TypeInfo{Type: vo.DataTypeString},
"key342": &vo.TypeInfo{Type: vo.DataTypeString},
"key343": &vo.TypeInfo{Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
}},
},
},
c := &Runner{
code: codeTpl,
language: coderunner.Python,
outputConfig: map[string]*vo.TypeInfo{
"key0": {Type: vo.DataTypeInteger},
"key1": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key2": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key3": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key31": {Type: vo.DataTypeString},
"key32": {Type: vo.DataTypeString},
"key33": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
"key34": {Type: vo.DataTypeObject, Properties: map[string]*vo.TypeInfo{
"key341": {Type: vo.DataTypeString},
"key342": {Type: vo.DataTypeString},
"key343": {Type: vo.DataTypeArray, ElemTypeInfo: &vo.TypeInfo{Type: vo.DataTypeNumber}},
}},
},
},
Runner: mockRunner,
},
runner: mockRunner,
}
ret, err := c.RunCode(ctx, map[string]any{
ret, err := c.Invoke(ctx, map[string]any{
"input": "1123",
})