Files
mayfly-go/server/internal/flow/application/node_aitask.go

161 lines
4.2 KiB
Go
Raw Normal View History

package application
import (
"context"
"fmt"
"mayfly-go/internal/ai/agent"
"mayfly-go/internal/ai/prompt"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/imsg"
"mayfly-go/internal/flow/infra/persistence"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/jsonx"
"time"
"github.com/spf13/cast"
)
/******************* AI任务节点 *******************/
const (
FlowNodeTypeAiTask entity.FlowNodeType = "aitask"
)
// AiTaskNode AI任务节点
type AiTaskNode struct {
entity.FlowNode
AuditRule string `json:"auditRule" form:"auditRule"` // 审批规则
}
// ToUserTaskNode 将标准节点转换成用户任务节点(方便取值)
func ToAiTaskNode(node *entity.FlowNode) *AiTaskNode {
return &AiTaskNode{
FlowNode: *node,
AuditRule: node.GetExtraString("auditRule"),
}
}
type FlowNodeAiTaskApprovalMode string
// AiTaskNodeBehavior Ai任务节点行为处理器
type AiTaskNodeBehavior struct {
DefaultNodeBehavior
}
var _ NodeBehavior = (*AiTaskNodeBehavior)(nil)
func (h *AiTaskNodeBehavior) GetType() entity.FlowNodeType {
return FlowNodeTypeAiTask
}
func (h *AiTaskNodeBehavior) Validate(ctx context.Context, flowDef *entity.FlowDef, node *entity.FlowNode) error {
aitaskNode := ToAiTaskNode(node)
if aitaskNode.AuditRule == "" {
return errorx.NewBizI(ctx, imsg.ErrAiTaskNodeAuditRuleNotEmpty, "name", node.Name)
}
return nil
}
func (h *AiTaskNodeBehavior) IsAsync() bool {
return true
}
func (u *AiTaskNodeBehavior) Execute(ctx *ExecutionCtx) error {
ctx.parent = context.Background() // 该节点为异步操作,需重新赋值父上下文
flowNode := ctx.GetFlowNode()
aitaskNode := ToAiTaskNode(flowNode)
2026-01-05 20:07:17 +08:00
aiagent, err := agent.NewAiAgent(ctx, agent.ToolTypeDb)
if err != nil {
return err
}
auditRule := aitaskNode.AuditRule
sysPrompt := prompt.GetPrompt(prompt.FLOW_BIZ_AUDIT, auditRule)
procinst := ctx.Procinst
now := time.Now()
procinstTask := &entity.ProcinstTask{
ProcinstId: procinst.Id,
ExecutionId: ctx.Execution.Id,
NodeKey: flowNode.Key,
NodeName: flowNode.Name,
NodeType: flowNode.Type,
}
procinstTask.CreateTime = &now
allowExecute := false
suggestion := ""
2026-01-05 20:07:17 +08:00
cancelCtx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancelFunc()
res, err := aiagent.GetChatMsg(cancelCtx, sysPrompt, jsonx.ToStr(procinst.BizForm))
if err != nil {
2026-01-05 20:07:17 +08:00
suggestion = fmt.Sprintf("AI agent response failed: %v", err)
logx.Error(suggestion)
} else {
2026-01-05 20:07:17 +08:00
resJson, err := jsonx.ToMap(res)
if err != nil {
suggestion = fmt.Sprintf("AI agent response parsing to JSON failed: %v, response: %s", err, res)
logx.Error(suggestion)
} else {
allowExecute = cast.ToBool(resJson["allowExecute"])
suggestion = cast.ToString(resJson["suggestion"])
}
}
procinstTask.Remark = suggestion
procinstTask.SetEnd()
procinstApp := GetProcinstApp()
executionApp := GetExecutionApp()
procinstTaskApp := GetProcinstTaskApp()
return procinstTaskApp.Tx(ctx, func(c context.Context) error {
if !allowExecute {
// 流程实例退回
procinst.Status = entity.ProcinstStatusBack
ctx.OpExtra.Set("approvalResult", entity.ProcinstTaskStatusBack)
procinstTask.Status = entity.ProcinstTaskStatusBack
if err := procinstApp.Save(c, procinst); err != nil {
return err
}
} else {
ctx.OpExtra.Set("approvalResult", entity.ProcinstTaskStatusCompleted)
procinstTask.Status = entity.ProcinstTaskStatusCompleted
}
// 保存任务与任务候选者信息兼容usertask展示
if err := procinstTaskApp.Save(c, procinstTask); err != nil {
return err
}
handler := "AI"
procinstTaskCandidate := &entity.ProcinstTaskCandidate{
TaskId: procinstTask.Id,
ProcinstId: procinst.Id,
Candidate: handler,
Handler: &handler,
Status: procinstTask.Status,
}
procinstTaskCandidate.CreateTime = &now
procinstTaskCandidate.SetEnd()
if err := persistence.GetProcinstTaskCandidateRepo().Save(c, procinstTaskCandidate); err != nil {
return err
}
if !allowExecute {
// 跳转至开始节点,重新修改提交
ctx.Execution.State = entity.ExectionStateSuspended // 执行流挂起
return executionApp.MoveTo(ctx, ctx.GetFlowDef().GetNodeByType(FlowNodeTypeStart)[0])
}
return u.Leave(ctx)
})
}