Files
ai-agent/workflow/service/flow/values/value_request.go
T
19904408334 d699f7ce14 feat(workflow): 增加工作流计费与执行生命周期管理
- 新增计费模块:执行开始建单、终态结算/取消/失败处理,支持按条/按秒/按token计费
- 新增执行生命周期跟踪:优雅关停时取消运行中执行并等待落库
- 新增异步任务等待/通知机制(Wait/Notify)
- 重构执行记录落库与进度上报,统一失败分类与重试语义
- 重命名文件:async_task.go→async.go、flow_checkpoint_store.go→exec_checkpoint.go、flow_graph_util.go→exec_record.go
- 更新 .gitignore 与数据库密码配置
2026-09-03 13:22:22 +08:00

141 lines
4.4 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 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 为 Pathvalue 为参数值;保留 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
}
}