2025-12-08 20:50:16 +08:00
|
|
|
|
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)
|
2025-12-08 20:50:16 +08:00
|
|
|
|
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))
|
2025-12-08 20:50:16 +08:00
|
|
|
|
if err != nil {
|
2026-01-05 20:07:17 +08:00
|
|
|
|
suggestion = fmt.Sprintf("AI agent response failed: %v", err)
|
2025-12-08 20:50:16 +08:00
|
|
|
|
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"])
|
|
|
|
|
|
}
|
2025-12-08 20:50:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
}
|