704 lines
22 KiB
Go
704 lines
22 KiB
Go
package flow
|
||
|
||
import (
|
||
"ai-agent/workflow/consts/node"
|
||
flowDto "ai-agent/workflow/model/dto/flow"
|
||
"ai-agent/workflow/model/entity"
|
||
"context"
|
||
"encoding/json"
|
||
"reflect"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/glog"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
"github.com/tidwall/gjson"
|
||
)
|
||
|
||
var (
|
||
// 匹配 [数字]
|
||
regNumIndex = regexp.MustCompile(`\[\d+\]`)
|
||
// 匹配 .attrs
|
||
regAttrs = regexp.MustCompile(`\.attrs`)
|
||
// 匹配带捕获组的数组下标,转扁平点分路径用
|
||
arrayIndexPath = regexp.MustCompile(`\[(\d+)\]`)
|
||
)
|
||
|
||
// 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, "")
|
||
index := CleanFieldPathReplaceNumIndex(path)
|
||
attrs := CleanFieldPathRemoveAttrs(index)
|
||
return attrs
|
||
}
|
||
|
||
func CleanFieldPathReplaceNumIndex(path string) string {
|
||
// 1. 替换 [数字] 为 [*]
|
||
s := regNumIndex.ReplaceAllString(path, `.#`)
|
||
return s
|
||
}
|
||
|
||
func CleanFieldPathRemoveAttrs(path string) string {
|
||
// 2. 移除所有 .attrs
|
||
s := regAttrs.ReplaceAllString(path, "")
|
||
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: "<jsonType>", 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
|
||
}
|
||
if tmplArr, isArr := tmplVal.([]any); isArr {
|
||
result[key] = mapTemplateArray(tmplArr, srcVal)
|
||
continue
|
||
}
|
||
result[key] = srcVal
|
||
}
|
||
return result
|
||
}
|
||
|
||
// mapTemplateArray 按模板数组的元素结构映射 source 数组:
|
||
// 模板首元素为对象时,逐元素按 MapResultByTemplate 过滤只保留模板字段;
|
||
// 模板数组为空或首元素非对象(无法确定元素结构)时,原样拷贝 source 数组。
|
||
func mapTemplateArray(tmplArr []any, srcVal any) any {
|
||
srcList, ok := srcVal.([]any)
|
||
if !ok || len(tmplArr) == 0 {
|
||
return srcVal
|
||
}
|
||
elemTmpl, ok := tmplArr[0].(map[string]any)
|
||
if !ok {
|
||
return srcVal
|
||
}
|
||
result := make([]any, 0, len(srcList))
|
||
for _, srcElem := range srcList {
|
||
if srcMap, isMap := srcElem.(map[string]any); isMap {
|
||
result = append(result, MapResultByTemplate(elemTmpl, srcMap))
|
||
} else {
|
||
result = append(result, srcElem)
|
||
}
|
||
}
|
||
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 是各段扁平请求参数,按段序收集字段为数组(段位留 nil)
|
||
// - 其他节点:读 OutputResult 中 fieldName 路径对应的值
|
||
func resolveValueSource(global *flowDto.FlowExecutionInput, nodeId, field 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"]) != field {
|
||
continue
|
||
}
|
||
if !g.IsEmpty(output["value"]) {
|
||
return output["value"], output["refsName"], true
|
||
}
|
||
}
|
||
case node.NodeTypeScriptTranscribe:
|
||
// 脚本转写节点 OutputResult 是各段扁平请求参数(split_shots_pipeline 产出,key 为字面量
|
||
// prompt/duration/seed 等),按段序读取 output[field] 收集为数组,供分段模型节点整体引用。
|
||
// 每段都占一位(字段缺失/为空留 nil),保证数组与段序对齐,供 split_segment 按段取值。
|
||
var list []any
|
||
for _, output := range nodeConfig.OutputResult {
|
||
list = append(list, output[field])
|
||
}
|
||
for _, v := range list {
|
||
if !g.IsEmpty(v) {
|
||
return list, "", true
|
||
}
|
||
}
|
||
default:
|
||
// templates 是模型节点在前端配置的静态输出模板,不在 OutputResult 中,需单独取
|
||
if field == "templates" {
|
||
if !g.IsEmpty(nodeConfig.Templates) {
|
||
return nodeConfig.Templates, "", true
|
||
}
|
||
return nil, nil, false
|
||
}
|
||
for _, output := range nodeConfig.OutputResult {
|
||
// 模型节点输出记录是单 key 的字面量扁平 key(如 "choices.attrs[0].attrs.delta.attrs.content"),
|
||
// gjson 会把 . 和 [0] 当结构路径解析,无法命中字面量 key,故先按字面量 key 直接取值;
|
||
// 未命中再回退 gjson 路径查询(兼容真正嵌套的输出结构)。
|
||
if v, has := output[field]; has {
|
||
value = v
|
||
} else {
|
||
value = gjson.Get(gconv.String(output), field).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{}:
|
||
// 有 valueSource:解析引用节点值
|
||
if valueSource, hasSource := v["valueSource"]; hasSource {
|
||
sources := new([]entity.ValueSource)
|
||
gconv.Structs(valueSource, sources)
|
||
|
||
// 多个引用源:把各源解析出的值拼成 "label: value"(无 label 只拼值),逗号分隔
|
||
if len(*sources) > 1 {
|
||
parts := make([]string, 0, len(*sources))
|
||
var refsName any
|
||
for _, src := range *sources {
|
||
value, rn, ok := resolveValueSource(globalParams, src.NodeId, src.Field)
|
||
if !ok || schemaValueEmpty(value) {
|
||
continue
|
||
}
|
||
// 引用非模型节点(开始/表单/HTTP/脚本转写等)时,值按当前字段声明的 type 做类型化转换;
|
||
// 模型节点值由模型网关处理,复制时不需要转换
|
||
if !isModelSourceNode(globalParams, src.NodeId) {
|
||
value = assignBySchemaType(v, value)
|
||
}
|
||
text := toPlainString(value)
|
||
if src.Label != "" {
|
||
text = src.Label + ": " + text
|
||
}
|
||
parts = append(parts, text)
|
||
if !g.IsEmpty(rn) && g.IsEmpty(refsName) {
|
||
refsName = rn
|
||
}
|
||
}
|
||
if len(parts) > 0 {
|
||
v["value"] = strings.Join(parts, ", ")
|
||
if !g.IsEmpty(refsName) {
|
||
v["refsName"] = refsName
|
||
}
|
||
return
|
||
}
|
||
} else if len(*sources) == 1 {
|
||
// 单个引用源:保持旧行为,值按原样赋值(非模型节点按声明 type 转换)
|
||
src := (*sources)[0]
|
||
value, refsName, ok := resolveValueSource(globalParams, src.NodeId, src.Field)
|
||
if ok && !isModelSourceNode(globalParams, src.NodeId) {
|
||
value = assignBySchemaType(v, value)
|
||
}
|
||
if ok && !schemaValueEmpty(value) {
|
||
v["value"] = value
|
||
if !g.IsEmpty(refsName) {
|
||
v["refsName"] = refsName
|
||
}
|
||
return
|
||
}
|
||
}
|
||
}
|
||
// 统一兜底:无 valueSource(或解析失败/值为空)时,value 为空或 0 则取 defaultValue
|
||
if defaultValue, hasDefault := v["defaultValue"]; hasDefault && isEmptyForFallback(v["value"]) && !schemaValueEmpty(defaultValue) {
|
||
v["value"] = defaultValue
|
||
}
|
||
// 递归遍历所有子元素
|
||
for _, child := range v {
|
||
walkMap(child, globalParams)
|
||
}
|
||
case []interface{}:
|
||
// 数组遍历
|
||
for _, item := range v {
|
||
walkMap(item, globalParams)
|
||
}
|
||
}
|
||
}
|
||
|
||
// isEmptyForFallback 兜底场景判空:除 schemaValueEmpty 规则外,数字 0 也视为未填写,
|
||
// 便于配置了 defaultValue 的字段在值为 0 时用默认值兜底。
|
||
// 覆盖 json.Number(gconv 反序列化数字的运行时类型)与字符串 "0"/"0.0"。
|
||
func isEmptyForFallback(v interface{}) bool {
|
||
if schemaValueEmpty(v) {
|
||
return true
|
||
}
|
||
switch val := v.(type) {
|
||
case float32:
|
||
return val == 0
|
||
case float64:
|
||
return val == 0
|
||
case int:
|
||
return val == 0
|
||
case int8:
|
||
return val == 0
|
||
case int16:
|
||
return val == 0
|
||
case int32:
|
||
return val == 0
|
||
case int64:
|
||
return val == 0
|
||
case uint:
|
||
return val == 0
|
||
case uint8:
|
||
return val == 0
|
||
case uint16:
|
||
return val == 0
|
||
case uint32:
|
||
return val == 0
|
||
case uint64:
|
||
return val == 0
|
||
case json.Number:
|
||
if f, err := val.Float64(); err == nil {
|
||
return f == 0
|
||
}
|
||
case string:
|
||
if f, err := strconv.ParseFloat(val, 64); err == nil {
|
||
return f == 0
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// isModelSourceNode 判断引用源节点是否为模型节点(值由模型网关处理,复制时不转换)
|
||
func isModelSourceNode(global *flowDto.FlowExecutionInput, nodeId string) bool {
|
||
if global == nil || global.ConfigMap == nil {
|
||
return false
|
||
}
|
||
nodeConfig := global.ConfigMap[nodeId]
|
||
return nodeConfig != nil && nodeConfig.NodeCode == node.NodeTypeModel
|
||
}
|
||
|
||
// assignBySchemaType 按当前字段声明的 schema 类型把值类型化:
|
||
// string 遇数组/对象转 JSON 字符串;number/boolean 解析字符串;object/array 解析 JSON 字符串;其余原样返回
|
||
func assignBySchemaType(node map[string]interface{}, value any) any {
|
||
t, _ := node["type"].(string)
|
||
return assignByType(t, value)
|
||
}
|
||
|
||
// assignByType 按字段声明的 type 把值类型化;walkMap 的 schema 节点与 parseMap 的模型参数共用
|
||
func assignByType(t string, value any) any {
|
||
switch t {
|
||
case "string":
|
||
return toSchemaString(value)
|
||
case "number":
|
||
return toSchemaNumber(value)
|
||
case "boolean":
|
||
return toSchemaBool(value)
|
||
case "object", "array":
|
||
return toSchemaStruct(value)
|
||
default:
|
||
return value
|
||
}
|
||
}
|
||
|
||
// toSchemaString 转 string:字符串原样,数组元素拼成字符串(单元素取元素本身,多元素逗号连接),对象序列化为 JSON 字符串
|
||
func toSchemaString(v any) any {
|
||
switch val := v.(type) {
|
||
case []interface{}:
|
||
parts := make([]string, 0, len(val))
|
||
for _, item := range val {
|
||
parts = append(parts, toPlainString(item))
|
||
}
|
||
return strings.Join(parts, ",")
|
||
case map[string]interface{}:
|
||
if b, err := json.Marshal(val); err == nil {
|
||
return string(b)
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
|
||
// toPlainString 把数组元素转成不带括号的纯字符串:
|
||
// 数组([]any / 类型化切片)逐元素取纯字符串,单元素取元素本身,多元素逗号连接;
|
||
// 对象序列化为 JSON 字符串;其余原样字符串化。
|
||
func toPlainString(v any) string {
|
||
if s, ok := v.(string); ok {
|
||
return s
|
||
}
|
||
switch val := v.(type) {
|
||
case []interface{}:
|
||
parts := make([]string, 0, len(val))
|
||
for _, item := range val {
|
||
parts = append(parts, toPlainString(item))
|
||
}
|
||
return strings.Join(parts, ",")
|
||
case map[string]interface{}:
|
||
if b, err := json.Marshal(val); err == nil {
|
||
return string(b)
|
||
}
|
||
}
|
||
rv := reflect.ValueOf(v)
|
||
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) {
|
||
parts := make([]string, 0, rv.Len())
|
||
for i := 0; i < rv.Len(); i++ {
|
||
parts = append(parts, toPlainString(rv.Index(i).Interface()))
|
||
}
|
||
return strings.Join(parts, ",")
|
||
}
|
||
if b, err := json.Marshal(v); err == nil {
|
||
return string(b)
|
||
}
|
||
return gconv.String(v)
|
||
}
|
||
|
||
// toSchemaNumber 转 number:数字原样,字符串尝试解析为 float64,失败原样返回
|
||
func toSchemaNumber(v any) any {
|
||
if s, ok := v.(string); ok {
|
||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||
return f
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
|
||
// toSchemaBool 转 boolean:布尔原样,字符串尝试解析为 bool,失败原样返回
|
||
func toSchemaBool(v any) any {
|
||
if s, ok := v.(string); ok {
|
||
if b, err := strconv.ParseBool(s); err == nil {
|
||
return b
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
|
||
// toSchemaStruct 转 object/array:合法 JSON 字符串解析为结构化数据,否则原样返回
|
||
func toSchemaStruct(v any) any {
|
||
s, ok := v.(string)
|
||
if !ok {
|
||
return v
|
||
}
|
||
if !json.Valid([]byte(s)) {
|
||
return v
|
||
}
|
||
var out any
|
||
if err := json.Unmarshal([]byte(s), &out); err != nil {
|
||
return v
|
||
}
|
||
return out
|
||
}
|
||
|
||
// 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: <schemaEditorType>} 包装节点
|
||
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
|
||
}
|
||
}
|
||
|
||
// BuildModelRequestBody 从参数定义 + 全局执行上下文构建最终嵌套 JSON 请求体。
|
||
func BuildModelRequestBody(params []entity.FlowModelParams, globalParams *flowDto.FlowExecutionInput) (map[string]interface{}, error) {
|
||
// 1. 解析引用、过滤空值
|
||
resolved := parseMap(params, globalParams)
|
||
|
||
// 2. 转扁平路径映射
|
||
flat := toFlatMap(resolved)
|
||
|
||
// 3. 引用了脚本转写节点的字段打内部标记 __segment_fields(逗号分隔的扁平路径),
|
||
// 供前置处理器 split_segment 按段拆批;__ 前缀内部键由 invokePreTool 统一剥离,不传给模型网关
|
||
if seg := segmentFields(resolved, globalParams); len(seg) > 0 {
|
||
flat["__segment_fields"] = strings.Join(seg, ",")
|
||
}
|
||
|
||
return flat, nil
|
||
}
|
||
|
||
// segmentFields 收集引用了脚本转写节点的字段扁平路径(分段字段)。
|
||
// 脚本转写节点按段产出一份扁平参数列表,下游模型节点单源引用其字段时,
|
||
// 值按段序聚合成数组(见 resolveValueSource 的 scriptTranscribe 分支),需随批拆分。
|
||
func segmentFields(resolved []entity.FlowModelParams, globalParams *flowDto.FlowExecutionInput) []string {
|
||
if globalParams == nil || globalParams.ConfigMap == nil {
|
||
return nil
|
||
}
|
||
var fields []string
|
||
for _, p := range resolved {
|
||
if g.IsEmpty(p.Path) || len(p.ValueSource) != 1 {
|
||
continue
|
||
}
|
||
src := p.ValueSource[0]
|
||
if nodeConfig := globalParams.ConfigMap[src.NodeId]; nodeConfig != nil &&
|
||
nodeConfig.NodeCode == node.NodeTypeScriptTranscribe {
|
||
fields = append(fields, flatPath(p.Path))
|
||
}
|
||
}
|
||
return fields
|
||
}
|
||
|
||
// parseMap 解析模型请求参数
|
||
func parseMap(data []entity.FlowModelParams, globalParams *flowDto.FlowExecutionInput) []entity.FlowModelParams {
|
||
newData := make([]entity.FlowModelParams, 0, len(data))
|
||
for _, item := range data {
|
||
var d entity.FlowModelParams
|
||
d.Path = item.Path
|
||
d.Type = item.Type
|
||
|
||
// 无引用源:直接取静态值
|
||
if g.IsEmpty(item.ValueSource) {
|
||
if isParamEmpty(item.Value) {
|
||
continue
|
||
}
|
||
d = item
|
||
newData = append(newData, d)
|
||
continue
|
||
}
|
||
|
||
// 有引用源:单源保持旧行为;多源把各源解析出的值拼成 "label: value"(无 label 只拼值),逗号分隔
|
||
var value, refsName any
|
||
if len(item.ValueSource) > 1 {
|
||
parts := make([]string, 0, len(item.ValueSource))
|
||
for _, src := range item.ValueSource {
|
||
v, rn, ok := resolveValueSource(globalParams, src.NodeId, src.Field)
|
||
if !ok || isParamEmpty(v) {
|
||
continue
|
||
}
|
||
// 模型解析时引用其他节点的值不做类型化转换;
|
||
// 多引用源需拼接为字符串,用 toPlainString 渲染(数组取元素去括号)
|
||
text := toPlainString(v)
|
||
if src.Label != "" {
|
||
text = src.Label + ": " + text
|
||
}
|
||
parts = append(parts, text)
|
||
if !g.IsEmpty(rn) && g.IsEmpty(refsName) {
|
||
refsName = rn
|
||
}
|
||
}
|
||
if len(parts) == 0 {
|
||
// 解析失败不静默,留日志便于排查引用丢失
|
||
glog.Debugf(context.Background(),
|
||
"resolve value source failed, nodeId=%+v path=%s",
|
||
item.ValueSource, item.Path)
|
||
continue
|
||
}
|
||
d.Value = strings.Join(parts, ", ")
|
||
d.RefsName = gconv.String(refsName)
|
||
d.ValueSource = item.ValueSource
|
||
newData = append(newData, d)
|
||
continue
|
||
}
|
||
|
||
// 单个引用源:解析取非空值;模型解析时引用其他节点的数组值不做类型化转换,整体传给模型
|
||
src := item.ValueSource[0]
|
||
value, refsName, ok := resolveValueSource(globalParams, src.NodeId, src.Field)
|
||
if !ok || isParamEmpty(value) {
|
||
// 解析失败不静默,留日志便于排查引用丢失
|
||
glog.Debugf(context.Background(),
|
||
"resolve value source failed, nodeId=%+v path=%s",
|
||
item.ValueSource, item.Path)
|
||
continue
|
||
}
|
||
d.Value = value
|
||
d.RefsName = gconv.String(refsName)
|
||
d.ValueSource = item.ValueSource
|
||
newData = append(newData, d)
|
||
}
|
||
return newData
|
||
}
|
||
|
||
// toFlatMap 将解析后的参数列表转为 sjson 可用的扁平路径映射。
|
||
// key 为 Path,value 为参数值;保留 RefsName 供上层追踪引用来源。
|
||
func toFlatMap(params []entity.FlowModelParams) map[string]interface{} {
|
||
m := make(map[string]interface{}, len(params))
|
||
for _, p := range params {
|
||
if g.IsEmpty(p.Path) {
|
||
continue
|
||
}
|
||
m[flatPath(p.Path)] = p.Value
|
||
}
|
||
return m
|
||
}
|
||
|
||
// flatPath 把数组下标路径转扁平点分路径:a[0].b → a.0.b。
|
||
func flatPath(path string) string {
|
||
return arrayIndexPath.ReplaceAllString(path, `.$1`)
|
||
}
|
||
|
||
// isParamEmpty 判断参数值是否为"空"。
|
||
// 仅 nil、空字符串、空切片/映射视为空;0、false 等零值是合法值,保留。
|
||
func isParamEmpty(v interface{}) bool {
|
||
if v == nil {
|
||
return true
|
||
}
|
||
switch val := v.(type) {
|
||
case string:
|
||
return val == ""
|
||
case []byte:
|
||
return len(val) == 0
|
||
case []interface{}:
|
||
return len(val) == 0
|
||
case map[string]interface{}:
|
||
return len(val) == 0
|
||
default:
|
||
// 数字、布尔、结构体等一律视为非空
|
||
return false
|
||
}
|
||
}
|