package flow import ( "ai-agent/workflow/consts/flow" "ai-agent/workflow/consts/node" flowDao "ai-agent/workflow/dao/flow" "ai-agent/workflow/model/dto" flowDto "ai-agent/workflow/model/dto/flow" "ai-agent/workflow/model/entity" "context" "fmt" "regexp" "strconv" "strings" "time" "gitea.com/red-future/common/utils" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, node *entity.FlowNode) (map[string]any, map[string]any, map[string]any) { input := make(map[string]any) output := make(map[string]any) model := make(map[string]any) // 1. 有引用 → 取引用节点的字段值 if len(node.InputSource) > 0 { for _, source := range node.InputSource { refNodeID := source.NodeId isQuoteOutput := source.QuoteOutput fields := source.Field refNode, ok := execInput.ConfigMap[refNodeID] if !ok { continue } inputMap := buildInputMap(refNode) outputMap := mergeOutput(refNode.OutputResult) modelMap := mergeModel(refNode.ModelConfig) if isQuoteOutput { for k, v := range outputMap { output[k] = v } } if len(fields) > 0 { // 取指定字段 for _, f := range fields { if v, ok := inputMap[f]; ok { input[f] = v } if v, ok := modelMap[f]; ok { model[f] = v } } } else { // 取全部 for k, v := range inputMap { input[k] = v } for k, v := range modelMap { model[k] = v } } } } 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 // 合并成你需要的 { key: { value: xxx } } 结构 func mergeModel(output node.ModelItem) map[string]any { m := make(map[string]any) // 遍历 output.ModelForm 里的每一个 key 和原始值 for key, rawValue := range output.ModelForm { // 包装成 { "value": 原始值 } m[key] = map[string]any{ "value": rawValue, } } return m } func StartLambda(ctx context.Context, input any) (any, error) { return input, nil } func FormLambda(ctx context.Context, input any) (any, error) { return input, nil } func IntentLambda(ctx context.Context, input any) (any, error) { nodeInput, ok := input.(*flowDto.NodeExecutionInput) if !ok { return nil, fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput,实际 %T", input) } // 1. 直接用你原来的方法(返回两个 map) inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config) var outputResult []node.NodeFormField 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 { if !strings.Contains(field.Field, "html") && !strings.Contains(field.Field, "img") { outputResult = append(outputResult, field) } } } for _, valueAny := range modelMap { if field, ok := valueAny.(node.NodeFormField); ok { outputResult = append(outputResult, field) } } nodeInput.Config.OutputResult = outputResult return nodeInput, nil } // JudgeLambda 分支判断核心:读取IntentLambda的输出 → 返回目标节点ID做路由 func JudgeLambda(ctx context.Context, input any) (string, error) { nodeInput, ok := input.(*flowDto.NodeExecutionInput) if !ok { return "", fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput,实际 %T", input) } out := new([]node.NodeFormField) err := gconv.Structs(nodeInput.Config.OutputResult, out) if err != nil { return "", err } contextParts := "" for _, v := range nodeInput.Config.FormConfig { contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value) } for _, v := range *out { contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value) } configMap := gconv.Map(nodeInput.Config.Config) ids := gconv.Strings(configMap["branch_ids"]) branchIdNameMap := gconv.Map(configMap["branch_id_name_map"]) // 【重构】构建提示词:展示ID和对应的名称 var branchIdNameLines []string for _, id := range ids { name := gconv.String(branchIdNameMap[id]) branchIdNameLines = append(branchIdNameLines, fmt.Sprintf("%s: %s", id, name)) } prompt := fmt.Sprintf(` 你是流程路由助手,你的任务是根据上下文,选择一个正确的节点ID返回。 规则: 1. 只允许从下面的可选节点ID列表中选择一个返回 2. 不要返回任何多余文字、标点、解释、标题 3. 只返回纯节点ID 可选节点ID(ID: 节点描述): %s 上下文内容: %s `, strings.Join(branchIdNameLines, "\n"), contextParts) getIsChatModel, err := GetIsChatModel(ctx) if err != nil { return "", err } req := flowDto.ComposeMessagesReq{ ModelName: getIsChatModel.ModelName, SkillName: "", IsBuild: true, Cause: "判断节点", Form: map[string]any{}, UserForm: map[string]any{"prompt": prompt}, UserFiles: nodeInput.Global.FileUrl, SessionId: nodeInput.Global.SessionId, } msg, err := ComposeMessages(ctx, &req) if err != nil { return "", err } taskResult, err := GatewayTask(ctx, msg.EpicycleId, getIsChatModel.ModelName, msg.Messages) if err != nil { return "", err } result, err := GetTaskResult(ctx, taskResult) if err != nil { return "", err } mapTaskResult := gconv.Map(result.Text) content := "" for key, _ := range getIsChatModel.ResponseBody { content = gconv.String(mapTaskResult[key]) } fmt.Printf("JudgeLambda路由:目标节点ID=%s\n", gconv.String(content)) return content, nil } // TextModelLambda 构建文案 func TextModelLambda(ctx context.Context, input any) (any, error) { nodeInput, ok := input.(*flowDto.NodeExecutionInput) if !ok { return nil, fmt.Errorf("入参类型错误") } // 1. 直接用你原来的方法(返回两个 map) inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config) var outputResult []node.NodeFormField 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 { if !strings.Contains(field.Field, "html") && !strings.Contains(field.Field, "img") { outputResult = append(outputResult, field) } } } for _, valueAny := range modelMap { if field, ok := valueAny.(node.NodeFormField); ok { outputResult = append(outputResult, field) } } resultUserFrom := make(map[string]any) for _, item := range outputResult { resultUserFrom[item.Label] = item } for _, item := range nodeInput.Config.FormConfig { resultUserFrom[item.Label] = item } if !g.IsEmpty(nodeInput.Global.Desc) { resultUserFrom["desc"] = node.NodeFormField{ Value: nodeInput.Global.Desc, Field: "desc", Label: "描述", Type: "text", } } resultFrom := make(map[string]any) for key, item := range nodeInput.Config.ModelConfig.ModelForm { resultFrom[key] = map[string]any{ "value": item, } } var skillName = nodeInput.Config.SkillName if g.IsEmpty(nodeInput.Config.SkillName) { skillName = nodeInput.Global.SkillName } contentStr := "你是专业内容生成助手,请严格按以下规则输出内容:\n1. 输出标准 HTML 片段,不要 Markdown,不要 ``` 符号,不要多余解释\n2. 整体用
\n6. 列表使用
需要配图:N 张
N 是这条文案需要的图片数量,只能是数字,不能是其他文字\n11. 只输出 HTML 结构,不输出任何额外文字" resultUserFrom["prompt"] = contentStr req := flowDto.ComposeMessagesReq{ ModelName: nodeInput.Config.ModelConfig.ModelName, SkillName: skillName, IsBuild: true, Cause: "文案节点", Form: resultFrom, UserForm: resultUserFrom, UserFiles: nodeInput.Global.FileUrl, SessionId: nodeInput.Global.SessionId, } //contentStr := "你是专业内容生成助手,请按以下通用规则输出内容:\n1. 输出标准 HTML 片段,不要 Markdown,不要 ``` 符号,不要多余解释\n2. 整体用\n6. 列表使用
\n6. 列表使用
需要配图:3 张
拿到 3) func extractImageCount(content string) int { re := regexp.MustCompile(`需要配图:(\d+) 张
`) match := re.FindStringSubmatch(content) if len(match) >= 2 { num, _ := strconv.Atoi(match[1]) return num } return 0 // 没找到默认 0 } // stripHtmlTags 去掉所有HTML标签,保留换行和文本结构,并删除配图标记行 func stripHtmlTags(html string) string { // 1. 替换块级标签为换行,保证排版 blockTags := regexp.MustCompile(`?(div|p|h1|h2|h3|h4|h5|h6|li|ul|ol|br|tr|td|th)[^>]*>`) text := blockTags.ReplaceAllString(html, "\n") // 2. 去掉所有剩余的 HTML 标签 allTags := regexp.MustCompile(`<[^>]+>`) text = allTags.ReplaceAllString(text, "") // 3. 🔥 新增:删除 "需要配图:X 张" 这一行(含前后可能的空格/换行) imageCountLine := regexp.MustCompile(`(?m)^\s*需要配图:\d+\s*张\s*$`) text = imageCountLine.ReplaceAllString(text, "") // 4. 清理多余空行(多个换行只保留一个,更干净) text = regexp.MustCompile(`\n\s*\n`).ReplaceAllString(text, "\n") // 5. 只去掉首尾空白,中间换行保留 text = strings.TrimSpace(text) return text } // SplitMultiContents 拆分模型返回的多条文案(基于HTML标签分隔) func SplitMultiContents(htmlContent string) []string { var contents []string // 正则匹配需要配图:X 张
if item.Content != "" { // 正则删除整行 re := regexp.MustCompile(`需要配图:\d+ 张
`) cleanContent := re.ReplaceAllString(item.Content, "") // 写入清理后的文案 htmlBuilder.WriteString(fmt.Sprintf(`