Refactor AIAgent runtime handling and remove unused fields
This commit is contained in:
@@ -24,7 +24,11 @@ func NewService() *Service {
|
|||||||
|
|
||||||
func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) {
|
func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) {
|
||||||
req.UserMessage.Content = utils.BuildRuntimeMessageText(req.UserMessage.MessageType, req.UserMessage.Content)
|
req.UserMessage.Content = utils.BuildRuntimeMessageText(req.UserMessage.MessageType, req.UserMessage.Content)
|
||||||
req.AIAgent = applyWorkflowInstruction(req.AIAgent)
|
aiAgent, err := prepareWorkflowAgent(req.AIAgent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.AIAgent = aiAgent
|
||||||
toolSet, err := s.prepare.prepareToolsForRun(req)
|
toolSet, err := s.prepare.prepareToolsForRun(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -45,7 +49,11 @@ func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Resume(ctx context.Context, req ResumeRequest) (*Summary, error) {
|
func (s *Service) Resume(ctx context.Context, req ResumeRequest) (*Summary, error) {
|
||||||
req.AIAgent = applyWorkflowInstruction(req.AIAgent)
|
aiAgent, err := prepareWorkflowAgent(req.AIAgent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.AIAgent = aiAgent
|
||||||
toolSet, err := s.prepare.prepareToolsForResume(req)
|
toolSet, err := s.prepare.prepareToolsForResume(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (c *toolCatalog) parseAgentAllowedToolCodes(aiAgent models.AIAgent) []strin
|
|||||||
ret = append(ret, graphTools...)
|
ret = append(ret, graphTools...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result, ok := resolveAgentWorkflow(aiAgent); ok {
|
if result, err := resolveAgentWorkflow(aiAgent); err == nil {
|
||||||
ret = append(ret, result.ToolCodes...)
|
ret = append(ret, result.ToolCodes...)
|
||||||
}
|
}
|
||||||
return toolx.NormalizeToolCodes(ret)
|
return toolx.NormalizeToolCodes(ret)
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ func TestToolCatalogIncludesPublishedWorkflowGraphTools(t *testing.T) {
|
|||||||
|
|
||||||
catalog := newToolCatalog()
|
catalog := newToolCatalog()
|
||||||
ret := catalog.parseAgentAllowedToolCodes(models.AIAgent{
|
ret := catalog.parseAgentAllowedToolCodes(models.AIAgent{
|
||||||
RuntimeMode: enums.AIAgentRuntimeModeWorkflow,
|
|
||||||
WorkflowVersionID: version.ID,
|
WorkflowVersionID: version.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ func TestToolCatalogIncludesPublishedWorkflowGraphTools(t *testing.T) {
|
|||||||
assertContainsToolCode(t, ret, toolx.GraphHandoffConversation.Code)
|
assertContainsToolCode(t, ret, toolx.GraphHandoffConversation.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyWorkflowInstructionAppendsPublishedWorkflow(t *testing.T) {
|
func TestPrepareWorkflowAgentAppendsPublishedWorkflow(t *testing.T) {
|
||||||
setupWorkflowRuntimeTestDB(t)
|
setupWorkflowRuntimeTestDB(t)
|
||||||
version := createWorkflowRuntimeTestVersion(t, dsl.Definition{
|
version := createWorkflowRuntimeTestVersion(t, dsl.Definition{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 1,
|
||||||
@@ -105,11 +104,13 @@ func TestApplyWorkflowInstructionAppendsPublishedWorkflow(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
agent := applyWorkflowInstruction(models.AIAgent{
|
agent, err := prepareWorkflowAgent(models.AIAgent{
|
||||||
SystemPrompt: "Base prompt.",
|
SystemPrompt: "Base prompt.",
|
||||||
RuntimeMode: enums.AIAgentRuntimeModeWorkflow,
|
|
||||||
WorkflowVersionID: version.ID,
|
WorkflowVersionID: version.ID,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("prepare workflow agent: %v", err)
|
||||||
|
}
|
||||||
if agent.SystemPrompt == "Base prompt." {
|
if agent.SystemPrompt == "Base prompt." {
|
||||||
t.Fatalf("expected workflow appendix to be appended")
|
t.Fatalf("expected workflow appendix to be appended")
|
||||||
}
|
}
|
||||||
@@ -118,6 +119,39 @@ func TestApplyWorkflowInstructionAppendsPublishedWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrepareWorkflowAgentRejectsMissingPublishedWorkflow(t *testing.T) {
|
||||||
|
_, err := prepareWorkflowAgent(models.AIAgent{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected missing workflow version error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "workflow version is required") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareWorkflowAgentRejectsDeletedPublishedWorkflow(t *testing.T) {
|
||||||
|
setupWorkflowRuntimeTestDB(t)
|
||||||
|
version := createWorkflowRuntimeTestVersion(t, dsl.Definition{
|
||||||
|
SchemaVersion: 1,
|
||||||
|
EntryNodeID: "start",
|
||||||
|
Nodes: []dsl.Node{
|
||||||
|
{ID: "start", Type: workflowregistry.NodeTypeStart, Name: "Start"},
|
||||||
|
{ID: "end", Type: workflowregistry.NodeTypeEnd, Name: "End"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err := sqls.DB().Model(&models.AIWorkflowVersion{}).Where("id = ?", version.ID).Update("status", enums.StatusDeleted).Error; err != nil {
|
||||||
|
t.Fatalf("delete workflow version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := prepareWorkflowAgent(models.AIAgent{WorkflowVersionID: version.ID})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected invalid workflow version error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "workflow version does not exist") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupWorkflowRuntimeTestDB(t *testing.T) {
|
func setupWorkflowRuntimeTestDB(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||||
|
|||||||
@@ -8,37 +8,41 @@ import (
|
|||||||
"agent-desk/internal/ai/workflow/dsl"
|
"agent-desk/internal/ai/workflow/dsl"
|
||||||
"agent-desk/internal/models"
|
"agent-desk/internal/models"
|
||||||
"agent-desk/internal/pkg/enums"
|
"agent-desk/internal/pkg/enums"
|
||||||
|
"agent-desk/internal/pkg/errorsx"
|
||||||
"agent-desk/internal/repositories"
|
"agent-desk/internal/repositories"
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
"github.com/mlogclub/simple/sqls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resolveAgentWorkflow(aiAgent models.AIAgent) (compiler.Result, bool) {
|
func resolveAgentWorkflow(aiAgent models.AIAgent) (compiler.Result, error) {
|
||||||
if aiAgent.RuntimeMode != enums.AIAgentRuntimeModeWorkflow || aiAgent.WorkflowVersionID <= 0 {
|
if aiAgent.WorkflowVersionID <= 0 {
|
||||||
return compiler.Result{}, false
|
return compiler.Result{}, errorsx.InvalidParam("workflow version is required")
|
||||||
}
|
}
|
||||||
version := repositories.AIWorkflowVersionRepository.Get(sqls.DB(), aiAgent.WorkflowVersionID)
|
version := repositories.AIWorkflowVersionRepository.Get(sqls.DB(), aiAgent.WorkflowVersionID)
|
||||||
if version == nil || version.Status != enums.StatusOk {
|
if version == nil || version.Status != enums.StatusOk {
|
||||||
return compiler.Result{}, false
|
return compiler.Result{}, errorsx.InvalidParam("workflow version does not exist")
|
||||||
}
|
}
|
||||||
var def dsl.Definition
|
var def dsl.Definition
|
||||||
if err := json.Unmarshal([]byte(version.Definition), &def); err != nil {
|
if err := json.Unmarshal([]byte(version.Definition), &def); err != nil {
|
||||||
return compiler.Result{}, false
|
return compiler.Result{}, errorsx.InvalidParam("workflow definition is invalid")
|
||||||
}
|
}
|
||||||
return compiler.Compile(def), true
|
return compiler.Compile(def), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyWorkflowInstruction(aiAgent models.AIAgent) models.AIAgent {
|
func prepareWorkflowAgent(aiAgent models.AIAgent) (models.AIAgent, error) {
|
||||||
result, ok := resolveAgentWorkflow(aiAgent)
|
result, err := resolveAgentWorkflow(aiAgent)
|
||||||
if !ok || strings.TrimSpace(result.Appendix) == "" {
|
if err != nil {
|
||||||
return aiAgent
|
return aiAgent, err
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(result.Appendix) == "" {
|
||||||
|
return aiAgent, nil
|
||||||
}
|
}
|
||||||
prompt := strings.TrimSpace(aiAgent.SystemPrompt)
|
prompt := strings.TrimSpace(aiAgent.SystemPrompt)
|
||||||
appendix := strings.TrimSpace(result.Appendix)
|
appendix := strings.TrimSpace(result.Appendix)
|
||||||
if prompt == "" {
|
if prompt == "" {
|
||||||
aiAgent.SystemPrompt = appendix
|
aiAgent.SystemPrompt = appendix
|
||||||
return aiAgent
|
return aiAgent, nil
|
||||||
}
|
}
|
||||||
aiAgent.SystemPrompt = prompt + "\n\n" + appendix
|
aiAgent.SystemPrompt = prompt + "\n\n" + appendix
|
||||||
return aiAgent
|
return aiAgent, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,8 +189,6 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
|
|||||||
Teams: make([]response.AIAgentTeamResponse, 0),
|
Teams: make([]response.AIAgentTeamResponse, 0),
|
||||||
DirectTools: make([]response.AIAgentMCPToolResponse, 0),
|
DirectTools: make([]response.AIAgentMCPToolResponse, 0),
|
||||||
GraphTools: make([]string, 0),
|
GraphTools: make([]string, 0),
|
||||||
RuntimeMode: item.RuntimeMode,
|
|
||||||
RuntimeModeName: enums.GetAIAgentRuntimeModeLabel(item.RuntimeMode),
|
|
||||||
WorkflowVersionID: item.WorkflowVersionID,
|
WorkflowVersionID: item.WorkflowVersionID,
|
||||||
SortNo: item.SortNo,
|
SortNo: item.SortNo,
|
||||||
CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"),
|
CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
|||||||
@@ -527,7 +527,6 @@ type AIAgent struct {
|
|||||||
SkillIDs string `gorm:"type:varchar(500);not null;default:''"` // SkillIDs 为绑定的技能ID列表,按顺序表示允许路由的范围。
|
SkillIDs string `gorm:"type:varchar(500);not null;default:''"` // SkillIDs 为绑定的技能ID列表,按顺序表示允许路由的范围。
|
||||||
AllowedMCPTools string `gorm:"type:text"` // AllowedMCPTools 为允许 direct tool 路由的 MCP 工具白名单配置JSON。
|
AllowedMCPTools string `gorm:"type:text"` // AllowedMCPTools 为允许 direct tool 路由的 MCP 工具白名单配置JSON。
|
||||||
AllowedGraphTools string `gorm:"type:text"` // AllowedGraphTools 为允许 Graph Tool 的白名单配置JSON。
|
AllowedGraphTools string `gorm:"type:text"` // AllowedGraphTools 为允许 Graph Tool 的白名单配置JSON。
|
||||||
RuntimeMode enums.AIAgentRuntimeMode `gorm:"type:int;not null;default:1;index"` // RuntimeMode 为 Agent 执行模式,如内置 Graph 或发布的会话流程。
|
|
||||||
WorkflowVersionID int64 `gorm:"type:bigint;not null;default:0;index"` // WorkflowVersionID 为绑定的已发布会话流程版本ID。
|
WorkflowVersionID int64 `gorm:"type:bigint;not null;default:0;index"` // WorkflowVersionID 为绑定的已发布会话流程版本ID。
|
||||||
SortNo int `gorm:"type:int;not null;default:0;index"` // SortNo 为后台展示排序号。
|
SortNo int `gorm:"type:int;not null;default:0;index"` // SortNo 为后台展示排序号。
|
||||||
AuditFields
|
AuditFields
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ type AIAgentResponse struct {
|
|||||||
Skills []AIAgentSkillResponse `json:"skills"`
|
Skills []AIAgentSkillResponse `json:"skills"`
|
||||||
DirectTools []AIAgentMCPToolResponse `json:"directTools"`
|
DirectTools []AIAgentMCPToolResponse `json:"directTools"`
|
||||||
GraphTools []string `json:"graphTools"`
|
GraphTools []string `json:"graphTools"`
|
||||||
RuntimeMode enums.AIAgentRuntimeMode `json:"runtimeMode"`
|
|
||||||
RuntimeModeName string `json:"runtimeModeName"`
|
|
||||||
WorkflowVersionID int64 `json:"workflowVersionId"`
|
WorkflowVersionID int64 `json:"workflowVersionId"`
|
||||||
SortNo int `json:"sortNo"`
|
SortNo int `json:"sortNo"`
|
||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
|
|||||||
@@ -31,24 +31,3 @@ var aiModelTypeLabelMap = map[AIModelType]string{
|
|||||||
func GetAIModelTypeLabel(modelType AIModelType) string {
|
func GetAIModelTypeLabel(modelType AIModelType) string {
|
||||||
return aiModelTypeLabelMap[modelType]
|
return aiModelTypeLabelMap[modelType]
|
||||||
}
|
}
|
||||||
|
|
||||||
type AIAgentRuntimeMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
AIAgentRuntimeModeBuiltinGraph AIAgentRuntimeMode = 1
|
|
||||||
AIAgentRuntimeModeWorkflow AIAgentRuntimeMode = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var AIAgentRuntimeModeValues = []AIAgentRuntimeMode{
|
|
||||||
AIAgentRuntimeModeBuiltinGraph,
|
|
||||||
AIAgentRuntimeModeWorkflow,
|
|
||||||
}
|
|
||||||
|
|
||||||
var aiAgentRuntimeModeLabelMap = map[AIAgentRuntimeMode]string{
|
|
||||||
AIAgentRuntimeModeBuiltinGraph: "内置 Graph",
|
|
||||||
AIAgentRuntimeModeWorkflow: "会话流程",
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAIAgentRuntimeModeLabel(mode AIAgentRuntimeMode) string {
|
|
||||||
return aiAgentRuntimeModeLabelMap[mode]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -229,7 +229,6 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
|||||||
SkillIDs: utils.JoinInt64s(skillIDs),
|
SkillIDs: utils.JoinInt64s(skillIDs),
|
||||||
AllowedMCPTools: directToolsJSON,
|
AllowedMCPTools: directToolsJSON,
|
||||||
AllowedGraphTools: graphToolsJSON,
|
AllowedGraphTools: graphToolsJSON,
|
||||||
RuntimeMode: enums.AIAgentRuntimeModeBuiltinGraph,
|
|
||||||
WorkflowVersionID: 0,
|
WorkflowVersionID: 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -357,26 +356,6 @@ func (s *aIAgentService) normalizeGraphTools(input []string) ([]string, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *aIAgentService) normalizeRuntimeMode(input enums.AIAgentRuntimeMode, workflowVersionID int64) (enums.AIAgentRuntimeMode, int64, error) {
|
|
||||||
if input == 0 {
|
|
||||||
input = enums.AIAgentRuntimeModeBuiltinGraph
|
|
||||||
}
|
|
||||||
if !slices.Contains(enums.AIAgentRuntimeModeValues, input) {
|
|
||||||
return 0, 0, errorsx.InvalidParam("invalid ai agent runtime mode")
|
|
||||||
}
|
|
||||||
if input != enums.AIAgentRuntimeModeWorkflow {
|
|
||||||
return input, 0, nil
|
|
||||||
}
|
|
||||||
if workflowVersionID <= 0 {
|
|
||||||
return 0, 0, errorsx.InvalidParam("workflow version is required")
|
|
||||||
}
|
|
||||||
version := AIWorkflowService.GetVersion(workflowVersionID)
|
|
||||||
if version == nil || version.Status != enums.StatusOk {
|
|
||||||
return 0, 0, errorsx.InvalidParam("workflow version does not exist")
|
|
||||||
}
|
|
||||||
return input, workflowVersionID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *aIAgentService) UpdateSort(ids []int64) error {
|
func (s *aIAgentService) UpdateSort(ids []int64) error {
|
||||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||||
for i, id := range ids {
|
for i, id := range ids {
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ func TestAIWorkflowServicePublishAgentWorkflowBindsAgentVersion(t *testing.T) {
|
|||||||
if storedAgent == nil {
|
if storedAgent == nil {
|
||||||
t.Fatalf("expected stored agent")
|
t.Fatalf("expected stored agent")
|
||||||
}
|
}
|
||||||
if storedAgent.RuntimeMode != enums.AIAgentRuntimeModeWorkflow {
|
|
||||||
t.Fatalf("expected agent runtime workflow, got %d", storedAgent.RuntimeMode)
|
|
||||||
}
|
|
||||||
if storedAgent.WorkflowVersionID != version.ID {
|
if storedAgent.WorkflowVersionID != version.ID {
|
||||||
t.Fatalf("expected agent workflow version %d, got %d", version.ID, storedAgent.WorkflowVersionID)
|
t.Fatalf("expected agent workflow version %d, got %d", version.ID, storedAgent.WorkflowVersionID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,6 @@ func (s *aiWorkflowService) PublishAgentWorkflow(req request.PublishAIWorkflowRe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return repositories.AIAgentRepository.Updates(ctx.Tx, req.AgentID, map[string]any{
|
return repositories.AIAgentRepository.Updates(ctx.Tx, req.AgentID, map[string]any{
|
||||||
"runtime_mode": enums.AIAgentRuntimeModeWorkflow,
|
|
||||||
"workflow_version_id": version.ID,
|
"workflow_version_id": version.ID,
|
||||||
"update_user_id": operator.UserID,
|
"update_user_id": operator.UserID,
|
||||||
"update_user_name": operator.Username,
|
"update_user_name": operator.Username,
|
||||||
|
|||||||
@@ -836,10 +836,6 @@ export function AIAgentConfigWorkbench({
|
|||||||
<span className="text-muted-foreground">Agent 状态</span>
|
<span className="text-muted-foreground">Agent 状态</span>
|
||||||
<Badge variant="secondary">{agent?.statusName || "-"}</Badge>
|
<Badge variant="secondary">{agent?.statusName || "-"}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-muted-foreground">运行模式</span>
|
|
||||||
<Badge variant="outline">{agent?.runtimeModeName || "-"}</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">生效流程版本</span>
|
<span className="text-muted-foreground">生效流程版本</span>
|
||||||
<Badge variant={agent?.workflowVersionId ? "default" : "outline"}>
|
<Badge variant={agent?.workflowVersionId ? "default" : "outline"}>
|
||||||
|
|||||||
@@ -242,8 +242,6 @@ export type AIAgent = {
|
|||||||
arguments?: Record<string, string>
|
arguments?: Record<string, string>
|
||||||
}[]
|
}[]
|
||||||
graphTools: string[]
|
graphTools: string[]
|
||||||
runtimeMode: number
|
|
||||||
runtimeModeName: string
|
|
||||||
workflowVersionId: number
|
workflowVersionId: number
|
||||||
sortNo: number
|
sortNo: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
|
|||||||
Reference in New Issue
Block a user