package flow import ( "ai-agent/workflow/consts/node" flowDto "ai-agent/workflow/model/dto/flow" "regexp" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" "github.com/tidwall/gjson" ) var ( // 匹配 [数字] regNumIndex = regexp.MustCompile(`\[\d+\]`) // 匹配 .attrs regAttrs = regexp.MustCompile(`\.attrs`) ) // CleanFieldPath 清理字段路径:移除 .attrs、数字下标转为 [*] // 示例:usage.attrs.total_tokens → usage.total_tokens // 示例:choices.attrs[0].attrs.message.attrs.content → choices[*].message.content func CleanFieldPath(path string) string { // 1. 替换 [数字] 为 [*] s := regNumIndex.ReplaceAllString(path, `.#`) // 2. 移除所有 .attrs s = regAttrs.ReplaceAllString(s, "") return s } // UnwrapSchemaWrapper 递归剥掉 json-schema-editor 输出的 {type, value/attrs} 包裹层, // 只保留干净的 key/value 嵌套结构。 // 示例: // // {"a": {"type":"string","value":"hi"}} → {"a": "hi"} // {"b": {"type":"object","attrs":{"c":1}}} → {"b": {"c": 1}} // {"arr": {"type":"array","attrs":[{"type":"number","value":1}]}} → {"arr": [1]} func UnwrapSchemaWrapper(v any) any { switch val := v.(type) { case map[string]any: // 识别包裹节点:{type: "", value/attrs: <实际值>, ...} if t, ok := val["type"].(string); ok && isSchemaEditorType(t) { dataKey := "value" if t == "object" || t == "array" { dataKey = "attrs" } if raw, has := val[dataKey]; has { return UnwrapSchemaWrapper(raw) } } res := make(map[string]any, len(val)) for k, child := range val { res[k] = UnwrapSchemaWrapper(child) } return res case []any: res := make([]any, len(val)) for i, item := range val { res[i] = UnwrapSchemaWrapper(item) } return res default: return val } } // isSchemaEditorType 是否为 json-schema-editor 的 6 种类型标识 func isSchemaEditorType(t string) bool { switch t { case "string", "number", "boolean", "null", "object", "array": return true } return false } // MapResultByTemplate 按 template 定义的结构,从 source 中拷贝对应字段的值。 // 只保留 template 里出现的字段:对象字段按同名字段递归拷贝,标量/数组字段直接拷贝 source 的值。 func MapResultByTemplate(template map[string]any, source map[string]any) map[string]any { result := make(map[string]any, len(template)) for key, tmplVal := range template { srcVal, ok := source[key] if !ok { continue } if tmplMap, isMap := tmplVal.(map[string]any); isMap { if srcMap, isMap := srcVal.(map[string]any); isMap { result[key] = MapResultByTemplate(tmplMap, srcMap) } continue } result[key] = srcVal } return result } // ProcessValueSourceRecursive 递归遍历map,同级同时存在value和valueSource则把value设置为"AA" func ProcessValueSourceRecursive(rawParams map[string]interface{}, globalParams *flowDto.FlowExecutionInput) { walkMap(rawParams, globalParams) } // resolveValueSource 解析 valueSource {nodeId, fieldName} 引用的实际值。 // 返回 (value, refsName, ok);ok=false 表示引用节点不存在或引用值仍为空。 // - 开始/表单节点:OutputConfig 平铺条目按 field == fieldName 匹配(前端约定以 field 为主, // 不兼容 path),直接读 entry 的 value / refsName // - scriptTranscribe 节点:读 OutputResult 的 shots // - 其他节点:读 OutputResult 中 fieldName 路径对应的值 func resolveValueSource(global *flowDto.FlowExecutionInput, nodeId, fieldName string) (value any, refsName any, ok bool) { if global == nil || global.ConfigMap == nil { return nil, nil, false } nodeConfig := global.ConfigMap[nodeId] if nodeConfig == nil { return nil, nil, false } switch nodeConfig.NodeCode { case node.NodeTypeStart, node.NodeTypeForm: for _, output := range nodeConfig.OutputConfig { if gconv.String(output["field"]) != fieldName { continue } if !g.IsEmpty(output["value"]) { return output["value"], output["refsName"], true } } case node.NodeTypeScriptTranscribe: for _, output := range nodeConfig.OutputResult { value := gjson.Get(gconv.String(output), CleanFieldPath("shots")).Value() if !g.IsEmpty(value) { return value, gjson.Get(gconv.String(output), CleanFieldPath("refsName")).Value(), true } } default: for _, output := range nodeConfig.OutputResult { value := gjson.Get(gconv.String(output), CleanFieldPath(fieldName)).Value() if !g.IsEmpty(value) { return value, gjson.Get(gconv.String(output), CleanFieldPath("refsName")).Value(), true } } } return nil, nil, false } // walkMap 递归处理map/数组 func walkMap(data interface{}, globalParams *flowDto.FlowExecutionInput) { switch v := data.(type) { case map[string]interface{}: // 当前对象同时存在 value 和 valueSource if valueSource, hasSource := v["valueSource"]; hasSource { mapValueSource := gconv.Map(valueSource) nodeId := gconv.String(mapValueSource["nodeId"]) fieldName := gconv.String(mapValueSource["fieldName"]) if fieldName == "" { fieldName = gconv.String(mapValueSource["field"]) } if nodeId != "" && fieldName != "" { if value, refsName, ok := resolveValueSource(globalParams, nodeId, fieldName); ok { v["value"] = value if !g.IsEmpty(refsName) { v["refsName"] = refsName } return } } } // 递归遍历所有子元素 for _, child := range v { walkMap(child, globalParams) } case []interface{}: // 数组遍历 for _, item := range v { walkMap(item, globalParams) } } } // CleanEmptyModelParams 剔除模型请求参数中 value 为空的字段; // 数组/枚举(attrs / enumValues)元素整体为空时移除整个元素。0/false 视为有效值。 func CleanEmptyModelParams(params map[string]interface{}) { cleanSchemaMap(params) } // cleanSchemaMap 递归清理普通 map:包装节点按 schema 语义清理,空字段删除 func cleanSchemaMap(m map[string]interface{}) { for key, val := range m { switch v := val.(type) { case map[string]interface{}: if isSchemaWrapperNode(v) { cleanSchemaWrapper(v) if isSchemaNodeEmpty(v) { delete(m, key) } } else { cleanSchemaMap(v) } case []interface{}: m[key] = cleanSchemaSlice(v) } } } // cleanSchemaWrapper 清理单个 {type,...} 包装节点:递归 value / attrs / enumValues 容器 func cleanSchemaWrapper(node map[string]interface{}) { if mv, ok := node["value"].(map[string]interface{}); ok { cleanSchemaMap(mv) } if lv, ok := node["value"].([]interface{}); ok { node["value"] = cleanSchemaSlice(lv) } if attrs, ok := node["attrs"].(map[string]interface{}); ok { cleanSchemaMap(attrs) } if attrs, ok := node["attrs"].([]interface{}); ok { node["attrs"] = cleanSchemaSlice(attrs) } if evs, ok := node["enumValues"].([]interface{}); ok { node["enumValues"] = cleanSchemaSlice(evs) } } // cleanSchemaSlice 清理数组/枚举元素,元素为包装节点且整体为空时移除 func cleanSchemaSlice(list []interface{}) []interface{} { i := 0 for i < len(list) { if item, ok := list[i].(map[string]interface{}); ok { if isSchemaWrapperNode(item) { cleanSchemaWrapper(item) if isSchemaNodeEmpty(item) { list = append(list[:i], list[i+1:]...) continue } } else { cleanSchemaMap(item) } } i++ } return list } // isSchemaWrapperNode 是否为 {type: } 包装节点 func isSchemaWrapperNode(m map[string]interface{}) bool { t, ok := m["type"].(string) return ok && isSchemaEditorType(t) } // isSchemaNodeEmpty 判断 schema 节点是否已无有效内容: // 标量看 value(0/false 有效);object/array 看 value/attrs/enumValues 容器是否都为空 func isSchemaNodeEmpty(node map[string]interface{}) bool { t, _ := node["type"].(string) switch t { case "object": return schemaContainerEmpty(node, "value") && schemaContainerEmpty(node, "attrs") case "array": return schemaContainerEmpty(node, "value") && schemaContainerEmpty(node, "attrs") && schemaContainerEmpty(node, "enumValues") default: return schemaValueEmpty(node["value"]) } } // schemaContainerEmpty 容器(value/attrs/enumValues)是否为空 func schemaContainerEmpty(node map[string]interface{}, key string) bool { switch v := node[key].(type) { case []interface{}: return len(v) == 0 case map[string]interface{}: return len(v) == 0 default: return v == nil } } // schemaValueEmpty 值是否为空;0/false 视为有效值不剔除 func schemaValueEmpty(v interface{}) bool { switch val := v.(type) { case nil: return true case string: return val == "" case []interface{}: return len(val) == 0 case map[string]interface{}: return len(val) == 0 default: return false } }