Files
ai-agent/workflow/dao/flow/flow_async_task_dao.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

104 lines
4.6 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/public"
"ai-agent/workflow/model/entity"
"context"
"fmt"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
)
const (
FlowAsyncStateInflight = 0 // 任务已提交,结果未取(执行中/结果已发布未取/失败)
FlowAsyncStateDone = 1 // 成功,结果已缓存
FlowAsyncStateFailed = 2 // 确定失败
FlowAsyncSegSentinel = -1 // 非段异步调用的段序号哨兵值
)
var FlowAsyncTaskDao = &flowAsyncTaskDao{}
type flowAsyncTaskDao struct{}
// Get 查询唯一键 (execution_id, node_id, segment_index) 的记录
func (d *flowAsyncTaskDao) Get(ctx context.Context, execId int64, nodeId string, segIdx int) (res *entity.FlowAsyncTask, err error) {
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowAsyncTask).
Where(entity.FlowAsyncTaskCol.ExecutionId, execId).
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,若不限定会把已缓存的结果覆盖成 '{}',与"保留已完成结果"矛盾。
func (d *flowAsyncTaskDao) Upsert(ctx context.Context, execId int64, nodeId string, segIdx int, modelId, taskId int64, msgTopic string) error {
rec := &entity.FlowAsyncTask{
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.ExecutionId, 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, execId int64, 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.ExecutionId, execId).
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
}
// DeleteByKey 物理删除:实体嵌 SQLBaseDO 软删后同键重存无法复活(ON CONFLICT 不含 deleted_at),
// 与 flow_segment_result 相同约束,须 raw Exec 用物理全名
func (d *flowAsyncTaskDao) DeleteByKey(ctx context.Context, execId int64, nodeId string, segIdx int) error {
const physicalTable = "black_deacon_flow_async_task"
_, err := gfdb.DB(ctx, public.DbNameBlackDeacon).
Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE execution_id = ? AND node_id = ? AND segment_index = ?", physicalTable), execId, nodeId, segIdx)
return err
}
// DeleteByExecution 清理指定执行的异步任务缓存。统一清理策略(Task 11 方案A,无周期兜底):
// 仅在两处 exec 级删除点调用——① 工作流执行成功后(BuildExecution 尾部,与 checkpoint/段清理同处);
// ② 同一条 exec 以"全新跑"重开(forceNewRun 起跑前,参数已变,旧异步结果必须作废防误复用)。
// 失败/取消/重试耗尽一律保留产物,供同参数手动续跑(reExecute)复用;不作节点级或周期清扫。
func (d *flowAsyncTaskDao) DeleteByExecution(ctx context.Context, execId int64) error {
const physicalTable = "black_deacon_flow_async_task"
_, err := gfdb.DB(ctx, public.DbNameBlackDeacon).
Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE execution_id = ?", physicalTable), execId)
return err
}