288 lines
8.7 KiB
Go
288 lines
8.7 KiB
Go
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: "<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
|
||
}
|
||
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: <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
|
||
}
|
||
}
|