fix: where HTTP node URL, JSON text, and raw text template rendering could not find the corresponding rendering variables (#745)

This commit is contained in:
Zhj
2025-08-15 11:25:27 +08:00
committed by GitHub
parent ff00dcb31b
commit 4ff734f15f
5 changed files with 765 additions and 33 deletions

View File

@@ -23,10 +23,12 @@ import (
"github.com/cloudwego/eino/compose"
"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/schema"
"github.com/coze-dev/coze-studio/backend/pkg/lang/crypto"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
)
var extractBracesRegexp = regexp.MustCompile(`\{\{(.*?)}}`)
@@ -43,9 +45,10 @@ func extractBracesContent(s string) []string {
}
type ImplicitNodeDependency struct {
NodeID string
FieldPath compose.FieldPath
TypeInfo *vo.TypeInfo
NodeID string
FieldPath compose.FieldPath
TypeInfo *vo.TypeInfo
IsIntermediateVar bool
}
func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNodeDependency, error) {
@@ -84,14 +87,24 @@ func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNod
return nil, err
}
}
if node.Data.Inputs.Body.BodyType == string(BodyTypeRawText) {
rawTextVars := extractBracesContent(node.Data.Inputs.Body.BodyData.Json)
rawTextVars := extractBracesContent(node.Data.Inputs.Body.BodyData.RawText)
err = extractDependenciesFromVars(rawTextVars)
if err != nil {
return nil, err
}
}
nodeID2ParentID := make(map[string]string)
for _, n := range canvas.Nodes {
if len(n.Blocks) > 0 {
for _, block := range n.Blocks {
nodeID2ParentID[block.ID] = n.ID
}
}
}
var nodeFinder func(nodes []*vo.Node, nodeID string) *vo.Node
nodeFinder = func(nodes []*vo.Node, nodeID string) *vo.Node {
for i := range nodes {
@@ -111,23 +124,78 @@ func extractImplicitDependency(node *vo.Node, canvas *vo.Canvas) ([]*ImplicitNod
if fNode == nil {
continue
}
tInfoMap := make(map[string]*vo.TypeInfo, len(node.Data.Outputs))
for _, vAny := range fNode.Data.Outputs {
v, err := vo.ParseVariable(vAny)
if err != nil {
return nil, err
intermediateVars := map[string]bool{}
tInfoMap := make(map[string]*vo.TypeInfo)
parentID, ok := nodeID2ParentID[node.ID]
if ok && parentID == fNode.ID {
// when referencing a composite node, the index field type is available by default
tInfoMap["index"] = &vo.TypeInfo{
Type: vo.DataTypeInteger,
}
tInfo, err := convert.CanvasVariableToTypeInfo(v)
if err != nil {
return nil, err
for _, p := range fNode.Data.Inputs.InputParameters {
tInfo, err := convert.CanvasBlockInputToTypeInfo(p.Input)
if err != nil {
return nil, err
}
if tInfo.Type != vo.DataTypeArray {
return nil, fmt.Errorf("when referencing a composite node, the input parameter must be an array type")
}
tInfoMap[p.Name] = tInfo.ElemTypeInfo
}
if fNode.Data.Inputs.Loop != nil {
for _, vAny := range fNode.Data.Inputs.Loop.VariableParameters {
v, err := convert.ParseParam(vAny)
if err != nil {
return nil, err
}
tInfo, err := convert.CanvasBlockInputToTypeInfo(v.Input)
if err != nil {
return nil, err
}
tInfoMap[v.Name] = tInfo
intermediateVars[v.Name] = true
}
}
} else if entity.NodeTypeMetas[entity.IDStrToNodeType(fNode.Type)].IsComposite {
for _, vAny := range fNode.Data.Outputs {
v, err := convert.ParseParam(vAny)
if err != nil {
return nil, err
}
tInfo, err := convert.CanvasBlockInputToTypeInfo(v.Input)
if err != nil {
return nil, err
}
tInfoMap[v.Name] = tInfo
}
} else {
for _, vAny := range fNode.Data.Outputs {
v, err := vo.ParseVariable(vAny)
if err != nil {
return nil, err
}
tInfo, err := convert.CanvasVariableToTypeInfo(v)
if err != nil {
return nil, err
}
tInfoMap[v.Name] = tInfo
}
tInfoMap[v.Name] = tInfo
}
tInfo, ok := getTypeInfoByPath(ds.FieldPath[0], ds.FieldPath[1:], tInfoMap)
if !ok {
return nil, fmt.Errorf("cannot find type info for dependency: %s", ds.FieldPath)
}
ds.TypeInfo = tInfo
if len(intermediateVars) > 0 {
ds.IsIntermediateVar = intermediateVars[strings.Join(ds.FieldPath, ".")]
}
}
return dependencies, nil
@@ -152,7 +220,7 @@ var globalVariableRegex = regexp.MustCompile(`global_variable_\w+\s*\["(.*?)"]`)
func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, implicitNodeDependencies []*ImplicitNodeDependency) (err error) {
inputs := n.Data.Inputs
implicitPathVars := make(map[string]bool)
addImplicitVarsSources := func(prefix string, vars []string) error {
addImplicitVarsSources := func(prefix string, vars []string, parent *vo.Node) error {
for _, v := range vars {
if strings.HasPrefix(v, "block_output_") {
paths := strings.Split(strings.TrimPrefix(v, "block_output_"), ".")
@@ -167,7 +235,8 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl
}
implicitPathVars[pathValue] = true
ns.SetInputType(pathValue, dep.TypeInfo)
ns.AddInputSource(&vo.FieldInfo{
filedInfo := &vo.FieldInfo{
Path: []string{pathValue},
Source: vo.FieldSource{
Ref: &vo.Reference{
@@ -175,7 +244,12 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl
FromPath: dep.FieldPath,
},
},
})
}
if dep.IsIntermediateVar && parent != nil {
filedInfo.Source.Ref.VariableType = ptr.Of(vo.ParentIntermediate)
}
ns.AddInputSource(filedInfo)
}
}
}
@@ -215,7 +289,7 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl
}
urlVars := extractBracesContent(inputs.APIInfo.URL)
err = addImplicitVarsSources("__apiInfo_url_", urlVars)
err = addImplicitVarsSources("__apiInfo_url_", urlVars, n.Parent())
if err != nil {
return err
}
@@ -304,13 +378,13 @@ func setHttpRequesterInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema, impl
ns.AddInputSource(sources...)
case BodyTypeJSON:
jsonVars := extractBracesContent(inputs.Body.BodyData.Json)
err = addImplicitVarsSources("__body_bodyData_json_", jsonVars)
err = addImplicitVarsSources("__body_bodyData_json_", jsonVars, n.Parent())
if err != nil {
return err
}
case BodyTypeRawText:
rawTextVars := extractBracesContent(inputs.Body.BodyData.RawText)
err = addImplicitVarsSources("__body_bodyData_rawText_", rawTextVars)
err = addImplicitVarsSources("__body_bodyData_rawText_", rawTextVars, n.Parent())
if err != nil {
return err
}

View File

@@ -360,6 +360,11 @@ type HTTPRequester struct {
md5FieldMapping MD5FieldMapping
}
func adaptTemplate(template string) string {
return globalVariableReplaceRegexp.ReplaceAllString(template, "global_variable_$1.$2")
}
func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (output map[string]any, err error) {
var (
req = &Request{}
@@ -380,7 +385,7 @@ func (hg *HTTPRequester) Invoke(ctx context.Context, input map[string]any) (outp
Header: http.Header{},
}
httpURL, err := nodes.TemplateRender(hg.urlConfig.Tpl, req.URLVars)
httpURL, err := nodes.TemplateRender(adaptTemplate(hg.urlConfig.Tpl), req.URLVars)
if err != nil {
return nil, err
}
@@ -524,7 +529,7 @@ func (b *BodyConfig) getBodyAndContentType(ctx context.Context, req *Request) (i
switch b.BodyType {
case BodyTypeJSON:
jsonString, err := nodes.TemplateRender(b.TextJsonConfig.Tpl, req.JsonVars)
jsonString, err := nodes.TemplateRender(adaptTemplate(b.TextJsonConfig.Tpl), req.JsonVars)
if err != nil {
return nil, contentType, err
}
@@ -539,7 +544,7 @@ func (b *BodyConfig) getBodyAndContentType(ctx context.Context, req *Request) (i
body = strings.NewReader(form.Encode())
contentType = ContentTypeFormURLEncoded
case BodyTypeRawText:
textString, err := nodes.TemplateRender(b.TextPlainConfig.Tpl, req.TextPlainVars)
textString, err := nodes.TemplateRender(adaptTemplate(b.TextPlainConfig.Tpl), req.TextPlainVars)
if err != nil {
return nil, contentType, err
}
@@ -632,7 +637,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
result := make(map[string]any)
result["method"] = hg.method
u, err := nodes.TemplateRender(hg.urlConfig.Tpl, request.URLVars)
u, err := nodes.TemplateRender(adaptTemplate(hg.urlConfig.Tpl), request.URLVars)
if err != nil {
return nil, err
}
@@ -666,7 +671,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
result["body"] = nil
switch hg.bodyConfig.BodyType {
case BodyTypeJSON:
js, err := nodes.TemplateRender(hg.bodyConfig.TextJsonConfig.Tpl, request.JsonVars)
js, err := nodes.TemplateRender(adaptTemplate(hg.bodyConfig.TextJsonConfig.Tpl), request.JsonVars)
if err != nil {
return nil, err
}
@@ -677,7 +682,7 @@ func (hg *HTTPRequester) ToCallbackInput(_ context.Context, input map[string]any
}
result["body"] = ret
case BodyTypeRawText:
tx, err := nodes.TemplateRender(hg.bodyConfig.TextPlainConfig.Tpl, request.TextPlainVars)
tx, err := nodes.TemplateRender(adaptTemplate(hg.bodyConfig.TextPlainConfig.Tpl), request.TextPlainVars)
if err != nil {
return nil, err
@@ -729,7 +734,7 @@ func (hg *HTTPRequester) parserToRequest(input map[string]any) (*Request, error)
if strings.HasPrefix(urlKey, "global_variable_") {
urlKey = globalVariableReplaceRegexp.ReplaceAllString(urlKey, "global_variable_$1.$2")
}
nodes.SetMapValue(request.URLVars, strings.Split(urlKey, "."), value.(string))
nodes.SetMapValue(request.URLVars, strings.Split(urlKey, "."), value)
}
}
if strings.HasPrefix(key, headersPrefix) {
@@ -777,7 +782,6 @@ func (hg *HTTPRequester) parserToRequest(input map[string]any) (*Request, error)
if formDataKey, ok := hg.md5FieldMapping.BodyMD5Mapping[formDataMd5Key]; ok {
request.FormDataVars[formDataKey] = value.(string)
}
}
if strings.HasPrefix(bodyKey, bodyFormURLEncodedPrefix) {