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

@@ -0,0 +1,663 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package convert
import (
"fmt"
"strconv"
"strings"
einoCompose "github.com/cloudwego/eino/compose"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func CanvasVariableToTypeInfo(v *vo.Variable) (*vo.TypeInfo, error) {
tInfo := &vo.TypeInfo{
Required: v.Required,
Desc: v.Description,
}
switch v.Type {
case vo.VariableTypeString:
switch v.AssistType {
case vo.AssistTypeTime:
tInfo.Type = vo.DataTypeTime
case vo.AssistTypeNotSet:
tInfo.Type = vo.DataTypeString
default:
fileType, ok := assistTypeToFileType(v.AssistType)
if ok {
tInfo.Type = vo.DataTypeFile
tInfo.FileType = &fileType
} else {
return nil, fmt.Errorf("unsupported assist type: %v", v.AssistType)
}
}
case vo.VariableTypeInteger:
tInfo.Type = vo.DataTypeInteger
case vo.VariableTypeFloat:
tInfo.Type = vo.DataTypeNumber
case vo.VariableTypeBoolean:
tInfo.Type = vo.DataTypeBoolean
case vo.VariableTypeObject:
tInfo.Type = vo.DataTypeObject
tInfo.Properties = make(map[string]*vo.TypeInfo)
if v.Schema != nil {
for _, subVAny := range v.Schema.([]any) {
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := CanvasVariableToTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.Properties[subV.Name] = subTInfo
}
}
case vo.VariableTypeList:
tInfo.Type = vo.DataTypeArray
subVAny := v.Schema
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := CanvasVariableToTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.ElemTypeInfo = subTInfo
default:
return nil, fmt.Errorf("unsupported variable type: %s", v.Type)
}
return tInfo, nil
}
func CanvasBlockInputToTypeInfo(b *vo.BlockInput) (tInfo *vo.TypeInfo, err error) {
defer func() {
if err != nil {
err = vo.WrapIfNeeded(errno.ErrSchemaConversionFail, err)
}
}()
tInfo = &vo.TypeInfo{}
if b == nil {
return tInfo, nil
}
switch b.Type {
case vo.VariableTypeString:
switch b.AssistType {
case vo.AssistTypeTime:
tInfo.Type = vo.DataTypeTime
case vo.AssistTypeNotSet:
tInfo.Type = vo.DataTypeString
default:
fileType, ok := assistTypeToFileType(b.AssistType)
if ok {
tInfo.Type = vo.DataTypeFile
tInfo.FileType = &fileType
} else {
return nil, fmt.Errorf("unsupported assist type: %v", b.AssistType)
}
}
case vo.VariableTypeInteger:
tInfo.Type = vo.DataTypeInteger
case vo.VariableTypeFloat:
tInfo.Type = vo.DataTypeNumber
case vo.VariableTypeBoolean:
tInfo.Type = vo.DataTypeBoolean
case vo.VariableTypeObject:
tInfo.Type = vo.DataTypeObject
tInfo.Properties = make(map[string]*vo.TypeInfo)
if b.Schema != nil {
for _, subVAny := range b.Schema.([]any) {
if b.Value.Type == vo.BlockInputValueTypeRef {
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := CanvasVariableToTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.Properties[subV.Name] = subTInfo
} else if b.Value.Type == vo.BlockInputValueTypeObjectRef {
subV, err := parseParam(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := CanvasBlockInputToTypeInfo(subV.Input)
if err != nil {
return nil, err
}
tInfo.Properties[subV.Name] = subTInfo
}
}
}
case vo.VariableTypeList:
tInfo.Type = vo.DataTypeArray
subVAny := b.Schema
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := CanvasVariableToTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.ElemTypeInfo = subTInfo
default:
return nil, fmt.Errorf("unsupported variable type: %s", b.Type)
}
return tInfo, nil
}
func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, parentNode *vo.Node) (sources []*vo.FieldInfo, err error) {
value := b.Value
if value == nil {
return nil, fmt.Errorf("input %v has no value, type= %s", path, b.Type)
}
switch value.Type {
case vo.BlockInputValueTypeObjectRef:
sc := b.Schema
if sc == nil {
return nil, fmt.Errorf("input %v has no schema, type= %s", path, b.Type)
}
paramList, ok := sc.([]any)
if !ok {
return nil, fmt.Errorf("input %v schema not []any, type= %T", path, sc)
}
for i := range paramList {
paramAny := paramList[i]
param, err := parseParam(paramAny)
if err != nil {
return nil, err
}
copied := make([]string, len(path))
copy(copied, path)
subFieldInfo, err := CanvasBlockInputToFieldInfo(param.Input, append(copied, param.Name), parentNode)
if err != nil {
return nil, err
}
sources = append(sources, subFieldInfo...)
}
return sources, nil
case vo.BlockInputValueTypeLiteral:
content := value.Content
if content == nil {
return nil, fmt.Errorf("input %v is literal but has no value, type= %s", path, b.Type)
}
switch b.Type {
case vo.VariableTypeObject:
m := make(map[string]any)
if err = sonic.UnmarshalString(content.(string), &m); err != nil {
return nil, err
}
content = m
case vo.VariableTypeList:
l := make([]any, 0)
if err = sonic.UnmarshalString(content.(string), &l); err != nil {
return nil, err
}
content = l
case vo.VariableTypeInteger:
switch content.(type) {
case string:
content, err = strconv.ParseInt(content.(string), 10, 64)
if err != nil {
return nil, err
}
case int64:
content = content.(int64)
case float64:
content = int64(content.(float64))
default:
return nil, fmt.Errorf("unsupported variable type fot integer: %s", b.Type)
}
case vo.VariableTypeFloat:
switch content.(type) {
case string:
content, err = strconv.ParseFloat(content.(string), 64)
if err != nil {
return nil, err
}
case int64:
content = float64(content.(int64))
case float64:
content = content.(float64)
default:
return nil, fmt.Errorf("unsupported variable type for float: %s", b.Type)
}
case vo.VariableTypeBoolean:
switch content.(type) {
case string:
content, err = strconv.ParseBool(content.(string))
if err != nil {
return nil, err
}
case bool:
content = content.(bool)
default:
return nil, fmt.Errorf("unsupported variable type for boolean: %s", b.Type)
}
default:
}
return []*vo.FieldInfo{
{
Path: path,
Source: vo.FieldSource{
Val: content,
},
},
}, nil
case vo.BlockInputValueTypeRef:
content := value.Content
if content == nil {
return nil, fmt.Errorf("input %v is literal but has no value, type= %s", path, b.Type)
}
ref, err := parseBlockInputRef(content)
if err != nil {
return nil, err
}
fieldSource, err := CanvasBlockInputRefToFieldSource(ref)
if err != nil {
return nil, err
}
if parentNode != nil {
if fieldSource.Ref != nil && len(fieldSource.Ref.FromNodeKey) > 0 && fieldSource.Ref.FromNodeKey == vo.NodeKey(parentNode.ID) {
varRoot := fieldSource.Ref.FromPath[0]
if parentNode.Data.Inputs.Loop != nil {
for _, p := range parentNode.Data.Inputs.VariableParameters {
if p.Name == varRoot {
fieldSource.Ref.FromNodeKey = ""
pi := vo.ParentIntermediate
fieldSource.Ref.VariableType = &pi
}
}
}
}
}
return []*vo.FieldInfo{
{
Path: path,
Source: *fieldSource,
},
}, nil
default:
return nil, fmt.Errorf("unsupported value type: %s for blockInput type= %s", value.Type, b.Type)
}
}
func parseBlockInputRef(content any) (*vo.BlockInputReference, error) {
if bi, ok := content.(*vo.BlockInputReference); ok {
return bi, nil
}
m, ok := content.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid content type: %T when parse BlockInputRef", content)
}
marshaled, err := sonic.Marshal(m)
if err != nil {
return nil, err
}
p := &vo.BlockInputReference{}
if err := sonic.Unmarshal(marshaled, p); err != nil {
return nil, err
}
return p, nil
}
func parseParam(v any) (*vo.Param, error) {
if pa, ok := v.(*vo.Param); ok {
return pa, nil
}
m, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid content type: %T when parse Param", v)
}
marshaled, err := sonic.Marshal(m)
if err != nil {
return nil, err
}
p := &vo.Param{}
if err := sonic.Unmarshal(marshaled, p); err != nil {
return nil, err
}
return p, nil
}
func CanvasBlockInputRefToFieldSource(r *vo.BlockInputReference) (*vo.FieldSource, error) {
switch r.Source {
case vo.RefSourceTypeBlockOutput:
if len(r.BlockID) == 0 {
return nil, fmt.Errorf("invalid BlockInputReference = %+v, BlockID is empty when source is block output", r)
}
parts := strings.Split(r.Name, ".") // an empty r.Name signals an all-to-all mapping
return &vo.FieldSource{
Ref: &vo.Reference{
FromNodeKey: vo.NodeKey(r.BlockID),
FromPath: parts,
},
}, nil
case vo.RefSourceTypeGlobalApp, vo.RefSourceTypeGlobalSystem, vo.RefSourceTypeGlobalUser:
if len(r.Path) == 0 {
return nil, fmt.Errorf("invalid BlockInputReference = %+v, Path is empty when source is variables", r)
}
var varType vo.GlobalVarType
switch r.Source {
case vo.RefSourceTypeGlobalApp:
varType = vo.GlobalAPP
case vo.RefSourceTypeGlobalSystem:
varType = vo.GlobalSystem
case vo.RefSourceTypeGlobalUser:
varType = vo.GlobalUser
default:
return nil, fmt.Errorf("invalid BlockInputReference = %+v, Source is invalid", r)
}
return &vo.FieldSource{
Ref: &vo.Reference{
VariableType: &varType,
FromPath: r.Path,
},
}, nil
default:
return nil, fmt.Errorf("unsupported ref source type: %s", r.Source)
}
}
func assistTypeToFileType(a vo.AssistType) (vo.FileSubType, bool) {
switch a {
case vo.AssistTypeNotSet:
return "", false
case vo.AssistTypeTime:
return "", false
case vo.AssistTypeImage:
return vo.FileTypeImage, true
case vo.AssistTypeAudio:
return vo.FileTypeAudio, true
case vo.AssistTypeVideo:
return vo.FileTypeVideo, true
case vo.AssistTypeDefault:
return vo.FileTypeDefault, true
case vo.AssistTypeDoc:
return vo.FileTypeDocument, true
case vo.AssistTypeExcel:
return vo.FileTypeExcel, true
case vo.AssistTypeCode:
return vo.FileTypeCode, true
case vo.AssistTypePPT:
return vo.FileTypePPT, true
case vo.AssistTypeTXT:
return vo.FileTypeTxt, true
case vo.AssistTypeSvg:
return vo.FileTypeSVG, true
case vo.AssistTypeVoice:
return vo.FileTypeVoice, true
case vo.AssistTypeZip:
return vo.FileTypeZip, true
default:
panic("impossible")
}
}
func SetInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error {
if n.Data.Inputs == nil {
return nil
}
inputParams := n.Data.Inputs.InputParameters
if len(inputParams) == 0 {
return nil
}
for _, param := range inputParams {
name := param.Name
tInfo, err := CanvasBlockInputToTypeInfo(param.Input)
if err != nil {
return err
}
ns.SetInputType(name, tInfo)
sources, err := CanvasBlockInputToFieldInfo(param.Input, einoCompose.FieldPath{name}, n.Parent())
if err != nil {
return err
}
ns.AddInputSource(sources...)
}
return nil
}
func SetOutputTypesForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error {
for _, vAny := range n.Data.Outputs {
v, err := vo.ParseVariable(vAny)
if err != nil {
return err
}
tInfo, err := CanvasVariableToTypeInfo(v)
if err != nil {
return err
}
if v.ReadOnly {
if v.Name == "errorBody" { // reserved output fields when exception happens
continue
}
}
ns.SetOutputType(v.Name, tInfo)
}
return nil
}
func SetOutputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error {
for _, vAny := range n.Data.Outputs {
param, err := parseParam(vAny)
if err != nil {
return err
}
name := param.Name
tInfo, err := CanvasBlockInputToTypeInfo(param.Input)
if err != nil {
return err
}
ns.SetOutputType(name, tInfo)
sources, err := CanvasBlockInputToFieldInfo(param.Input, einoCompose.FieldPath{name}, n.Parent())
if err != nil {
return err
}
ns.AddOutputSource(sources...)
}
return nil
}
func BlockInputToNamedTypeInfo(name string, b *vo.BlockInput) (*vo.NamedTypeInfo, error) {
tInfo := &vo.NamedTypeInfo{
Name: name,
}
if b == nil {
return tInfo, nil
}
switch b.Type {
case vo.VariableTypeString:
switch b.AssistType {
case vo.AssistTypeTime:
tInfo.Type = vo.DataTypeTime
case vo.AssistTypeNotSet:
tInfo.Type = vo.DataTypeString
default:
fileType, ok := assistTypeToFileType(b.AssistType)
if ok {
tInfo.Type = vo.DataTypeFile
tInfo.FileType = &fileType
} else {
return nil, fmt.Errorf("unsupported assist type: %v", b.AssistType)
}
}
case vo.VariableTypeInteger:
tInfo.Type = vo.DataTypeInteger
case vo.VariableTypeFloat:
tInfo.Type = vo.DataTypeNumber
case vo.VariableTypeBoolean:
tInfo.Type = vo.DataTypeBoolean
case vo.VariableTypeObject:
tInfo.Type = vo.DataTypeObject
if b.Schema != nil {
tInfo.Properties = make([]*vo.NamedTypeInfo, 0, len(b.Schema.([]any)))
for _, subVAny := range b.Schema.([]any) {
if b.Value.Type == vo.BlockInputValueTypeRef {
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subNInfo, err := VariableToNamedTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.Properties = append(tInfo.Properties, subNInfo)
} else if b.Value.Type == vo.BlockInputValueTypeObjectRef {
subV, err := parseParam(subVAny)
if err != nil {
return nil, err
}
subNInfo, err := BlockInputToNamedTypeInfo(subV.Name, subV.Input)
if err != nil {
return nil, err
}
tInfo.Properties = append(tInfo.Properties, subNInfo)
}
}
}
case vo.VariableTypeList:
tInfo.Type = vo.DataTypeArray
subVAny := b.Schema
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subNInfo, err := VariableToNamedTypeInfo(subV)
if err != nil {
return nil, err
}
tInfo.ElemTypeInfo = subNInfo
default:
return nil, fmt.Errorf("unsupported variable type: %s", b.Type)
}
return tInfo, nil
}
func VariableToNamedTypeInfo(v *vo.Variable) (*vo.NamedTypeInfo, error) {
nInfo := &vo.NamedTypeInfo{
Required: v.Required,
Name: v.Name,
Desc: v.Description,
}
switch v.Type {
case vo.VariableTypeString:
switch v.AssistType {
case vo.AssistTypeTime:
nInfo.Type = vo.DataTypeTime
case vo.AssistTypeNotSet:
nInfo.Type = vo.DataTypeString
default:
fileType, ok := assistTypeToFileType(v.AssistType)
if ok {
nInfo.Type = vo.DataTypeFile
nInfo.FileType = &fileType
} else {
return nil, fmt.Errorf("unsupported assist type: %v", v.AssistType)
}
}
case vo.VariableTypeInteger:
nInfo.Type = vo.DataTypeInteger
case vo.VariableTypeFloat:
nInfo.Type = vo.DataTypeNumber
case vo.VariableTypeBoolean:
nInfo.Type = vo.DataTypeBoolean
case vo.VariableTypeObject:
nInfo.Type = vo.DataTypeObject
if v.Schema != nil {
nInfo.Properties = make([]*vo.NamedTypeInfo, 0)
for _, subVAny := range v.Schema.([]any) {
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := VariableToNamedTypeInfo(subV)
if err != nil {
return nil, err
}
nInfo.Properties = append(nInfo.Properties, subTInfo)
}
}
case vo.VariableTypeList:
nInfo.Type = vo.DataTypeArray
subVAny := v.Schema
subV, err := vo.ParseVariable(subVAny)
if err != nil {
return nil, err
}
subTInfo, err := VariableToNamedTypeInfo(subV)
if err != nil {
return nil, err
}
nInfo.ElemTypeInfo = subTInfo
default:
return nil, fmt.Errorf("unsupported variable type: %s", v.Type)
}
return nInfo, nil
}