From aa7f94e08f2f31189a578af9b09f2705a1b19e99 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Tue, 14 Apr 2026 15:10:50 +0800 Subject: [PATCH] feat: add support for Graph Tools in AIAgent model and related services --- .../ai/application/runtime/tool_catalog.go | 21 ++- .../console/ai_agent_controller.go | 30 ++++ internal/models/models.go | 1 + internal/pkg/dto/request/ai_request.go | 1 + internal/pkg/dto/response/ai_response.go | 1 + internal/pkg/toolx/builtin_tools.go | 6 + internal/services/ai_agent_service.go | 40 ++++++ .../(console)/ai-agents/_components/edit.tsx | 130 ++++++++++++++++-- web/lib/api/admin.ts | 2 + 9 files changed, 213 insertions(+), 19 deletions(-) diff --git a/internal/ai/application/runtime/tool_catalog.go b/internal/ai/application/runtime/tool_catalog.go index c232b28..c19e530 100644 --- a/internal/ai/application/runtime/tool_catalog.go +++ b/internal/ai/application/runtime/tool_catalog.go @@ -73,16 +73,23 @@ func (c *toolCatalog) parseSkillAllowedToolCodes(skill *models.SkillDefinition) } func (c *toolCatalog) parseAgentAllowedToolCodes(aiAgent *models.AIAgent) []string { - if aiAgent == nil || strings.TrimSpace(aiAgent.AllowedMCPTools) == "" { + if aiAgent == nil { return nil } - items, err := toolx.ParseAgentMCPToolsJSON(aiAgent.AllowedMCPTools) - if err != nil { - return nil + ret := make([]string, 0) + if raw := strings.TrimSpace(aiAgent.AllowedMCPTools); raw != "" { + items, err := toolx.ParseAgentMCPToolsJSON(raw) + if err == nil { + for _, item := range items { + ret = append(ret, item.ToolCode) + } + } } - ret := make([]string, 0, len(items)) - for _, item := range items { - ret = append(ret, item.ToolCode) + if raw := strings.TrimSpace(aiAgent.AllowedGraphTools); raw != "" { + var graphTools []string + if err := json.Unmarshal([]byte(raw), &graphTools); err == nil { + ret = append(ret, graphTools...) + } } return toolx.NormalizeToolCodes(ret) } diff --git a/internal/controllers/console/ai_agent_controller.go b/internal/controllers/console/ai_agent_controller.go index 440e82d..08f306e 100644 --- a/internal/controllers/console/ai_agent_controller.go +++ b/internal/controllers/console/ai_agent_controller.go @@ -131,6 +131,7 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse { Skills: make([]response.AIAgentSkillResponse, 0), Teams: make([]response.AIAgentTeamResponse, 0), DirectTools: make([]response.AIAgentMCPToolResponse, 0), + GraphTools: make([]string, 0), SortNo: item.SortNo, Remark: item.Remark, CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"), @@ -175,6 +176,10 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse { if toolx.IsAutoInjectedToolCode(toolCode) { continue } + if toolx.IsAgentDirectGraphToolCode(toolCode) { + ret.GraphTools = appendGraphToolCodeIfMissing(ret.GraphTools, toolCode) + continue + } serverCode := strings.TrimSpace(tool.ServerCode) toolName := strings.TrimSpace(tool.ToolName) if registeredServerCode, registeredToolName, ok := toolx.GetRegisteredToolIdentity(toolCode); ok { @@ -207,5 +212,30 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse { } } } + 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 } + +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) +} diff --git a/internal/models/models.go b/internal/models/models.go index a99f1f4..771bdff 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -514,6 +514,7 @@ 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。 SortNo int `gorm:"type:int;not null;default:0;index"` // SortNo 为后台展示排序号。 Remark string `gorm:"type:text"` // Remark 为备注。 AuditFields diff --git a/internal/pkg/dto/request/ai_request.go b/internal/pkg/dto/request/ai_request.go index b8dc1cc..882c64c 100644 --- a/internal/pkg/dto/request/ai_request.go +++ b/internal/pkg/dto/request/ai_request.go @@ -56,6 +56,7 @@ type CreateAIAgentRequest struct { KnowledgeIDs []int64 `json:"knowledgeIds"` SkillIDs []int64 `json:"skillIds"` DirectTools []AIAgentMCPToolRequest `json:"directTools"` + GraphTools []string `json:"graphTools"` Remark string `json:"remark"` } diff --git a/internal/pkg/dto/response/ai_response.go b/internal/pkg/dto/response/ai_response.go index 29ccec4..74e9c5e 100644 --- a/internal/pkg/dto/response/ai_response.go +++ b/internal/pkg/dto/response/ai_response.go @@ -89,6 +89,7 @@ type AIAgentResponse struct { SkillIDs []int64 `json:"skillIds"` Skills []AIAgentSkillResponse `json:"skills"` DirectTools []AIAgentMCPToolResponse `json:"directTools"` + GraphTools []string `json:"graphTools"` SortNo int `json:"sortNo"` Remark string `json:"remark"` CreatedAt string `json:"createdAt"` diff --git a/internal/pkg/toolx/builtin_tools.go b/internal/pkg/toolx/builtin_tools.go index 06c5fd8..99f45d3 100644 --- a/internal/pkg/toolx/builtin_tools.go +++ b/internal/pkg/toolx/builtin_tools.go @@ -281,6 +281,12 @@ func IsAgentDirectToolCode(toolCode string) bool { return ok && spec.DirectAccess } +func IsAgentDirectGraphToolCode(toolCode string) bool { + toolCode = NormalizeToolCodeAlias(strings.TrimSpace(toolCode)) + spec, ok := GetRegisteredToolSpec(toolCode) + return ok && spec.DirectAccess && spec.SourceType == enums.ToolSourceTypeGraph +} + func NormalizeToolCodeAlias(toolCode string) string { toolCode = strings.TrimSpace(toolCode) if canonical, ok := toolAliasToCanonical[toolCode]; ok { diff --git a/internal/services/ai_agent_service.go b/internal/services/ai_agent_service.go index 68f6091..4f97299 100644 --- a/internal/services/ai_agent_service.go +++ b/internal/services/ai_agent_service.go @@ -105,6 +105,7 @@ 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, "remark": item.Remark, "update_user_id": operator.UserID, "update_user_name": operator.Username, @@ -179,6 +180,10 @@ 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) @@ -187,6 +192,14 @@ 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.InvalidParam("Graph Tools 配置格式不合法") + } + graphToolsJSON = string(buf) + } return &models.AIAgent{ Name: name, Description: strings.TrimSpace(req.Description), @@ -201,6 +214,7 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe KnowledgeIDs: utils.JoinInt64s(knowledgeIDs), SkillIDs: utils.JoinInt64s(skillIDs), AllowedMCPTools: directToolsJSON, + AllowedGraphTools: graphToolsJSON, Remark: strings.TrimSpace(req.Remark), }, nil } @@ -289,6 +303,9 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ if toolx.IsAutoInjectedToolCode(strings.TrimSpace(normalized.ToolCode)) { continue } + if toolx.ResolveToolSourceType(normalized.ToolCode) != enums.ToolSourceTypeMCP { + return nil, errorsx.InvalidParam("Direct Tools 仅允许配置 MCP 工具") + } if err := ToolCatalogService.ValidateToolCode(normalized.ToolCode); err != nil { return nil, err } @@ -302,6 +319,29 @@ 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.InvalidParam("Graph Tools 仅允许配置 Graph Tool") + } + 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 { diff --git a/web/app/(console)/ai-agents/_components/edit.tsx b/web/app/(console)/ai-agents/_components/edit.tsx index 4c411b6..36178e7 100644 --- a/web/app/(console)/ai-agents/_components/edit.tsx +++ b/web/app/(console)/ai-agents/_components/edit.tsx @@ -69,6 +69,11 @@ type DirectToolOption = { groupLabel: string; }; +type GraphToolOption = { + value: string; + label: string; +}; + type EditDialogProps = { open: boolean; saving: boolean; @@ -150,6 +155,7 @@ function buildPayload( teamIds: number[], skillIds: number[], directTools: CreateAIAgentPayload["directTools"], + graphTools: CreateAIAgentPayload["graphTools"], ): CreateAIAgentPayload { return { name: form.name.trim(), @@ -165,6 +171,7 @@ function buildPayload( knowledgeIds, skillIds, directTools, + graphTools, remark: form.remark.trim(), }; } @@ -226,14 +233,19 @@ function EditDialogBody({ const [skillToAdd, setSkillToAdd] = useState(""); const [directToolGroupToAdd, setDirectToolGroupToAdd] = useState(""); const [directToolToAdd, setDirectToolToAdd] = useState(""); + const [graphToolToAdd, setGraphToolToAdd] = useState(""); const [aiConfigs, setAIConfigs] = useState([]); const [knowledgeBases, setKnowledgeBases] = useState([]); const [agentTeams, setAgentTeams] = useState([]); const [skills, setSkills] = useState([]); const [directTools, setDirectTools] = useState([]); + const [graphTools, setGraphTools] = useState([]); const [directToolOptions, setDirectToolOptions] = useState( [], ); + const [graphToolOptions, setGraphToolOptions] = useState( + [], + ); const [toolCatalog, setToolCatalog] = useState([]); useEffect(() => { @@ -244,11 +256,13 @@ function EditDialogBody({ setSelectedTeamIds([]); setSelectedSkillIds([]); setDirectTools([]); + setGraphTools([]); setKnowledgeToAdd(""); setTeamToAdd(""); setSkillToAdd(""); setDirectToolGroupToAdd(""); setDirectToolToAdd(""); + setGraphToolToAdd(""); return; } setLoading(true); @@ -259,11 +273,13 @@ function EditDialogBody({ setSelectedTeamIds((data.teams ?? []).map((team) => team.id)); setSelectedSkillIds(data.skillIds ?? []); setDirectTools(data.directTools ?? []); + setGraphTools(data.graphTools ?? []); setKnowledgeToAdd(""); setTeamToAdd(""); setSkillToAdd(""); setDirectToolGroupToAdd(""); setDirectToolToAdd(""); + setGraphToolToAdd(""); } catch (error) { toast.error( error instanceof Error ? error.message : "加载 AI Agent 详情失败", @@ -338,7 +354,7 @@ function EditDialogBody({ setToolCatalog(catalog); setDirectToolOptions( catalog - .filter((tool) => !tool.autoInjected) + .filter((tool) => !tool.autoInjected && tool.sourceType === "mcp") .map((tool) => ({ value: tool.toolCode, label: `${tool.title || tool.toolName} · ${tool.toolCode}`, @@ -358,6 +374,14 @@ function EditDialogBody({ }, })), ); + setGraphToolOptions( + catalog + .filter((tool) => tool.sourceType === "graph") + .map((tool) => ({ + value: tool.toolCode, + label: `${tool.title || tool.toolName} · ${tool.toolCode}`, + })), + ); } catch (error) { toast.error( error instanceof Error ? error.message : "加载 Direct Tools 失败", @@ -470,10 +494,7 @@ function EditDialogBody({ const directToolsGrouped = useMemo(() => { const groups = new Map(); for (const tool of directTools) { - const groupLabel = - tool.serverCode === "builtin" || tool.toolCode.startsWith("builtin/") - ? "内置工具" - : tool.serverCode || "未分组"; + const groupLabel = tool.serverCode || "未分组"; const current = groups.get(groupLabel) ?? []; current.push(tool); groups.set(groupLabel, current); @@ -481,6 +502,14 @@ function EditDialogBody({ return Array.from(groups.entries()); }, [directTools]); + const addableGraphToolOptions = useMemo( + () => + graphToolOptions.filter( + (option) => !graphTools.includes(option.value), + ), + [graphToolOptions, graphTools], + ); + const addableTeamOptions = useMemo( () => teamOptions.filter( @@ -512,6 +541,7 @@ function EditDialogBody({ selectedTeamIds, selectedSkillIds, directTools, + graphTools, ), ); } @@ -593,6 +623,18 @@ function EditDialogBody({ setDirectTools((prev) => prev.filter((item) => item.toolCode !== value)); } + function handleAddGraphTool(value: string) { + if (!value || graphTools.includes(value)) { + return; + } + setGraphTools((prev) => [...prev, value]); + setGraphToolToAdd(""); + } + + function handleRemoveGraphTool(value: string) { + setGraphTools((prev) => prev.filter((item) => item !== value)); + } + return ( -
+
知识库
@@ -854,8 +896,7 @@ function EditDialogBody({
Direct Tools
- 用于低风险、原子化的实时查询。可选择 MCP 工具,也可选择系统内置工具。 - `tool_search` 会由 Runtime 自动注入,这里不需要手动添加。 + 仅用于外部 MCP 工具的低风险、原子化查询。
@@ -923,9 +964,7 @@ function EditDialogBody({ > {tool.title || catalogItem?.title || value} - {catalogItem?.sourceType === "builtin" - ? "内置" - : tool.serverCode || "MCP"} + {tool.serverCode || "MCP"}
+ +
+
Graph Tools
+
+ 用于建单、转人工等系统内置流程,不再混放到 Direct Tools 中。 +
+ + +
+
+ +
+ +
+
+ {graphTools.length === 0 ? ( + + 不配置 Graph Tool 时,Agent 不会暴露建单/转人工等内置流程工具。 + + ) : ( + graphTools.map((toolCode) => { + const catalogItem = toolCatalog.find( + (item) => item.toolCode === toolCode, + ); + return ( + + {catalogItem?.title || toolCode} + + graph + + + + ); + }) + )} +
+
+
+
diff --git a/web/lib/api/admin.ts b/web/lib/api/admin.ts index 3597af0..4fbc00d 100644 --- a/web/lib/api/admin.ts +++ b/web/lib/api/admin.ts @@ -220,6 +220,7 @@ export type AIAgent = { description: string arguments?: Record }[] + graphTools: string[] sortNo: number remark: string createdAt: string @@ -249,6 +250,7 @@ export type CreateAIAgentPayload = { description: string arguments?: Record }[] + graphTools: string[] remark: string }