feat: 新增执行记录实体与文件上传能力
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
// Package split_batch 工作流前置处理器:按各字段 constraint.uploadTotalMaxCount 拆分模型请求参数。
|
||||
// 处理器实现自包含(算法随处理器走,不依赖业务包),通过 init 注册进 processor 注册表。
|
||||
package split_batch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"ai-agent/workflow/service/flow/processor"
|
||||
)
|
||||
|
||||
func init() {
|
||||
processor.Register(SplitBatchModelParamsProcessor())
|
||||
}
|
||||
|
||||
// SplitBatchModelParamsProcessor 将模型请求参数按各字段 constraint.uploadTotalMaxCount 分批的前置处理器。
|
||||
// 入参 args 即模型请求参数本体(需已解析好 valueSource,value 已填充)。
|
||||
func SplitBatchModelParamsProcessor() *processor.Processor {
|
||||
return &processor.Processor{
|
||||
Name: "split_batch_model_params",
|
||||
Description: "按 constraint.uploadTotalMaxCount 分批模型请求参数",
|
||||
Func: func(ctx context.Context, args map[string]any) (any, error) {
|
||||
if args == nil {
|
||||
return nil, fmt.Errorf("缺少模型请求参数")
|
||||
}
|
||||
return SplitBatchModelParams(args), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// splitBatchField 描述一个需要按 constraint.uploadTotalMaxCount 分批的字段
|
||||
type splitBatchField struct {
|
||||
path []any // 从参数根节点到该字段 value 的路径(map 用 key,数组用下标)
|
||||
items []any // 该字段 value 拆出的待分批元素列表
|
||||
maxCount int // 每批最大元素数(constraint.uploadTotalMaxCount)
|
||||
}
|
||||
|
||||
// SplitBatchModelParams 把模型请求参数拆成多批,供分批请求模型使用。
|
||||
// 处理流程:
|
||||
// 1. 深拷贝入参,避免污染调用方数据;
|
||||
// 2. 递归解析 valueSource,把引用节点输出的字段值写入对应字段的 value;
|
||||
// 3. 找出所有带 constraint.uploadTotalMaxCount 且 value 为集合(map/slice)的字段,
|
||||
// map 按 key 排序取 value 列表作为元素;总批数 = 各字段 (元素数/上限) 向上取整的最大值;
|
||||
// 4. 每批 = 整份参数深拷贝 + 各分批字段 value 替换为对应切片。
|
||||
//
|
||||
// 未超量时返回单份(value 已被解析填充)参数。调用方可遍历返回值逐个请求模型。
|
||||
func SplitBatchModelParams(rawParams map[string]any) []map[string]any {
|
||||
params, ok := deepCopyAny(rawParams).(map[string]any)
|
||||
if !ok {
|
||||
params = make(map[string]any)
|
||||
}
|
||||
|
||||
fields := make([]splitBatchField, 0)
|
||||
collectBatchFields(params, nil, &fields)
|
||||
|
||||
batchCount := 1
|
||||
for _, f := range fields {
|
||||
n := (len(f.items) + f.maxCount - 1) / f.maxCount
|
||||
if n > batchCount {
|
||||
batchCount = n
|
||||
}
|
||||
}
|
||||
if batchCount <= 1 {
|
||||
return []map[string]any{params}
|
||||
}
|
||||
|
||||
batches := make([]map[string]any, 0, batchCount)
|
||||
for i := 0; i < batchCount; i++ {
|
||||
batch, _ := deepCopyAny(params).(map[string]any)
|
||||
for _, f := range fields {
|
||||
start := i * f.maxCount
|
||||
if start >= len(f.items) {
|
||||
setValueAtPath(batch, f.path, []any{})
|
||||
continue
|
||||
}
|
||||
end := start + f.maxCount
|
||||
if end > len(f.items) {
|
||||
end = len(f.items)
|
||||
}
|
||||
setValueAtPath(batch, f.path, f.items[start:end])
|
||||
}
|
||||
batches = append(batches, batch)
|
||||
}
|
||||
return batches
|
||||
}
|
||||
|
||||
// collectBatchFields 递归收集所有带 constraint.uploadTotalMaxCount 且 value 为集合的分批字段
|
||||
func collectBatchFields(node any, path []any, out *[]splitBatchField) {
|
||||
switch v := node.(type) {
|
||||
case map[string]any:
|
||||
if maxCount, ok := uploadTotalMaxCountOf(v); ok {
|
||||
if items, has := toItems(v["value"]); has {
|
||||
*out = append(*out, splitBatchField{
|
||||
path: append(append([]any{}, path...), "value"),
|
||||
items: items,
|
||||
maxCount: maxCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
for key, child := range v {
|
||||
collectBatchFields(child, append(append([]any{}, path...), key), out)
|
||||
}
|
||||
case []any:
|
||||
for i, child := range v {
|
||||
collectBatchFields(child, append(append([]any{}, path...), i), out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uploadTotalMaxCountOf 读取 schema 节点 constraint.uploadTotalMaxCount
|
||||
func uploadTotalMaxCountOf(m map[string]any) (int, bool) {
|
||||
c, ok := m["constraint"]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
cm := gconv.Map(c)
|
||||
if cm == nil {
|
||||
return 0, false
|
||||
}
|
||||
n := gconv.Int(cm["uploadTotalMaxCount"])
|
||||
if n <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// toItems 把集合 value 转成元素列表:切片原样返回;map 按 key 排序取 value,保证分批顺序稳定
|
||||
func toItems(v any) ([]any, bool) {
|
||||
switch val := v.(type) {
|
||||
case []any:
|
||||
return val, len(val) > 0
|
||||
case map[string]any:
|
||||
if len(val) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
keys := make([]string, 0, len(val))
|
||||
for k := range val {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
items := make([]any, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
items = append(items, val[k])
|
||||
}
|
||||
return items, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// setValueAtPath 沿 path 逐级导航(map 用 key、数组用下标),在最后一级写入 value
|
||||
func setValueAtPath(root map[string]any, path []any, value any) {
|
||||
if len(path) == 0 {
|
||||
return
|
||||
}
|
||||
var cur any = root
|
||||
for i := 0; i < len(path)-1; i++ {
|
||||
switch step := path[i].(type) {
|
||||
case string:
|
||||
m, ok := cur.(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
cur = m[step]
|
||||
case int:
|
||||
s, ok := cur.([]any)
|
||||
if !ok || step < 0 || step >= len(s) {
|
||||
return
|
||||
}
|
||||
cur = s[step]
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
switch last := path[len(path)-1].(type) {
|
||||
case string:
|
||||
if m, ok := cur.(map[string]any); ok {
|
||||
m[last] = value
|
||||
}
|
||||
case int:
|
||||
if s, ok := cur.([]any); ok && last >= 0 && last < len(s) {
|
||||
s[last] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deepCopyAny 深拷贝 map[string]any / []any 嵌套结构,避免批次之间互相影响
|
||||
func deepCopyAny(v any) any {
|
||||
switch val := v.(type) {
|
||||
case map[string]any:
|
||||
res := make(map[string]any, len(val))
|
||||
for k, child := range val {
|
||||
res[k] = deepCopyAny(child)
|
||||
}
|
||||
return res
|
||||
case []any:
|
||||
res := make([]any, len(val))
|
||||
for i, child := range val {
|
||||
res[i] = deepCopyAny(child)
|
||||
}
|
||||
return res
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user