package values import ( "ai-agent/workflow/consts/node" flowDto "ai-agent/workflow/model/dto/flow" "ai-agent/workflow/model/entity" "context" "strings" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/util/gconv" ) // 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 { text, rn, ok := joinValueSources(globalParams, item.ValueSource, isParamEmpty, nil) if !ok { parseMapLogFail(item) continue } value, refsName = text, rn } else { src := item.ValueSource[0] var ok bool value, refsName, ok = ResolveValueSource(globalParams, src.NodeId, src.Field) if !ok || isParamEmpty(value) { parseMapLogFail(item) continue } } d.Value = value d.RefsName = gconv.String(refsName) d.ValueSource = item.ValueSource newData = append(newData, d) } return newData } // parseMapLogFail 引用解析失败留日志(单源/多源共用,不静默,便于排查引用丢失) func parseMapLogFail(item entity.FlowModelParams) { glog.Debugf(context.Background(), "resolve value source failed, nodeId=%+v path=%s", item.ValueSource, item.Path) } // 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 } }