Files
ai-agent/workflow/dao/flow/flow_async_task_dao.go
19904408334 73f296731c refactor(workflow): 隔离逻辑运行键为 node_group_id
将 checkpoint、异步任务与段结果缓存的唯一键从 execution_id 改为 node_group_id,区分换参重跑与续跑语义;恢复/续跑复用执行记录的组标识,换参重跑则换新组并软删旧组残留,消除软删墓碑导致同键重存失效的问题。
2026-09-04 17:36:46 +08:00

105 lines
4.8 KiB
Go
Raw Permalink 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/public"
"ai-agent/workflow/model/entity"
"context"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
)
const (
FlowAsyncStateInflight = 0 // 任务已提交,结果未取(执行中/结果已发布未取/失败)
FlowAsyncStateDone = 1 // 成功,结果已缓存
FlowAsyncStateFailed = 2 // 确定失败
FlowAsyncSegSentinel = -1 // 非段异步调用的段序号哨兵值
)
var FlowAsyncTaskDao = &flowAsyncTaskDao{}
type flowAsyncTaskDao struct{}
// 缓存唯一键是 (node_group_id, node_id, segment_index)node_group_id 是"逻辑运行(attempt)"标识,
// 全新/换参重跑换新组 = 换新键;续跑/恢复复用 exec 记录的组 = 读到同组活行。
// 删除只有软删且只作用于"终态组"(成功尾部 / 换参重跑废弃的旧组),此类组此后永不再写,
// 软删行不会被同键重存(组永不复活)——与 flow_segment_result 同一约定。
// execution_id 仅作溯源保留,不参与唯一键。
// Get 查询唯一键 (node_group_id, node_id, segment_index) 的记录
func (d *flowAsyncTaskDao) Get(ctx context.Context, nodeGroupId string, nodeId string, segIdx int) (res *entity.FlowAsyncTask, err error) {
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowAsyncTask).
Where(entity.FlowAsyncTaskCol.NodeGroupId, nodeGroupId).
Where(entity.FlowAsyncTaskCol.NodeId, nodeId).
Where(entity.FlowAsyncTaskCol.SegmentIndex, segIdx).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
err = r.Struct(&res)
return
}
// Upsert 提交时写入/更新 in-flight 行:唯一键冲突则更新 model_id/task_id/msg_topic/state(保留已完成结果不动)。
// 与 flow_segment_result 相同走 OnConflict().Save();两点相对初稿的调整:
// 1. Result 列是 JSONB,空串会被 PG 拒绝(invalid input syntax for type json),
// 提交时本无结果,统一写 '{}' 占位(与表列默认值一致)。
// 2. 必须用 OnDuplicate 限定冲突更新列——GoFrame Save 默认把 Data 里所有列写进
// ON CONFLICT DO UPDATE SET,若不限定会把已缓存的结果覆盖成 '{}',与"保留已完成结果"矛盾。
//
// 提交/重提统一走本函数重置同组活行(state→in-flight + 新 task_id/msg_topic):不再"删行重建"
// 活行从未被软删,OnConflict 更新即可复位,无墓碑冲突、无物理删除。
func (d *flowAsyncTaskDao) Upsert(ctx context.Context, nodeGroupId string, execId int64, nodeId string, segIdx int, modelId, taskId int64, msgTopic string) error {
rec := &entity.FlowAsyncTask{
NodeGroupId: nodeGroupId,
ExecutionId: execId,
NodeId: nodeId,
SegmentIndex: segIdx,
ModelId: modelId,
TaskId: taskId,
MsgTopic: msgTopic,
State: FlowAsyncStateInflight,
Result: "{}",
}
_, err := gfdb.DB(ctx, public.DbNameBlackDeacon).
Model(ctx, public.TableNameFlowAsyncTask).
Data(rec).
OnConflict(entity.FlowAsyncTaskCol.NodeGroupId, entity.FlowAsyncTaskCol.NodeId, entity.FlowAsyncTaskCol.SegmentIndex).
OnDuplicate(entity.FlowAsyncTaskCol.ModelId, entity.FlowAsyncTaskCol.TaskId, entity.FlowAsyncTaskCol.MsgTopic, entity.FlowAsyncTaskCol.State).
Save()
return err
}
// UpdateByKey 按唯一键更新 state/resultOmitNil 丢弃 nil 字段,map 值非 nil 全写入;
// state=0 也能落库)。Result 列是 JSONB,空串无法写入,统一落 '{}' 表示无结果。
func (d *flowAsyncTaskDao) UpdateByKey(ctx context.Context, nodeGroupId string, nodeId string, segIdx int, state int, result string) error {
resultVal := result
if resultVal == "" {
resultVal = "{}"
}
_, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowAsyncTask).
Where(entity.FlowAsyncTaskCol.NodeGroupId, nodeGroupId).
Where(entity.FlowAsyncTaskCol.NodeId, nodeId).
Where(entity.FlowAsyncTaskCol.SegmentIndex, segIdx).
Data(map[string]any{
entity.FlowAsyncTaskCol.State: state,
entity.FlowAsyncTaskCol.Result: resultVal,
}).
Update()
return err
}
// DeleteByGroup 软删指定逻辑运行(组)的异步任务缓存(gfdb Model.Delete 在 deletedAt 配置下退化为软删)。
// 仅允许对"终态组"调用:① 工作流执行成功后(BuildExecution 尾部);② 同一条 exec 换参重跑(forceNewRun
// 废弃的旧组(execute 重置成功后回收)。终态组此后永不再被读写 → 软删行不复活、不占同键写入冲突。
// 失败/取消/重试耗尽一律不删(保留组行供同参数续跑复用)。绝无物理删除。
func (d *flowAsyncTaskDao) DeleteByGroup(ctx context.Context, nodeGroupId string) error {
_, err := gfdb.DB(ctx, public.DbNameBlackDeacon).
Model(ctx, public.TableNameFlowAsyncTask).
Where(entity.FlowAsyncTaskCol.NodeGroupId, nodeGroupId).
Delete()
return err
}