refactor: remove graph tools references and related logic from AIAgent and workflows
This commit is contained in:
@@ -2,9 +2,7 @@ package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"agent-desk/internal/ai/workflow/compiler"
|
||||
"agent-desk/internal/ai/workflow/dsl"
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
|
||||
type resolvedWorkflow struct {
|
||||
Definition dsl.Definition
|
||||
Compiled compiler.Result
|
||||
WorkflowID int64
|
||||
VersionID int64
|
||||
}
|
||||
@@ -35,7 +32,6 @@ func resolveAgentWorkflow(aiAgent models.AIAgent) (resolvedWorkflow, error) {
|
||||
}
|
||||
return resolvedWorkflow{
|
||||
Definition: def,
|
||||
Compiled: compiler.Compile(def),
|
||||
WorkflowID: version.WorkflowID,
|
||||
VersionID: version.ID,
|
||||
}, nil
|
||||
@@ -46,15 +42,5 @@ func prepareWorkflowAgent(aiAgent models.AIAgent) (models.AIAgent, resolvedWorkf
|
||||
if err != nil {
|
||||
return aiAgent, resolvedWorkflow{}, err
|
||||
}
|
||||
if strings.TrimSpace(workflow.Compiled.Appendix) == "" {
|
||||
return aiAgent, workflow, nil
|
||||
}
|
||||
prompt := strings.TrimSpace(aiAgent.SystemPrompt)
|
||||
appendix := strings.TrimSpace(workflow.Compiled.Appendix)
|
||||
if prompt == "" {
|
||||
aiAgent.SystemPrompt = appendix
|
||||
return aiAgent, workflow, nil
|
||||
}
|
||||
aiAgent.SystemPrompt = prompt + "\n\n" + appendix
|
||||
return aiAgent, workflow, nil
|
||||
}
|
||||
|
||||
@@ -46,6 +46,40 @@ func TestToWorkflowSummaryPreservesInterruptCheckpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareWorkflowAgentDoesNotInjectWorkflowAppendix(t *testing.T) {
|
||||
db := setupWorkflowResumeTestDB(t)
|
||||
definitionJSON := mustMarshalDefinition(t, dsl.Definition{
|
||||
SchemaVersion: 1,
|
||||
EntryNodeID: "start",
|
||||
Nodes: []dsl.Node{
|
||||
{ID: "start", Type: workflowregistry.NodeTypeStart, Name: "Start"},
|
||||
{ID: "handoff", Type: workflowregistry.NodeTypeHandoffToHuman, Name: "Handoff"},
|
||||
},
|
||||
Edges: []dsl.Edge{{ID: "edge_start_handoff", Source: "start", Target: "handoff"}},
|
||||
})
|
||||
version := models.AIWorkflowVersion{
|
||||
WorkflowID: 1,
|
||||
Version: 1,
|
||||
Status: enums.StatusOk,
|
||||
Definition: definitionJSON,
|
||||
}
|
||||
if err := db.Create(&version).Error; err != nil {
|
||||
t.Fatalf("create workflow version: %v", err)
|
||||
}
|
||||
|
||||
agent, _, err := prepareWorkflowAgent(models.AIAgent{
|
||||
ID: 1,
|
||||
SystemPrompt: "保持简洁回答。",
|
||||
WorkflowVersionID: version.ID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("prepareWorkflowAgent() error = %v", err)
|
||||
}
|
||||
if agent.SystemPrompt != "保持简洁回答。" {
|
||||
t.Fatalf("expected system prompt to stay unchanged, got %q", agent.SystemPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceResumeUsesWorkflowCheckpointData(t *testing.T) {
|
||||
db := setupWorkflowResumeTestDB(t)
|
||||
def := runtimeHumanConfirmDefinition()
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"agent-desk/internal/ai/workflow/dsl"
|
||||
workflowregistry "agent-desk/internal/ai/workflow/registry"
|
||||
"agent-desk/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
ToolCodes []string
|
||||
Appendix string
|
||||
}
|
||||
|
||||
func Compile(def dsl.Definition) Result {
|
||||
toolCodes := make([]string, 0)
|
||||
lines := make([]string, 0, len(def.Nodes)+2)
|
||||
if strings.TrimSpace(def.EntryNodeID) != "" {
|
||||
lines = append(lines, fmt.Sprintf("Workflow entry node: %s.", strings.TrimSpace(def.EntryNodeID)))
|
||||
}
|
||||
for _, node := range def.Nodes {
|
||||
nodeType := strings.TrimSpace(node.Type)
|
||||
if code := graphToolCodeForNodeType(nodeType); code != "" {
|
||||
toolCodes = append(toolCodes, code)
|
||||
}
|
||||
nodeName := strings.TrimSpace(node.Name)
|
||||
if nodeName == "" {
|
||||
nodeName = strings.TrimSpace(node.ID)
|
||||
}
|
||||
if nodeName == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("- %s: %s", nodeName, nodeType))
|
||||
}
|
||||
appendix := ""
|
||||
if len(lines) > 0 {
|
||||
appendix = "Published customer-service workflow:\n" + strings.Join(lines, "\n")
|
||||
}
|
||||
return Result{
|
||||
ToolCodes: toolx.NormalizeToolCodes(toolCodes),
|
||||
Appendix: appendix,
|
||||
}
|
||||
}
|
||||
|
||||
func graphToolCodeForNodeType(nodeType string) string {
|
||||
switch strings.TrimSpace(nodeType) {
|
||||
case workflowregistry.NodeTypeAnalyzeConversation:
|
||||
return toolx.GraphAnalyzeConversation.Code
|
||||
case workflowregistry.NodeTypePrepareTicketDraft:
|
||||
return toolx.GraphPrepareTicketDraft.Code
|
||||
case workflowregistry.NodeTypeCreateTicket:
|
||||
return toolx.GraphCreateTicketConfirm.Code
|
||||
case workflowregistry.NodeTypeHandoffToHuman:
|
||||
return toolx.GraphHandoffConversation.Code
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"agent-desk/internal/ai/workflow/dsl"
|
||||
workflowregistry "agent-desk/internal/ai/workflow/registry"
|
||||
"agent-desk/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
func TestCompileMapsWorkflowNodesToGraphTools(t *testing.T) {
|
||||
result := Compile(dsl.Definition{
|
||||
EntryNodeID: "start",
|
||||
Nodes: []dsl.Node{
|
||||
{ID: "start", Type: workflowregistry.NodeTypeStart, Name: "Start"},
|
||||
{ID: "analyze", Type: workflowregistry.NodeTypeAnalyzeConversation, Name: "Analyze"},
|
||||
{ID: "draft", Type: workflowregistry.NodeTypePrepareTicketDraft, Name: "Draft"},
|
||||
{ID: "create", Type: workflowregistry.NodeTypeCreateTicket, Name: "Create"},
|
||||
{ID: "handoff", Type: workflowregistry.NodeTypeHandoffToHuman, Name: "Handoff"},
|
||||
},
|
||||
})
|
||||
want := []string{
|
||||
toolx.GraphAnalyzeConversation.Code,
|
||||
toolx.GraphPrepareTicketDraft.Code,
|
||||
toolx.GraphCreateTicketConfirm.Code,
|
||||
toolx.GraphHandoffConversation.Code,
|
||||
}
|
||||
if len(result.ToolCodes) != len(want) {
|
||||
t.Fatalf("expected %d tool codes, got %d: %#v", len(want), len(result.ToolCodes), result.ToolCodes)
|
||||
}
|
||||
for i, item := range want {
|
||||
if result.ToolCodes[i] != item {
|
||||
t.Fatalf("tool code[%d] = %s, want %s", i, result.ToolCodes[i], item)
|
||||
}
|
||||
}
|
||||
if result.Appendix == "" {
|
||||
t.Fatalf("expected workflow appendix")
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,6 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
|
||||
Skills: make([]response.AIAgentSkillResponse, 0),
|
||||
Teams: make([]response.AIAgentTeamResponse, 0),
|
||||
DirectTools: make([]response.AIAgentMCPToolResponse, 0),
|
||||
GraphTools: make([]string, 0),
|
||||
WorkflowVersionID: item.WorkflowVersionID,
|
||||
WorkflowPublished: item.WorkflowVersionID > 0,
|
||||
WorkflowState: aiAgentWorkflowState(item.WorkflowVersionID),
|
||||
@@ -236,7 +235,6 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
|
||||
continue
|
||||
}
|
||||
if toolx.IsAgentDirectGraphToolCode(toolCode) {
|
||||
ret.GraphTools = appendGraphToolCodeIfMissing(ret.GraphTools, toolCode)
|
||||
continue
|
||||
}
|
||||
serverCode := strings.TrimSpace(tool.ServerCode)
|
||||
@@ -271,18 +269,6 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
|
||||
}
|
||||
}
|
||||
}
|
||||
if raw := strings.TrimSpace(item.AllowedGraphTools); raw != "" {
|
||||
var graphTools []string
|
||||
if err := json.Unmarshal([]byte(raw), &graphTools); err == nil {
|
||||
for _, toolCode := range graphTools {
|
||||
toolCode = toolx.NormalizeToolCodeAlias(strings.TrimSpace(toolCode))
|
||||
if !toolx.IsAgentDirectGraphToolCode(toolCode) {
|
||||
continue
|
||||
}
|
||||
ret.GraphTools = appendGraphToolCodeIfMissing(ret.GraphTools, toolCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -299,16 +285,3 @@ func aiAgentWorkflowStateText(workflowVersionID int64) string {
|
||||
}
|
||||
return "未发布"
|
||||
}
|
||||
|
||||
func appendGraphToolCodeIfMissing(items []string, toolCode string) []string {
|
||||
toolCode = strings.TrimSpace(toolCode)
|
||||
if toolCode == "" {
|
||||
return items
|
||||
}
|
||||
for _, item := range items {
|
||||
if strings.TrimSpace(item) == toolCode {
|
||||
return items
|
||||
}
|
||||
}
|
||||
return append(items, toolCode)
|
||||
}
|
||||
|
||||
@@ -524,7 +524,6 @@ type AIAgent struct {
|
||||
KnowledgeIDs string `gorm:"type:varchar(500);not null;default:''"` // KnowledgeIDs 为绑定的知识库ID列表,按顺序表示优先级。
|
||||
SkillIDs string `gorm:"type:varchar(500);not null;default:''"` // SkillIDs 为绑定的技能ID列表,按顺序表示允许路由的范围。
|
||||
AllowedMCPTools string `gorm:"type:text"` // AllowedMCPTools 为允许 direct tool 路由的 MCP 工具白名单配置JSON。
|
||||
AllowedGraphTools string `gorm:"type:text"` // AllowedGraphTools 为允许 Graph Tool 的白名单配置JSON。
|
||||
WorkflowVersionID int64 `gorm:"type:bigint;not null;default:0;index"` // WorkflowVersionID 为绑定的已发布会话流程版本ID。
|
||||
SortNo int `gorm:"type:int;not null;default:0;index"` // SortNo 为后台展示排序号。
|
||||
AuditFields
|
||||
|
||||
@@ -57,7 +57,6 @@ type CreateAIAgentRequest struct {
|
||||
KnowledgeIDs []int64 `json:"knowledgeIds"`
|
||||
SkillIDs []int64 `json:"skillIds"`
|
||||
DirectTools []AIAgentMCPToolRequest `json:"directTools"`
|
||||
GraphTools []string `json:"graphTools"`
|
||||
}
|
||||
|
||||
type UpdateAIAgentRequest struct {
|
||||
|
||||
@@ -90,7 +90,6 @@ type AIAgentResponse struct {
|
||||
SkillIDs []int64 `json:"skillIds"`
|
||||
Skills []AIAgentSkillResponse `json:"skills"`
|
||||
DirectTools []AIAgentMCPToolResponse `json:"directTools"`
|
||||
GraphTools []string `json:"graphTools"`
|
||||
WorkflowVersionID int64 `json:"workflowVersionId"`
|
||||
WorkflowPublished bool `json:"workflowPublished"`
|
||||
WorkflowState string `json:"workflowState"`
|
||||
|
||||
@@ -113,7 +113,6 @@ func (s *aIAgentService) UpdateAIAgent(req request.UpdateAIAgentRequest, operato
|
||||
"knowledge_ids": item.KnowledgeIDs,
|
||||
"skill_ids": item.SkillIDs,
|
||||
"allowed_mcp_tools": item.AllowedMCPTools,
|
||||
"allowed_graph_tools": item.AllowedGraphTools,
|
||||
"update_user_id": operator.UserID,
|
||||
"update_user_name": operator.Username,
|
||||
"updated_at": time.Now(),
|
||||
@@ -193,10 +192,6 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
graphTools, err := s.normalizeGraphTools(req.GraphTools)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directToolsJSON := ""
|
||||
if len(directTools) > 0 {
|
||||
buf, marshalErr := json.Marshal(directTools)
|
||||
@@ -205,14 +200,6 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
}
|
||||
directToolsJSON = string(buf)
|
||||
}
|
||||
graphToolsJSON := ""
|
||||
if len(graphTools) > 0 {
|
||||
buf, marshalErr := json.Marshal(graphTools)
|
||||
if marshalErr != nil {
|
||||
return nil, errorsx.InvalidParamI18n("error.e0028")
|
||||
}
|
||||
graphToolsJSON = string(buf)
|
||||
}
|
||||
return &models.AIAgent{
|
||||
Name: name,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
@@ -228,7 +215,6 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe
|
||||
KnowledgeIDs: utils.JoinInt64s(knowledgeIDs),
|
||||
SkillIDs: utils.JoinInt64s(skillIDs),
|
||||
AllowedMCPTools: directToolsJSON,
|
||||
AllowedGraphTools: graphToolsJSON,
|
||||
WorkflowVersionID: 0,
|
||||
}, nil
|
||||
}
|
||||
@@ -333,29 +319,6 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *aIAgentService) normalizeGraphTools(input []string) ([]string, error) {
|
||||
if len(input) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make([]string, 0, len(input))
|
||||
seen := make(map[string]struct{})
|
||||
for _, item := range input {
|
||||
toolCode := toolx.NormalizeToolCodeAlias(strings.TrimSpace(item))
|
||||
if toolCode == "" {
|
||||
continue
|
||||
}
|
||||
if !toolx.IsAgentDirectGraphToolCode(toolCode) {
|
||||
return nil, errorsx.InvalidParamI18n("error.e0027")
|
||||
}
|
||||
if _, exists := seen[toolCode]; exists {
|
||||
continue
|
||||
}
|
||||
seen[toolCode] = struct{}{}
|
||||
ret = append(ret, toolCode)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *aIAgentService) UpdateSort(ids []int64) error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
for i, id := range ids {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { readFile } from "node:fs/promises"
|
||||
|
||||
const configWorkbenchSource = await readFile(new URL("./config-workbench.tsx", import.meta.url), "utf8")
|
||||
const zhMessagesSource = await readFile(new URL("../../../../messages/zh-CN.json", import.meta.url), "utf8")
|
||||
const adminApiSource = await readFile(new URL("../../../../lib/api/admin.ts", import.meta.url), "utf8")
|
||||
const zhMessages = JSON.parse(zhMessagesSource)
|
||||
|
||||
test("AI Agent workflow-era policy copy separates handoff execution from knowledge fallback", () => {
|
||||
const combinedSource = `${configWorkbenchSource}\n${zhMessagesSource}`
|
||||
@@ -19,3 +21,13 @@ test("AI Agent workflow-era policy copy separates handoff execution from knowled
|
||||
assert.doesNotMatch(configWorkbenchSource, /兜底策略/)
|
||||
assert.doesNotMatch(configWorkbenchSource, /兜底文案/)
|
||||
})
|
||||
|
||||
test("AI Agent config no longer exposes legacy graph tool routing knobs", () => {
|
||||
const aiAgentMessages = JSON.stringify(zhMessages.aiAgent ?? {})
|
||||
|
||||
assert.doesNotMatch(configWorkbenchSource, /graphTools/)
|
||||
assert.doesNotMatch(adminApiSource, /graphTools/)
|
||||
assert.doesNotMatch(aiAgentMessages, /graphTools/)
|
||||
assert.doesNotMatch(aiAgentMessages, /Graph Tool/)
|
||||
assert.doesNotMatch(aiAgentMessages, /内置流程/)
|
||||
})
|
||||
|
||||
@@ -396,7 +396,6 @@ export function AIAgentConfigWorkbench({
|
||||
knowledgeIds: uniqueNumbers(selectedKnowledgeIds),
|
||||
skillIds: uniqueNumbers(selectedSkillIds),
|
||||
directTools,
|
||||
graphTools: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,6 @@ export type AIAgent = {
|
||||
description: string
|
||||
arguments?: Record<string, string>
|
||||
}[]
|
||||
graphTools: string[]
|
||||
workflowVersionId: number
|
||||
workflowPublished: boolean
|
||||
workflowState: string
|
||||
@@ -273,7 +272,6 @@ export type CreateAIAgentPayload = {
|
||||
description: string
|
||||
arguments?: Record<string, string>
|
||||
}[]
|
||||
graphTools: string[]
|
||||
}
|
||||
|
||||
export type UpdateAIAgentPayload = CreateAIAgentPayload & {
|
||||
|
||||
@@ -1090,7 +1090,6 @@
|
||||
"knowledgeTab": "Knowledge",
|
||||
"skillsTab": "Skills",
|
||||
"directToolsTab": "MCP tools",
|
||||
"graphToolsTab": "Graph Tools",
|
||||
"knowledgeHint": "Select at least one knowledge base. You can adjust priority order.",
|
||||
"selectKnowledge": "Select and add knowledge base",
|
||||
"searchKnowledge": "Search knowledge bases",
|
||||
@@ -1117,13 +1116,7 @@
|
||||
"searchDirectTool": "Search Direct Tools",
|
||||
"emptyDirectTool": "No Direct Tools available",
|
||||
"noDirectToolsHint": "Without Direct Tools, the agent will not call external or built-in tools directly. It will rely on knowledge bases, skills, and normal replies.",
|
||||
"removeDirectTool": "Remove Direct Tool {name}",
|
||||
"graphToolsHint": "Use for built-in workflows such as ticket creation and human handoff. These are kept separate from Direct Tools.",
|
||||
"selectGraphTool": "Select Graph Tool",
|
||||
"searchGraphTool": "Search Graph Tools",
|
||||
"emptyGraphTool": "No Graph Tools available",
|
||||
"noGraphToolsHint": "Without Graph Tools, the agent will not expose built-in workflow tools such as ticket creation or human handoff.",
|
||||
"removeGraphTool": "Remove Graph Tool {name}"
|
||||
"removeDirectTool": "Remove Direct Tool {name}"
|
||||
},
|
||||
"aiConfig": {
|
||||
"allStatuses": "All statuses",
|
||||
|
||||
@@ -1090,7 +1090,6 @@
|
||||
"knowledgeTab": "知识库",
|
||||
"skillsTab": "Skills",
|
||||
"directToolsTab": "MCP tools",
|
||||
"graphToolsTab": "内置流程",
|
||||
"knowledgeHint": "至少选择一个知识库,可调整知识库优先级。",
|
||||
"selectKnowledge": "选择并添加知识库",
|
||||
"searchKnowledge": "搜索知识库",
|
||||
@@ -1117,13 +1116,7 @@
|
||||
"searchDirectTool": "搜索 Direct Tool",
|
||||
"emptyDirectTool": "没有可添加的 Direct Tool",
|
||||
"noDirectToolsHint": "不配置 Direct Tool 时,Agent 不会直接调用外部或内置工具,只会依赖知识库、Skill 和普通回复。",
|
||||
"removeDirectTool": "移除 Direct Tool {name}",
|
||||
"graphToolsHint": "用于建单、转人工等系统内置流程,不再混放到 Direct Tools 中。",
|
||||
"selectGraphTool": "选择 Graph Tool",
|
||||
"searchGraphTool": "搜索 Graph Tool",
|
||||
"emptyGraphTool": "没有可添加的 Graph Tool",
|
||||
"noGraphToolsHint": "不配置 Graph Tool 时,Agent 不会暴露建单/转人工等内置流程工具。",
|
||||
"removeGraphTool": "移除 Graph Tool {name}"
|
||||
"removeDirectTool": "移除 Direct Tool {name}"
|
||||
},
|
||||
"aiConfig": {
|
||||
"allStatuses": "全部状态",
|
||||
|
||||
Reference in New Issue
Block a user