Files
ai-agent/workflow/service/flow/lambda_value_source.go
T

288 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 节点是否已无有效内容:
// 标量看 value0/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
}
}