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

@ -33,6 +33,8 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/pkg/safego"
@ -48,7 +50,6 @@ type nodeRunConfig[O any] struct {
maxRetry int64
errProcessType vo.ErrorProcessType
dataOnErr func(ctx context.Context) map[string]any
callbackEnabled bool
preProcessors []func(ctx context.Context, input map[string]any) (map[string]any, error)
postProcessors []func(ctx context.Context, input map[string]any) (map[string]any, error)
streamPreProcessors []func(ctx context.Context,
@ -58,12 +59,14 @@ type nodeRunConfig[O any] struct {
init []func(context.Context) (context.Context, error)
i compose.Invoke[map[string]any, map[string]any, O]
s compose.Stream[map[string]any, map[string]any, O]
c compose.Collect[map[string]any, map[string]any, O]
t compose.Transform[map[string]any, map[string]any, O]
}
func newNodeRunConfig[O any](ns *NodeSchema,
func newNodeRunConfig[O any](ns *schema2.NodeSchema,
i compose.Invoke[map[string]any, map[string]any, O],
s compose.Stream[map[string]any, map[string]any, O],
c compose.Collect[map[string]any, map[string]any, O],
t compose.Transform[map[string]any, map[string]any, O],
opts *newNodeOptions) *nodeRunConfig[O] {
meta := entity.NodeMetaByNodeType(ns.Type)
@ -92,12 +95,12 @@ func newNodeRunConfig[O any](ns *NodeSchema,
keyFinishedMarkerTrimmer(),
}
if meta.PreFillZero {
preProcessors = append(preProcessors, ns.inputValueFiller())
preProcessors = append(preProcessors, inputValueFiller(ns))
}
var postProcessors []func(ctx context.Context, input map[string]any) (map[string]any, error)
if meta.PostFillNil {
postProcessors = append(postProcessors, ns.outputValueFiller())
postProcessors = append(postProcessors, outputValueFiller(ns))
}
streamPreProcessors := []func(ctx context.Context,
@ -110,7 +113,15 @@ func newNodeRunConfig[O any](ns *NodeSchema,
},
}
if meta.PreFillZero {
streamPreProcessors = append(streamPreProcessors, ns.streamInputValueFiller())
streamPreProcessors = append(streamPreProcessors, streamInputValueFiller(ns))
}
if meta.UseCtxCache {
opts.init = append([]func(ctx context.Context) (context.Context, error){
func(ctx context.Context) (context.Context, error) {
return ctxcache.Init(ctx), nil
},
}, opts.init...)
}
opts.init = append(opts.init, func(ctx context.Context) (context.Context, error) {
@ -129,7 +140,6 @@ func newNodeRunConfig[O any](ns *NodeSchema,
maxRetry: maxRetry,
errProcessType: errProcessType,
dataOnErr: dataOnErr,
callbackEnabled: meta.CallbackEnabled,
preProcessors: preProcessors,
postProcessors: postProcessors,
streamPreProcessors: streamPreProcessors,
@ -138,18 +148,21 @@ func newNodeRunConfig[O any](ns *NodeSchema,
init: opts.init,
i: i,
s: s,
c: c,
t: t,
}
}
func newNodeRunConfigWOOpt(ns *NodeSchema,
func newNodeRunConfigWOOpt(ns *schema2.NodeSchema,
i compose.InvokeWOOpt[map[string]any, map[string]any],
s compose.StreamWOOpt[map[string]any, map[string]any],
c compose.CollectWOOpt[map[string]any, map[string]any],
t compose.TransformWOOpts[map[string]any, map[string]any],
opts *newNodeOptions) *nodeRunConfig[any] {
var (
iWO compose.Invoke[map[string]any, map[string]any, any]
sWO compose.Stream[map[string]any, map[string]any, any]
cWO compose.Collect[map[string]any, map[string]any, any]
tWO compose.Transform[map[string]any, map[string]any, any]
)
@ -165,13 +178,19 @@ func newNodeRunConfigWOOpt(ns *NodeSchema,
}
}
if c != nil {
cWO = func(ctx context.Context, in *schema.StreamReader[map[string]any], _ ...any) (out map[string]any, err error) {
return c(ctx, in)
}
}
if t != nil {
tWO = func(ctx context.Context, input *schema.StreamReader[map[string]any], opts ...any) (output *schema.StreamReader[map[string]any], err error) {
return t(ctx, input)
}
}
return newNodeRunConfig[any](ns, iWO, sWO, tWO, opts)
return newNodeRunConfig[any](ns, iWO, sWO, cWO, tWO, opts)
}
type newNodeOptions struct {
@ -180,57 +199,100 @@ type newNodeOptions struct {
init []func(context.Context) (context.Context, error)
}
type newNodeOption func(*newNodeOptions)
func toNode(ns *schema2.NodeSchema, r any) *Node {
iWOpt, _ := r.(nodes.InvokableNodeWOpt)
sWOpt, _ := r.(nodes.StreamableNodeWOpt)
cWOpt, _ := r.(nodes.CollectableNodeWOpt)
tWOpt, _ := r.(nodes.TransformableNodeWOpt)
iWOOpt, _ := r.(nodes.InvokableNode)
sWOOpt, _ := r.(nodes.StreamableNode)
cWOOpt, _ := r.(nodes.CollectableNode)
tWOOpt, _ := r.(nodes.TransformableNode)
func withCallbackInputConverter(f func(context.Context, map[string]any) (map[string]any, error)) newNodeOption {
return func(opts *newNodeOptions) {
opts.callbackInputConverter = f
var wOpt, wOOpt bool
if iWOpt != nil || sWOpt != nil || cWOpt != nil || tWOpt != nil {
wOpt = true
}
}
func withCallbackOutputConverter(f func(context.Context, map[string]any) (*nodes.StructuredCallbackOutput, error)) newNodeOption {
return func(opts *newNodeOptions) {
opts.callbackOutputConverter = f
if iWOOpt != nil || sWOOpt != nil || cWOOpt != nil || tWOOpt != nil {
wOOpt = true
}
if wOpt && wOOpt {
panic("a node's different streaming methods needs to be consistent: " +
"they should ALL have NodeOption or None should have them")
}
if !wOpt && !wOOpt {
panic("a node should implement at least one interface among: InvokableNodeWOpt, StreamableNodeWOpt, CollectableNodeWOpt, TransformableNodeWOpt, InvokableNode, StreamableNode, CollectableNode, TransformableNode")
}
}
func withInit(f func(context.Context) (context.Context, error)) newNodeOption {
return func(opts *newNodeOptions) {
opts.init = append(opts.init, f)
}
}
func invokableNode(ns *NodeSchema, i compose.InvokeWOOpt[map[string]any, map[string]any], opts ...newNodeOption) *Node {
options := &newNodeOptions{}
for _, opt := range opts {
opt(options)
ci, ok := r.(nodes.CallbackInputConverted)
if ok {
options.callbackInputConverter = ci.ToCallbackInput
}
return newNodeRunConfigWOOpt(ns, i, nil, nil, options).toNode()
}
func invokableNodeWO[O any](ns *NodeSchema, i compose.Invoke[map[string]any, map[string]any, O], opts ...newNodeOption) *Node {
options := &newNodeOptions{}
for _, opt := range opts {
opt(options)
co, ok := r.(nodes.CallbackOutputConverted)
if ok {
options.callbackOutputConverter = co.ToCallbackOutput
}
return newNodeRunConfig(ns, i, nil, nil, options).toNode()
}
func invokableTransformableNode(ns *NodeSchema, i compose.InvokeWOOpt[map[string]any, map[string]any],
t compose.TransformWOOpts[map[string]any, map[string]any], opts ...newNodeOption) *Node {
options := &newNodeOptions{}
for _, opt := range opts {
opt(options)
init, ok := r.(nodes.Initializer)
if ok {
options.init = append(options.init, init.Init)
}
return newNodeRunConfigWOOpt(ns, i, nil, t, options).toNode()
}
func invokableStreamableNodeWO[O any](ns *NodeSchema, i compose.Invoke[map[string]any, map[string]any, O], s compose.Stream[map[string]any, map[string]any, O], opts ...newNodeOption) *Node {
options := &newNodeOptions{}
for _, opt := range opts {
opt(options)
if wOpt {
var (
i compose.Invoke[map[string]any, map[string]any, nodes.NodeOption]
s compose.Stream[map[string]any, map[string]any, nodes.NodeOption]
c compose.Collect[map[string]any, map[string]any, nodes.NodeOption]
t compose.Transform[map[string]any, map[string]any, nodes.NodeOption]
)
if iWOpt != nil {
i = iWOpt.Invoke
}
if sWOpt != nil {
s = sWOpt.Stream
}
if cWOpt != nil {
c = cWOpt.Collect
}
if tWOpt != nil {
t = tWOpt.Transform
}
return newNodeRunConfig(ns, i, s, c, t, options).toNode()
}
return newNodeRunConfig(ns, i, s, nil, options).toNode()
var (
i compose.InvokeWOOpt[map[string]any, map[string]any]
s compose.StreamWOOpt[map[string]any, map[string]any]
c compose.CollectWOOpt[map[string]any, map[string]any]
t compose.TransformWOOpts[map[string]any, map[string]any]
)
if iWOOpt != nil {
i = iWOOpt.Invoke
}
if sWOOpt != nil {
s = sWOOpt.Stream
}
if cWOOpt != nil {
c = cWOOpt.Collect
}
if tWOOpt != nil {
t = tWOOpt.Transform
}
return newNodeRunConfigWOOpt(ns, i, s, c, t, options).toNode()
}
func (nc *nodeRunConfig[O]) invoke() func(ctx context.Context, input map[string]any, opts ...O) (output map[string]any, err error) {
@ -375,10 +437,8 @@ func (nc *nodeRunConfig[O]) transform() func(ctx context.Context, input *schema.
func (nc *nodeRunConfig[O]) toNode() *Node {
var opts []compose.LambdaOpt
opts = append(opts, compose.WithLambdaType(string(nc.nodeType)))
opts = append(opts, compose.WithLambdaCallbackEnable(true))
if nc.callbackEnabled {
opts = append(opts, compose.WithLambdaCallbackEnable(true))
}
l, err := compose.AnyLambda(nc.invoke(), nc.stream(), nil, nc.transform(), opts...)
if err != nil {
panic(fmt.Sprintf("failed to create lambda for node %s, err: %v", nc.nodeName, err))
@ -406,9 +466,6 @@ func newNodeRunner[O any](ctx context.Context, cfg *nodeRunConfig[O]) (context.C
}
func (r *nodeRunner[O]) onStart(ctx context.Context, input map[string]any) (context.Context, error) {
if !r.callbackEnabled {
return ctx, nil
}
if r.callbackInputConverter != nil {
convertedInput, err := r.callbackInputConverter(ctx, input)
if err != nil {
@ -425,10 +482,6 @@ func (r *nodeRunner[O]) onStart(ctx context.Context, input map[string]any) (cont
func (r *nodeRunner[O]) onStartStream(ctx context.Context, input *schema.StreamReader[map[string]any]) (
context.Context, *schema.StreamReader[map[string]any], error) {
if !r.callbackEnabled {
return ctx, input, nil
}
if r.callbackInputConverter != nil {
copied := input.Copy(2)
realConverter := func(ctx context.Context) func(map[string]any) (map[string]any, error) {
@ -580,14 +633,10 @@ func (r *nodeRunner[O]) transform(ctx context.Context, input *schema.StreamReade
}
func (r *nodeRunner[O]) onEnd(ctx context.Context, output map[string]any) error {
if r.errProcessType == vo.ErrorProcessTypeExceptionBranch || r.errProcessType == vo.ErrorProcessTypeDefault {
if r.errProcessType == vo.ErrorProcessTypeExceptionBranch || r.errProcessType == vo.ErrorProcessTypeReturnDefaultData {
output["isSuccess"] = true
}
if !r.callbackEnabled {
return nil
}
if r.callbackOutputConverter != nil {
convertedOutput, err := r.callbackOutputConverter(ctx, output)
if err != nil {
@ -603,15 +652,11 @@ func (r *nodeRunner[O]) onEnd(ctx context.Context, output map[string]any) error
func (r *nodeRunner[O]) onEndStream(ctx context.Context, output *schema.StreamReader[map[string]any]) (
*schema.StreamReader[map[string]any], error) {
if r.errProcessType == vo.ErrorProcessTypeExceptionBranch || r.errProcessType == vo.ErrorProcessTypeDefault {
if r.errProcessType == vo.ErrorProcessTypeExceptionBranch || r.errProcessType == vo.ErrorProcessTypeReturnDefaultData {
flag := schema.StreamReaderFromArray([]map[string]any{{"isSuccess": true}})
output = schema.MergeStreamReaders([]*schema.StreamReader[map[string]any]{flag, output})
}
if !r.callbackEnabled {
return output, nil
}
if r.callbackOutputConverter != nil {
copied := output.Copy(2)
realConverter := func(ctx context.Context) func(map[string]any) (*nodes.StructuredCallbackOutput, error) {
@ -632,9 +677,7 @@ func (r *nodeRunner[O]) onEndStream(ctx context.Context, output *schema.StreamRe
func (r *nodeRunner[O]) onError(ctx context.Context, err error) (map[string]any, bool) {
if r.interrupted {
if r.callbackEnabled {
_ = callbacks.OnError(ctx, err)
}
_ = callbacks.OnError(ctx, err)
return nil, false
}
@ -653,22 +696,20 @@ func (r *nodeRunner[O]) onError(ctx context.Context, err error) (map[string]any,
msg := sErr.Msg()
switch r.errProcessType {
case vo.ErrorProcessTypeDefault:
case vo.ErrorProcessTypeReturnDefaultData:
d := r.dataOnErr(ctx)
d["errorBody"] = map[string]any{
"errorMessage": msg,
"errorCode": code,
}
d["isSuccess"] = false
if r.callbackEnabled {
sErr = sErr.ChangeErrLevel(vo.LevelWarn)
sOutput := &nodes.StructuredCallbackOutput{
Output: d,
RawOutput: d,
Error: sErr,
}
_ = callbacks.OnEnd(ctx, sOutput)
sErr = sErr.ChangeErrLevel(vo.LevelWarn)
sOutput := &nodes.StructuredCallbackOutput{
Output: d,
RawOutput: d,
Error: sErr,
}
_ = callbacks.OnEnd(ctx, sOutput)
return d, true
case vo.ErrorProcessTypeExceptionBranch:
s := make(map[string]any)
@ -677,20 +718,16 @@ func (r *nodeRunner[O]) onError(ctx context.Context, err error) (map[string]any,
"errorCode": code,
}
s["isSuccess"] = false
if r.callbackEnabled {
sErr = sErr.ChangeErrLevel(vo.LevelWarn)
sOutput := &nodes.StructuredCallbackOutput{
Output: s,
RawOutput: s,
Error: sErr,
}
_ = callbacks.OnEnd(ctx, sOutput)
sErr = sErr.ChangeErrLevel(vo.LevelWarn)
sOutput := &nodes.StructuredCallbackOutput{
Output: s,
RawOutput: s,
Error: sErr,
}
_ = callbacks.OnEnd(ctx, sOutput)
return s, true
default:
if r.callbackEnabled {
_ = callbacks.OnError(ctx, sErr)
}
_ = callbacks.OnError(ctx, sErr)
return nil, false
}
}