refactor: 重构工作流执行图构建与节点上下文处理
This commit is contained in:
@@ -15,17 +15,19 @@ const (
|
|||||||
NodeNameVideoModel = "生成视频"
|
NodeNameVideoModel = "生成视频"
|
||||||
NodeNameAudioModel = "生成音频"
|
NodeNameAudioModel = "生成音频"
|
||||||
NodeNameBatchModel = "批量处理一起返回"
|
NodeNameBatchModel = "批量处理一起返回"
|
||||||
|
NodeNameDataConversionModel = "参数转换"
|
||||||
NodeNameSenseOptimizeModel = "语义优化"
|
NodeNameSenseOptimizeModel = "语义优化"
|
||||||
NodeNameStoryOptimizeModel = "分镜优化"
|
NodeNameStoryOptimizeModel = "分镜优化"
|
||||||
NodeNameScriptOptimizeModel = "剧本优化"
|
NodeNameScriptOptimizeModel = "剧本优化"
|
||||||
NodeNameDataConversionModel = "参数转换"
|
|
||||||
NodeNameModel = "模型"
|
NodeNameModel = "模型"
|
||||||
NodeNameMerge = "结果合并"
|
NodeNameMerge = "结果合并"
|
||||||
NodeNameDataMerge = "结果汇集"
|
NodeNameDataMerge = "结果汇集"
|
||||||
NodeNameJudge = "条件判断"
|
NodeNameJudge = "条件判断"
|
||||||
|
NodeNameLoop = "循环"
|
||||||
NodeNameForm = "表单"
|
NodeNameForm = "表单"
|
||||||
NodeNameHttp = "HTTP(S)接口"
|
NodeNameHttp = "HTTP(S)接口"
|
||||||
NodeNameCustomNode = "自定义节点"
|
NodeNameCustomNode = "自定义节点"
|
||||||
|
NodeNameSystemSum = "系统-结果汇总"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 表单字段 Label
|
// 表单字段 Label
|
||||||
@@ -54,20 +56,22 @@ const (
|
|||||||
NodeTypeAudioModel NodeType = "audio_model"
|
NodeTypeAudioModel NodeType = "audio_model"
|
||||||
NodeTypeBatchModel NodeType = "batch_model"
|
NodeTypeBatchModel NodeType = "batch_model"
|
||||||
|
|
||||||
|
NodeTypeDataConversionModel NodeType = "data_conversion_model"
|
||||||
NodeTypeSenseOptimizeModel NodeType = "sense_optimize_model"
|
NodeTypeSenseOptimizeModel NodeType = "sense_optimize_model"
|
||||||
NodeTypeStoryOptimizeModel NodeType = "story_optimize_model"
|
NodeTypeStoryOptimizeModel NodeType = "story_optimize_model"
|
||||||
NodeTypeScriptOptimizeModel NodeType = "script_optimize_model"
|
NodeTypeScriptOptimizeModel NodeType = "script_optimize_model"
|
||||||
// 基础
|
// 基础
|
||||||
NodeTypeDataConversionModel NodeType = "data_conversion_model"
|
NodeTypeModel NodeType = "model"
|
||||||
NodeTypeModel NodeType = "model"
|
NodeTypeMerge NodeType = "merge"
|
||||||
NodeTypeMerge NodeType = "merge"
|
NodeTypeDataMerge NodeType = "data_merge"
|
||||||
NodeTypeDataMerge NodeType = "data_merge"
|
NodeTypeJudge NodeType = "judge"
|
||||||
NodeTypeJudge NodeType = "judge"
|
NodeTypeForm NodeType = "form"
|
||||||
NodeTypeForm NodeType = "form"
|
NodeTypeIntent NodeType = "intent"
|
||||||
NodeTypeIntent NodeType = "intent"
|
NodeTypeHttp NodeType = "http"
|
||||||
NodeTypeHttp NodeType = "http"
|
|
||||||
// 自定义
|
// 自定义
|
||||||
NodeTypeCustomNode NodeType = "custom_node"
|
NodeTypeCustomNode NodeType = "custom_node"
|
||||||
|
// 系统
|
||||||
|
NodeTypeSystemSum NodeType = "system_sum"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -363,49 +363,12 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
return nil, errors.New("文件格式不支持")
|
return nil, errors.New("文件格式不支持")
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// ✅【第1步】给所有判断节点自动生成意图识别节点
|
|
||||||
// =========================================================================
|
|
||||||
judge2IntentNodeMap := make(map[string]string)
|
|
||||||
finalNodes := make([]entity.FlowNode, 0, len(req.FlowContent.Nodes)*2)
|
|
||||||
for _, item := range req.FlowContent.Nodes {
|
|
||||||
finalNodes = append(finalNodes, item)
|
|
||||||
// 判断节点自动加 intent 节点
|
|
||||||
if item.NodeCode == node.NodeTypeJudge {
|
|
||||||
intentNodeID := fmt.Sprintf("intent_%s", item.Id)
|
|
||||||
intentNode := entity.FlowNode{
|
|
||||||
Id: intentNodeID,
|
|
||||||
NodeCode: node.NodeTypeIntent,
|
|
||||||
Name: fmt.Sprintf("意图识别-%s", item.Name),
|
|
||||||
InputSource: item.InputSource, // ✅ 正确赋值
|
|
||||||
FormConfig: item.FormConfig, // ✅ 用户配置
|
|
||||||
ModelConfig: item.ModelConfig, // ✅ 系统配置
|
|
||||||
}
|
|
||||||
finalNodes = append(finalNodes, intentNode)
|
|
||||||
judge2IntentNodeMap[item.Id] = intentNodeID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryNodeID := "summary_node"
|
|
||||||
summaryNode := entity.FlowNode{
|
|
||||||
Id: summaryNodeID,
|
|
||||||
NodeCode: node.NodeTypeCustomNode, // 复用自定义节点类型,也可新增专属类型
|
|
||||||
Name: "结果汇总节点",
|
|
||||||
InputSource: []entity.FlowNodeInputSource{}, // 后续自动聚合所有节点输出
|
|
||||||
FormConfig: nil,
|
|
||||||
ModelConfig: node.ModelItem{},
|
|
||||||
}
|
|
||||||
finalNodes = append(finalNodes, summaryNode)
|
|
||||||
|
|
||||||
// 替换节点列表
|
|
||||||
req.FlowContent.Nodes = finalNodes
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// ✅【第2步】构建执行图
|
// ✅【第2步】构建执行图
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
var nodeList []entity.FlowNode
|
||||||
var runGraph compose.Runnable[any, any]
|
var runGraph compose.Runnable[any, any]
|
||||||
runGraph, err = BuildGraphFromFlowContent(execCtx, req.FlowContent, judge2IntentNodeMap, summaryNodeID)
|
nodeList, runGraph, err = BuildGraphFromFlowContent(execCtx, req.FlowContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||||
Id: executionId,
|
Id: executionId,
|
||||||
@@ -418,7 +381,6 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("执行工作流失败: %v", err)
|
return nil, fmt.Errorf("执行工作流失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// ✅【第3步】构建 ConfigMap
|
// ✅【第3步】构建 ConfigMap
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -426,15 +388,9 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
for _, cfg := range req.NodeInputParams {
|
for _, cfg := range req.NodeInputParams {
|
||||||
configMap[cfg.Id] = cfg
|
configMap[cfg.Id] = cfg
|
||||||
}
|
}
|
||||||
// 自动给意图节点复制配置
|
for _, i := range nodeList {
|
||||||
for judgeID, intentID := range judge2IntentNodeMap {
|
configMap[i.Id] = &i
|
||||||
if cfg, ok := configMap[judgeID]; ok {
|
|
||||||
configMap[intentID] = cfg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 初始化汇总节点配置
|
|
||||||
configMap[summaryNodeID] = &summaryNode
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// ✅【第4步】构建全局执行入参(现在 schemaMap 是有值的!)
|
// ✅【第4步】构建全局执行入参(现在 schemaMap 是有值的!)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -475,7 +431,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildGraphFromFlowContent 根据前端保存的工作流JSON,自动构建执行图
|
// BuildGraphFromFlowContent 根据前端保存的工作流JSON,自动构建执行图
|
||||||
func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo, judge2IntentNodeMap map[string]string, summaryNodeID string) (compose.Runnable[any, any], error) {
|
func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo) ([]entity.FlowNode, compose.Runnable[any, any], error) {
|
||||||
// 注册自定义合并函数:处理 *flowDto.FlowExecutionInput 类型合并
|
// 注册自定义合并函数:处理 *flowDto.FlowExecutionInput 类型合并
|
||||||
// 由于 ConfigMap 是 map 引用类型,所有并行分支修改已经写入共享内存
|
// 由于 ConfigMap 是 map 引用类型,所有并行分支修改已经写入共享内存
|
||||||
// 直接返回第一个实例即可,所有修改都已经可见
|
// 直接返回第一个实例即可,所有修改都已经可见
|
||||||
@@ -488,9 +444,26 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
})
|
})
|
||||||
|
|
||||||
graph := compose.NewGraph[any, any]()
|
graph := compose.NewGraph[any, any]()
|
||||||
nodeMap := make(map[string]entity.FlowNode)
|
|
||||||
|
var nodeList []entity.FlowNode
|
||||||
|
nodeId := uuid.NewString()
|
||||||
|
originalEndNodes := findEndNodes(flowContent.StartNodeId, flowContent.Edges)
|
||||||
|
for i := range originalEndNodes {
|
||||||
|
sprintf := fmt.Sprintf("%v_%d", nodeId, i)
|
||||||
|
summaryNode := entity.FlowNode{
|
||||||
|
Id: sprintf,
|
||||||
|
NodeCode: node.NodeTypeSystemSum,
|
||||||
|
Name: node.NodeNameSystemSum,
|
||||||
|
InputSource: []entity.FlowNodeInputSource{}, // 后续自动聚合所有节点输出
|
||||||
|
FormConfig: nil,
|
||||||
|
ModelConfig: node.ModelItem{},
|
||||||
|
}
|
||||||
|
nodeList = append(nodeList, summaryNode)
|
||||||
|
flowContent.Nodes = append(flowContent.Nodes, summaryNode)
|
||||||
|
}
|
||||||
|
|
||||||
// 注册所有节点
|
// 注册所有节点
|
||||||
|
nodeMap := make(map[string]entity.FlowNode)
|
||||||
for _, item := range flowContent.Nodes {
|
for _, item := range flowContent.Nodes {
|
||||||
nodeMap[item.Id] = item
|
nodeMap[item.Id] = item
|
||||||
if item.NodeCode != node.NodeTypeJudge {
|
if item.NodeCode != node.NodeTypeJudge {
|
||||||
@@ -498,6 +471,16 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册所有边
|
||||||
|
if flowContent.StartNodeId != "" {
|
||||||
|
_ = graph.AddEdge(compose.START, flowContent.StartNodeId)
|
||||||
|
}
|
||||||
|
for i, endID := range originalEndNodes {
|
||||||
|
sprintf := fmt.Sprintf("%v_%d", nodeId, i)
|
||||||
|
_ = graph.AddEdge(endID, sprintf)
|
||||||
|
_ = graph.AddEdge(sprintf, compose.END)
|
||||||
|
}
|
||||||
|
|
||||||
// 构建边关系
|
// 构建边关系
|
||||||
upstreamMap := make(map[string][]string)
|
upstreamMap := make(map[string][]string)
|
||||||
edgeMap := make(map[string][]entity.FlowEdge)
|
edgeMap := make(map[string][]entity.FlowEdge)
|
||||||
@@ -510,15 +493,8 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
for fromNodeID, edges := range edgeMap {
|
for fromNodeID, edges := range edgeMap {
|
||||||
fromNode := nodeMap[fromNodeID]
|
fromNode := nodeMap[fromNodeID]
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// 判断节点 → 分支处理
|
// 判断节点 → 分支处理
|
||||||
// --------------------------
|
|
||||||
if fromNode.NodeCode == node.NodeTypeJudge {
|
if fromNode.NodeCode == node.NodeTypeJudge {
|
||||||
intentNodeID, ok := judge2IntentNodeMap[fromNodeID]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("判断节点[%s]未生成意图节点", fromNodeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
branchMap := make(map[string]bool)
|
branchMap := make(map[string]bool)
|
||||||
for _, e := range edges {
|
for _, e := range edges {
|
||||||
branchMap[e.To] = true
|
branchMap[e.To] = true
|
||||||
@@ -553,11 +529,6 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
m["branch_id_name_map"] = branchIdNameMap // 传递ID-名称映射
|
m["branch_id_name_map"] = branchIdNameMap // 传递ID-名称映射
|
||||||
currentConfig.Config = m
|
currentConfig.Config = m
|
||||||
|
|
||||||
// 从意图节点取输出
|
|
||||||
if intentCfg, ok := execInput.ConfigMap[intentNodeID]; ok {
|
|
||||||
currentConfig.OutputResult = intentCfg.OutputResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关键修改:构造 NodeExecutionInput 传入 JudgeLambda
|
// 关键修改:构造 NodeExecutionInput 传入 JudgeLambda
|
||||||
nodeExecInput := &flowDto.NodeExecutionInput{
|
nodeExecInput := &flowDto.NodeExecutionInput{
|
||||||
Config: currentConfig, // 当前判断节点配置
|
Config: currentConfig, // 当前判断节点配置
|
||||||
@@ -566,34 +537,21 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
return JudgeLambda(ctx, nodeExecInput) // 传入 NodeExecutionInput 类型
|
return JudgeLambda(ctx, nodeExecInput) // 传入 NodeExecutionInput 类型
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = graph.AddBranch(intentNodeID, compose.NewGraphBranch(judgeLambda, branchMap))
|
_ = graph.AddBranch(upstreamMap[fromNodeID][0], compose.NewGraphBranch(judgeLambda, branchMap))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// 普通节点连线
|
// 普通节点连线
|
||||||
// --------------------------
|
|
||||||
for _, e := range edges {
|
for _, e := range edges {
|
||||||
toNode := nodeMap[e.To]
|
toNode := nodeMap[e.To]
|
||||||
if toNode.NodeCode == node.NodeTypeJudge {
|
if toNode.NodeCode == node.NodeTypeJudge {
|
||||||
_ = graph.AddEdge(e.From, fmt.Sprintf("intent_%s", toNode.Id))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ = graph.AddEdge(e.From, e.To)
|
_ = graph.AddEdge(e.From, e.To)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compile, err := graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"))
|
||||||
// ==================== 第四步:处理开始/结束节点 ====================
|
return nodeList, compile, err
|
||||||
if flowContent.StartNodeId != "" {
|
|
||||||
_ = graph.AddEdge(compose.START, flowContent.StartNodeId)
|
|
||||||
}
|
|
||||||
originalEndNodes := findEndNodes(flowContent.StartNodeId, flowContent.Edges)
|
|
||||||
for _, endID := range originalEndNodes {
|
|
||||||
_ = graph.AddEdge(endID, summaryNodeID)
|
|
||||||
}
|
|
||||||
_ = graph.AddEdge(summaryNodeID, compose.END)
|
|
||||||
|
|
||||||
return graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"), compose.WithNodeTriggerMode(compose.AllPredecessor))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------- 节点自动注册器(核心分发) --------------------------
|
// -------------------------- 节点自动注册器(核心分发) --------------------------
|
||||||
@@ -703,13 +661,11 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
return execInput, nil
|
return execInput, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nodeID == "summary_node" {
|
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SummaryLambda)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch code {
|
switch code {
|
||||||
case "__start__":
|
case "__start__":
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(StartLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(StartLambda)))
|
||||||
|
case node.NodeTypeSystemSum:
|
||||||
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SummaryLambda)))
|
||||||
case node.NodeTypeTextModel:
|
case node.NodeTypeTextModel:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(TextModelLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(TextModelLambda)))
|
||||||
case node.NodeTypeImageModel:
|
case node.NodeTypeImageModel:
|
||||||
@@ -722,12 +678,6 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(BatchModelLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(BatchModelLambda)))
|
||||||
case node.NodeTypeDataConversionModel:
|
case node.NodeTypeDataConversionModel:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataConversionLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataConversionLambda)))
|
||||||
//case node.NodeTypeSenseOptimizeModel:
|
|
||||||
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SenseOptimizeModelLambda)))
|
|
||||||
//case node.NodeTypeStoryOptimizeModel:
|
|
||||||
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(StoryOptimizeModelLambda)))
|
|
||||||
//case node.NodeTypeScriptOptimizeModel:
|
|
||||||
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(ScriptOptimizeModelLambda)))
|
|
||||||
case node.NodeTypeCustomNode:
|
case node.NodeTypeCustomNode:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(CustomLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(CustomLambda)))
|
||||||
case node.NodeTypeForm:
|
case node.NodeTypeForm:
|
||||||
@@ -737,49 +687,42 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
case node.NodeTypeMerge:
|
case node.NodeTypeMerge:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(MergeLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(MergeLambda)))
|
||||||
case node.NodeTypeDataMerge:
|
case node.NodeTypeDataMerge:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataMergeLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataMergeLambda)), compose.WithGraphCompileOptions(compose.WithNodeTriggerMode(compose.AllPredecessor)))
|
||||||
case node.NodeTypeHttp:
|
case node.NodeTypeHttp:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(HttpLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(HttpLambda)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// ✅【工具方法】找出所有没有出边的节点 → 作为结束节点连接 END
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
func findEndNodes(startNodeId string, edges []entity.FlowEdge) []string {
|
func findEndNodes(startNodeId string, edges []entity.FlowEdge) []string {
|
||||||
// 构建 节点 → 后续节点 的映射
|
|
||||||
nextMap := make(map[string][]string)
|
nextMap := make(map[string][]string)
|
||||||
for _, e := range edges {
|
for _, e := range edges {
|
||||||
nextMap[e.From] = append(nextMap[e.From], e.To)
|
nextMap[e.From] = append(nextMap[e.From], e.To)
|
||||||
}
|
}
|
||||||
|
|
||||||
endNodeSet := make(map[string]struct{})
|
endNodeSet := make(map[string]struct{})
|
||||||
|
visited := make(map[string]struct{})
|
||||||
|
queue := []string{startNodeId}
|
||||||
|
|
||||||
// 🚀 只从【开始节点】递归遍历(关键修复)
|
for len(queue) > 0 {
|
||||||
findLeafNodes(startNodeId, nextMap, endNodeSet)
|
node := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
// 转成数组返回
|
if _, exist := visited[node]; exist {
|
||||||
endNodes := make([]string, 0, len(endNodeSet))
|
continue
|
||||||
for id := range endNodeSet {
|
}
|
||||||
endNodes = append(endNodes, id)
|
visited[node] = struct{}{}
|
||||||
}
|
|
||||||
return endNodes
|
nextList := nextMap[node]
|
||||||
}
|
if len(nextList) == 0 {
|
||||||
|
endNodeSet[node] = struct{}{}
|
||||||
// --------------------------------------------------------------------
|
continue
|
||||||
// ✅ 递归:查找以 nodeId 开头的所有叶子节点
|
}
|
||||||
// --------------------------------------------------------------------
|
queue = append(queue, nextList...)
|
||||||
func findLeafNodes(nodeId string, nextMap map[string][]string, endNodeSet map[string]struct{}) {
|
|
||||||
nextNodes := nextMap[nodeId]
|
|
||||||
|
|
||||||
// 🚩 没有下一个节点 = 真实结束节点
|
|
||||||
if len(nextNodes) == 0 {
|
|
||||||
endNodeSet[nodeId] = struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归继续找下一个
|
|
||||||
for _, nextId := range nextNodes {
|
|
||||||
findLeafNodes(nextId, nextMap, endNodeSet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, len(endNodeSet))
|
||||||
|
for k := range endNodeSet {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,19 +41,35 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput,实际 %T", input)
|
return "", fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput,实际 %T", input)
|
||||||
}
|
}
|
||||||
|
//inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||||
|
//fmt.Printf("JudgeLambda路由:输入=%s\n", gjson.MustEncode(inputMap))
|
||||||
|
//fmt.Printf("JudgeLambda路由:输出=%s\n", gjson.MustEncode(outputMap))
|
||||||
|
//fmt.Printf("JudgeLambda路由:模型=%s\n", gjson.MustEncode(modelMap))
|
||||||
|
//configMap := gconv.Map(nodeInput.Config.Config)
|
||||||
|
//ids := gconv.Strings(configMap["branch_ids"])
|
||||||
|
//fmt.Printf("JudgeLambda路由:目标节点ID=%s\n", gconv.String(ids))
|
||||||
|
//
|
||||||
|
//m := map[string]bool{
|
||||||
|
// "80000a50-81e1-4c15-adae-aab6c0d781ad": true,
|
||||||
|
// "59a6ffa2-3252-4535-b6ed-d3e49cdf6c55": true,
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//return m, nil
|
||||||
// 1. 直接用你原来的方法(返回两个 map)
|
// 1. 直接用你原来的方法(返回两个 map)
|
||||||
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||||
var outputResult []node.NodeFormField
|
var outputResult []node.NodeFormField
|
||||||
for _, valueAny := range inputMap {
|
outputResult = append(outputResult, inputMap...)
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
outputResult = append(outputResult, outputMap...)
|
||||||
outputResult = append(outputResult, field)
|
//for _, valueAny := range inputMap {
|
||||||
}
|
// if field, ok := valueAny.(node.NodeFormField); ok {
|
||||||
}
|
// outputResult = append(outputResult, field)
|
||||||
for _, valueAny := range outputMap {
|
// }
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
//}
|
||||||
outputResult = append(outputResult, field)
|
//for _, valueAny := range outputMap {
|
||||||
}
|
// if field, ok := valueAny.(node.NodeFormField); ok {
|
||||||
}
|
// outputResult = append(outputResult, field)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
for _, valueAny := range modelMap {
|
for _, valueAny := range modelMap {
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
if field, ok := valueAny.(node.NodeFormField); ok {
|
||||||
outputResult = append(outputResult, field)
|
outputResult = append(outputResult, field)
|
||||||
@@ -204,6 +220,8 @@ func TextModelLambda(ctx context.Context, input any) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nodeInput.Config.OutputResult = outputRes
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
//}
|
||||||
|
|
||||||
return nodeInput, nil
|
return nodeInput, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,14 +299,13 @@ func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
|||||||
Label: fmt.Sprintf("video_oss_url:content:%d", 0),
|
Label: fmt.Sprintf("video_oss_url:content:%d", 0),
|
||||||
Type: "string",
|
Type: "string",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
outputRes = append(outputRes, node.NodeFormField{
|
|
||||||
Field: fmt.Sprintf("concat_video_url:content:%d", 0),
|
|
||||||
Value: urlPrefix + msg.FileURL,
|
|
||||||
Label: fmt.Sprintf("concat_video_url:content:%d", 0),
|
|
||||||
Type: "string",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
outputRes = append(outputRes, node.NodeFormField{
|
||||||
|
Field: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||||
|
Value: urlPrefix + msg.FileURL,
|
||||||
|
Label: fmt.Sprintf("视频内容:content:%d", 0),
|
||||||
|
Type: "string",
|
||||||
|
})
|
||||||
nodeInput.Config.OutputResult = outputRes
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
|
||||||
return nodeInput, nil
|
return nodeInput, nil
|
||||||
@@ -303,6 +320,64 @@ func HttpLambda(ctx context.Context, input any) (any, error) {
|
|||||||
outputRes := make([]node.NodeFormField, 0)
|
outputRes := make([]node.NodeFormField, 0)
|
||||||
var err error
|
var err error
|
||||||
outputRes, err = HttpNode(ctx, nodeInput)
|
outputRes, err = HttpNode(ctx, nodeInput)
|
||||||
|
//if nodeInput.Config.Name == "生成视频" {
|
||||||
|
// outputRes, err = HttpNode(ctx, nodeInput)
|
||||||
|
//} else {
|
||||||
|
// a := []map[string]any{
|
||||||
|
// {
|
||||||
|
// "timeline": "0.0-2.1",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/1.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "2.1-4.5",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/2.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "4.5-12.2",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/3.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "12.2-13.6",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/4.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "13.6-17.7",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/5.mp4model-gateway",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "17.7-31.0",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/6.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "31.0-33.2",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/7.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "33.2-37.4",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/8.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "37.4-38.9",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/9.mp4",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "timeline": "38.9-57.9",
|
||||||
|
// "url": "https://ark-auto-2127201628-cn-beijing-default.tos-cn-beijing.volces.com/%E8%A7%86%E9%A2%91/10.mp4",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// outputRes = append(outputRes, node.NodeFormField{
|
||||||
|
// Field: fmt.Sprintf("segments"),
|
||||||
|
// Value: a,
|
||||||
|
// Label: fmt.Sprintf("segments"),
|
||||||
|
// Type: "string",
|
||||||
|
// })
|
||||||
|
// outputRes = append(outputRes, node.NodeFormField{
|
||||||
|
// Field: fmt.Sprintf("audioUrl"),
|
||||||
|
// Value: "http://116.204.74.41:9000/tenantid-94/2026-06-11/9915351c-55b9-46d8-b783-3815126b.m4a",
|
||||||
|
// Label: fmt.Sprintf("audioUrl"),
|
||||||
|
// Type: "string",
|
||||||
|
// })
|
||||||
|
//}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -437,11 +512,8 @@ func MergeLambda(ctx context.Context, input any) (res any, err error) {
|
|||||||
// 1. 把所有节点输出拍平成 字段名->内容 的map
|
// 1. 把所有节点输出拍平成 字段名->内容 的map
|
||||||
dataMap := make(map[string]node.NodeFormField)
|
dataMap := make(map[string]node.NodeFormField)
|
||||||
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||||
for _, valueAny := range outputMap {
|
for _, field := range outputMap {
|
||||||
field := node.NodeFormField{}
|
dataMap[field.Field] = field
|
||||||
if field, ok = valueAny.(node.NodeFormField); ok {
|
|
||||||
dataMap[field.Field] = field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 提取所有文案:text_content_0,1,2...
|
// 2. 提取所有文案:text_content_0,1,2...
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ func ImgNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput, skillNa
|
|||||||
outputRes = append(outputRes, node.NodeFormField{
|
outputRes = append(outputRes, node.NodeFormField{
|
||||||
Field: fmt.Sprintf("img_url:%v:%d", k, i),
|
Field: fmt.Sprintf("img_url:%v:%d", k, i),
|
||||||
Value: v,
|
Value: v,
|
||||||
Label: fmt.Sprintf("img_url%v:%d", k, i),
|
Label: fmt.Sprintf("图片内容%v:%d", k, i),
|
||||||
Type: "string",
|
Type: "string",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -674,7 +674,7 @@ func AudioOptimizeNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInpu
|
|||||||
outputRes = append(outputRes, node.NodeFormField{
|
outputRes = append(outputRes, node.NodeFormField{
|
||||||
Field: fmt.Sprintf("audio_url:%v:%d", k, i),
|
Field: fmt.Sprintf("audio_url:%v:%d", k, i),
|
||||||
Value: v,
|
Value: v,
|
||||||
Label: fmt.Sprintf("audio_url:%v:%d", k, i),
|
Label: fmt.Sprintf("音频内容:%v:%d", k, i),
|
||||||
Type: "string",
|
Type: "string",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -939,23 +939,17 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
|||||||
func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, resultFrom []map[string]any, resultUserFrom []map[string]any) {
|
func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, resultFrom []map[string]any, resultUserFrom []map[string]any) {
|
||||||
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||||
var outputResult []node.NodeFormField
|
var outputResult []node.NodeFormField
|
||||||
for _, valueAny := range inputMap {
|
outputResult = append(outputResult, inputMap...)
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
|
||||||
outputResult = append(outputResult, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultUserFrom = []map[string]any{}
|
resultUserFrom = []map[string]any{}
|
||||||
for _, valueAny := range outputMap {
|
for _, field := range outputMap {
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
|
||||||
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
|
if strings.Contains(field.Field, "text_content") {
|
||||||
if strings.Contains(field.Field, "text_content") {
|
field.Value = StripHtmlTags(gconv.String(field.Value))
|
||||||
field.Value = StripHtmlTags(gconv.String(field.Value))
|
|
||||||
}
|
|
||||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
|
||||||
field.Label: field.Value,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||||
|
field.Label: field.Value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, valueAny := range modelMap {
|
for _, valueAny := range modelMap {
|
||||||
@@ -963,18 +957,13 @@ func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, result
|
|||||||
outputResult = append(outputResult, field)
|
outputResult = append(outputResult, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if !nodeInput.Global.IsDialogue {
|
if !nodeInput.Global.IsDialogue {
|
||||||
for _, item := range outputResult {
|
for _, item := range outputResult {
|
||||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||||
item.Label: item.Value,
|
item.Label: item.Value,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, item := range nodeInput.Config.FormConfig {
|
|
||||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
|
||||||
item.Label: item.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
if !g.IsEmpty(nodeInput.Global.Desc) {
|
if !g.IsEmpty(nodeInput.Global.Desc) {
|
||||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||||
"desc": nodeInput.Global.Desc,
|
"desc": nodeInput.Global.Desc,
|
||||||
@@ -998,83 +987,40 @@ func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, result
|
|||||||
return skillName, resultFrom, resultUserFrom
|
return skillName, resultFrom, resultUserFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *entity.FlowNode) (map[string]any, map[string]any, map[string]any) {
|
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *entity.FlowNode) ([]node.NodeFormField, []node.NodeFormField, map[string]any) {
|
||||||
input := make(map[string]any)
|
var input []node.NodeFormField
|
||||||
output := make(map[string]any)
|
var output []node.NodeFormField
|
||||||
model := make(map[string]any)
|
model := make(map[string]any)
|
||||||
// 1. 有引用 → 取引用节点的字段值
|
|
||||||
if len(nodeEntity.InputSource) > 0 {
|
if len(nodeEntity.InputSource) > 0 {
|
||||||
for _, source := range nodeEntity.InputSource {
|
for _, source := range nodeEntity.InputSource {
|
||||||
refNodeID := source.NodeId
|
refNode, ok := execInput.ConfigMap[source.NodeId]
|
||||||
fields := source.Field
|
|
||||||
|
|
||||||
refNode, ok := execInput.ConfigMap[refNodeID]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if len(source.Field) > 0 {
|
||||||
inputMap := buildInputMap(refNode)
|
|
||||||
outputMap := mergeOutput(refNode.OutputResult)
|
|
||||||
modelMap := mergeModel(refNode.ModelConfig)
|
|
||||||
if len(fields) > 0 {
|
|
||||||
// 取指定字段
|
// 取指定字段
|
||||||
for _, f := range fields {
|
for _, f := range source.Field {
|
||||||
if v, ok := inputMap[f]; ok {
|
for _, v := range refNode.FormConfig {
|
||||||
input[f] = v
|
if strings.Contains(v.Label, f) {
|
||||||
}
|
input = append(input, v)
|
||||||
if v, ok := modelMap[f]; ok {
|
|
||||||
model[f] = v
|
|
||||||
}
|
|
||||||
for k, v := range outputMap {
|
|
||||||
if strings.Contains(k, f) {
|
|
||||||
model[k] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for _, v := range refNode.ModelConfig.ModelForm {
|
||||||
} else {
|
if g.IsEmpty(v.Value) {
|
||||||
// 取全部
|
continue
|
||||||
if refNode.NodeCode != node.NodeTypeHttp {
|
}
|
||||||
for k, v := range inputMap {
|
if strings.Contains(v.Label, f) {
|
||||||
input[k] = v
|
model[f] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range refNode.OutputResult {
|
||||||
|
if strings.Contains(v.Label, f) {
|
||||||
|
output = append(output, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for k, v := range modelMap {
|
|
||||||
model[k] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return input, output, model
|
return input, output, model
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildInputMap 从 FormConfig 构造输入map
|
|
||||||
func buildInputMap(node *entity.FlowNode) map[string]any {
|
|
||||||
m := make(map[string]any)
|
|
||||||
for _, item := range node.FormConfig {
|
|
||||||
m[item.Label] = item
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeOutput 合并节点输出 []map → 单map
|
|
||||||
func mergeOutput(output []node.NodeFormField) map[string]any {
|
|
||||||
m := make(map[string]any)
|
|
||||||
for _, item := range output {
|
|
||||||
m[item.Label] = item
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeOutput 合并节点输出 []map → 单map
|
|
||||||
func mergeModel(output node.ModelItem) map[string]any {
|
|
||||||
m := make(map[string]any)
|
|
||||||
// 遍历 output.ModelForm 里的每一个 key 和原始值
|
|
||||||
for _, rawValue := range output.ModelForm {
|
|
||||||
if g.IsEmpty(rawValue.Value) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 包装成 { "value": 原始值 }
|
|
||||||
m[rawValue.Label] = rawValue.Value
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user