- 新增计费模块:执行开始建单、终态结算/取消/失败处理,支持按条/按秒/按token计费 - 新增执行生命周期跟踪:优雅关停时取消运行中执行并等待落库 - 新增异步任务等待/通知机制(Wait/Notify) - 重构执行记录落库与进度上报,统一失败分类与重试语义 - 重命名文件:async_task.go→async.go、flow_checkpoint_store.go→exec_checkpoint.go、flow_graph_util.go→exec_record.go - 更新 .gitignore 与数据库密码配置
141 lines
4.4 KiB
Go
141 lines
4.4 KiB
Go
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
|
||
}
|
||
}
|