From 11252d31da4036a8d46e9043f14e126da64fb66b Mon Sep 17 00:00:00 2001 From: mlogclub Date: Sun, 12 Apr 2026 22:27:09 +0800 Subject: [PATCH] feat: refactor tool definitions to use structured ToolSpec and update related references --- docs | 2 +- .../ai/runtime/internal/engine/service.go | 12 +- .../impl/callbacks/agent_trace_handler.go | 28 +-- .../internal/impl/factory/agent_factory.go | 66 ++---- internal/ai/runtime/registry/registry.go | 18 +- internal/ai/runtime/reply_service.go | 8 +- .../tools/analyze_conversation_tool.go | 6 +- .../tools/create_ticket_confirm_tool.go | 6 +- .../ai/runtime/tools/handoff_graph_tool.go | 6 +- .../tools/prepare_ticket_draft_tool.go | 6 +- internal/ai/runtime/tools/tool_search_tool.go | 6 +- .../tools/triage_service_request_tool.go | 6 +- .../console/ai_agent_controller.go | 30 +-- internal/pkg/toolx/builtin_tools.go | 208 ++++++++++++++---- internal/services/agent_run_log_service.go | 18 +- internal/services/tool_catalog_service.go | 58 ++--- 16 files changed, 280 insertions(+), 204 deletions(-) diff --git a/docs b/docs index 86165ab..18dc676 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 86165abf47a6571abd9c3ca30d0e85ba8ac8e55e +Subproject commit 18dc676a5f6d84a4c8a0497b3205651de7f634e7 diff --git a/internal/ai/runtime/internal/engine/service.go b/internal/ai/runtime/internal/engine/service.go index ae3e016..2ca5c0d 100644 --- a/internal/ai/runtime/internal/engine/service.go +++ b/internal/ai/runtime/internal/engine/service.go @@ -84,12 +84,12 @@ func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) { toolDefsByModelName[item.ModelName] = item.ToolCode } if len(filteredToolDefs) > 0 { - summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinToolSearchToolCode) - toolDefsByModelName[toolx.BuiltinToolSearchToolName] = toolx.BuiltinToolSearchToolCode + summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinToolSearch.Code) + toolDefsByModelName[toolx.BuiltinToolSearch.Name] = toolx.BuiltinToolSearch.Code } if req.SelectedSkill != nil { - summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinSkillToolCode) - toolDefsByModelName[toolx.BuiltinSkillToolName] = toolx.BuiltinSkillToolCode + summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinSkill.Code) + toolDefsByModelName[toolx.BuiltinSkill.Name] = toolx.BuiltinSkill.Code } for modelName, toolCode := range toolSetStaticToolCodes(req.ToolSet) { toolCode = strings.TrimSpace(toolCode) @@ -236,8 +236,8 @@ func (s *Service) Resume(ctx context.Context, req ResumeRequest) (*Summary, erro toolDefsByModelName[item.ModelName] = item.ToolCode } if len(toolDefs) > 0 { - summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinToolSearchToolCode) - toolDefsByModelName[toolx.BuiltinToolSearchToolName] = toolx.BuiltinToolSearchToolCode + summary.ToolCodes = appendIfMissing(summary.ToolCodes, toolx.BuiltinToolSearch.Code) + toolDefsByModelName[toolx.BuiltinToolSearch.Name] = toolx.BuiltinToolSearch.Code } for modelName, toolCode := range toolSetStaticToolCodes(req.ToolSet) { toolCode = strings.TrimSpace(toolCode) diff --git a/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go b/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go index e9cb769..80cb51c 100644 --- a/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go +++ b/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go @@ -64,7 +64,7 @@ func (h *RuntimeTraceHandler) WrapInvokableToolCall(_ context.Context, endpoint item.ErrorMessage = err.Error() } h.collector.AddToolItem(item) - if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.SourceType) == toolx.GraphToolCatalogServerCode { + if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.SourceType) == toolx.GraphCreateTicketConfirm.ServerCode { recommendedAction, riskLevel, ticketDraftReady := parseGraphToolOutcome(item.ToolCode, result) h.collector.AddGraphToolItem(GraphToolTraceItem{ ToolCode: item.ToolCode, @@ -82,7 +82,7 @@ func (h *RuntimeTraceHandler) WrapInvokableToolCall(_ context.Context, endpoint TicketDraftReady: ticketDraftReady, }) } - if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinToolSearchToolCode { + if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinToolSearch.Code { h.collector.AddToolSearchItem(h.buildToolSearchTraceItem(argumentsInJSON, result, err)) } return result, err @@ -99,9 +99,9 @@ func parseGraphToolOutcome(toolCode string, result string) (recommendedAction, r return "", "", false } switch toolCode { - case toolx.GraphAnalyzeConversationToolCode: + case toolx.GraphAnalyzeConversation.Code: return strings.TrimSpace(readToolSearchString(payload, "recommendedNextAction")), strings.TrimSpace(readToolSearchString(payload, "riskLevel")), false - case toolx.GraphTriageServiceRequestToolCode: + case toolx.GraphTriageServiceRequest.Code: recommendedAction = strings.TrimSpace(readToolSearchString(payload, "recommendedAction")) if analysis, ok := payload["analysis"].(map[string]any); ok { riskLevel = strings.TrimSpace(readToolSearchString(analysis, "riskLevel")) @@ -124,20 +124,20 @@ func (h *RuntimeTraceHandler) resolveToolMetadata(modelToolName string) (ToolMet if modelToolName == "" { return ToolMetadata{}, false } - if modelToolName == toolx.BuiltinToolSearchToolName { + if modelToolName == toolx.BuiltinToolSearch.Name { return ToolMetadata{ - ToolCode: toolx.BuiltinToolSearchToolCode, - ServerCode: toolx.BuiltinToolCatalogServerCode, - ToolName: toolx.BuiltinToolSearchToolName, - SourceType: toolx.BuiltinToolCatalogServerCode, + ToolCode: toolx.BuiltinToolSearch.Code, + ServerCode: toolx.BuiltinToolSearch.ServerCode, + ToolName: toolx.BuiltinToolSearch.Name, + SourceType: toolx.BuiltinToolSearch.SourceType, }, true } - if modelToolName == toolx.BuiltinSkillToolName { + if modelToolName == toolx.BuiltinSkill.Name { return ToolMetadata{ - ToolCode: toolx.BuiltinSkillToolCode, - ServerCode: toolx.BuiltinToolCatalogServerCode, - ToolName: toolx.BuiltinSkillToolName, - SourceType: toolx.BuiltinToolCatalogServerCode, + ToolCode: toolx.BuiltinSkill.Code, + ServerCode: toolx.BuiltinSkill.ServerCode, + ToolName: toolx.BuiltinSkill.Name, + SourceType: toolx.BuiltinSkill.SourceType, }, true } metadata, ok := h.toolMetadataBy[modelToolName] diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go index dba5107..c7a5d00 100644 --- a/internal/ai/runtime/internal/impl/factory/agent_factory.go +++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go @@ -108,42 +108,22 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, input Buil if modelName == "" || toolCode == "" { continue } - serverCode, toolName := "", "" - switch toolCode { - case toolx.BuiltinToolSearchToolCode: - serverCode = toolx.BuiltinToolCatalogServerCode - toolName = toolx.BuiltinToolSearchToolName - case toolx.GraphTriageServiceRequestToolCode: - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphTriageServiceRequestToolName - case toolx.GraphAnalyzeConversationToolCode: - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphAnalyzeConversationToolName - case toolx.GraphPrepareTicketDraftToolCode: - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphPrepareTicketDraftToolName - case toolx.GraphCreateTicketConfirmToolCode: - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphCreateTicketConfirmToolName - case toolx.GraphHandoffConversationToolCode: - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphHandoffConversationToolName - } + serverCode, toolName, _ := toolx.GetRegisteredToolIdentity(toolCode) toolMetadataBy[modelName] = einocallbacks.ToolMetadata{ ToolCode: toolCode, ServerCode: serverCode, ToolName: toolName, - SourceType: resolveToolSourceType(toolCode), + SourceType: toolx.ResolveToolSourceType(toolCode), } } if input.SelectedSkill != nil { - toolMetadataBy[toolx.BuiltinSkillToolName] = einocallbacks.ToolMetadata{ - ToolCode: toolx.BuiltinSkillToolCode, - ServerCode: toolx.BuiltinToolCatalogServerCode, - ToolName: toolx.BuiltinSkillToolName, - SourceType: toolx.BuiltinToolCatalogServerCode, + toolMetadataBy[toolx.BuiltinSkill.Name] = einocallbacks.ToolMetadata{ + ToolCode: toolx.BuiltinSkill.Code, + ServerCode: toolx.BuiltinSkill.ServerCode, + ToolName: toolx.BuiltinSkill.Name, + SourceType: toolx.BuiltinSkill.SourceType, } - input.Collector.SetSkillMiddleware(true, toolx.BuiltinSkillToolName) + input.Collector.SetSkillMiddleware(true, toolx.BuiltinSkill.Name) } handlers = append(handlers, einocallbacks.NewRuntimeTraceHandler(input.Collector, toolMetadataBy)) } @@ -181,7 +161,7 @@ func (f *AgentFactory) buildSelectedSkillMiddleware(ctx context.Context, selecte if err != nil { return nil, err } - toolName := toolx.BuiltinSkillToolName + toolName := toolx.BuiltinSkill.Name return einoskill.NewMiddleware(ctx, &einoskill.Config{ Backend: backend, SkillToolName: &toolName, @@ -189,22 +169,6 @@ func (f *AgentFactory) buildSelectedSkillMiddleware(ctx context.Context, selecte }) } -func resolveToolSourceType(toolCode string) string { - toolCode = strings.TrimSpace(toolCode) - switch { - case toolCode == toolx.BuiltinToolSearchToolCode: - return toolx.BuiltinToolCatalogServerCode - case toolCode == toolx.BuiltinSkillToolCode: - return toolx.BuiltinToolCatalogServerCode - case strings.HasPrefix(toolCode, toolx.GraphToolCatalogServerCode+"/"): - return toolx.GraphToolCatalogServerCode - case strings.HasPrefix(toolCode, toolx.BuiltinToolCatalogServerCode+"/"): - return toolx.BuiltinToolCatalogServerCode - default: - return "mcp" - } -} - func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) InstructionAssemblyResult { baseInstruction := "" if aiAgent != nil { @@ -222,7 +186,7 @@ func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.Ski 3. 如果当前已有固定内置工具可以完成任务,优先使用固定工具,不要滥用 tool_search。 `)) } - if hasToolCode(extraToolCodes, toolx.GraphTriageServiceRequestToolCode) { + if hasToolCode(extraToolCodes, toolx.GraphTriageServiceRequest.Code) { appendixParts = append(appendixParts, strings.TrimSpace(` 当你需要判断“继续解答 / 建单 / 转人工”这类复杂升级路径时,优先先调用 triage_service_request 这个 Graph Tool,并遵守以下规则: 1. 该工具会综合当前对话输出 recommendedAction,并在需要建单时附带 ticketDraft。 @@ -232,7 +196,7 @@ func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.Ski 5. 当升级路径不明确时,优先使用该工具,而不是直接凭主 prompt 做复杂分流判断。 `)) } - if hasToolCode(extraToolCodes, toolx.GraphPrepareTicketDraftToolCode) { + if hasToolCode(extraToolCodes, toolx.GraphPrepareTicketDraft.Code) { appendixParts = append(appendixParts, strings.TrimSpace(` 当用户已经表达了建单、投诉、报障、售后处理等诉求,但工单标题、描述或问题整理还比较散乱时,优先调用 prepare_ticket_draft 这个 Graph Tool,并遵守以下规则: 1. 该工具用于整理工单草稿,会返回建议标题、建议描述、缺失字段和追问建议。 @@ -241,7 +205,7 @@ func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.Ski 4. 该工具用于“整理草稿”,不代表已经创建工单。 `)) } - if hasToolCode(extraToolCodes, toolx.GraphAnalyzeConversationToolCode) { + if hasToolCode(extraToolCodes, toolx.GraphAnalyzeConversation.Code) { appendixParts = append(appendixParts, strings.TrimSpace(` 当对话可能涉及投诉升级、退款赔偿、明显负面情绪、是否要建单、是否要转人工等复杂判断时,优先调用 analyze_conversation 这个 Graph Tool,并遵守以下规则: 1. 该工具用于输出结构化摘要、风险信号和下一步建议,不代表实际已经建单或转人工。 @@ -250,7 +214,7 @@ func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.Ski 4. 如果工具建议为 continue_answering,优先继续澄清和解答,不要过早升级动作。 `)) } - if hasToolCode(extraToolCodes, toolx.GraphCreateTicketConfirmToolCode) { + if hasToolCode(extraToolCodes, toolx.GraphCreateTicketConfirm.Code) { appendixParts = append(appendixParts, strings.TrimSpace(` 你可以在确认信息充分后调用 create_ticket_with_confirmation 这个 Graph Tool 来创建工单,但必须遵守以下规则: 1. 只有在用户明确表达希望提交工单、投诉、报障、售后处理等诉求时,才考虑调用该工具。 @@ -260,7 +224,7 @@ func assembleAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.Ski 5. 如果用户只是咨询、抱怨或泛泛表达不满,但没有明确要求建单,优先继续澄清,不要主动创建工单。 `)) } - if hasToolCode(extraToolCodes, toolx.GraphHandoffConversationToolCode) { + if hasToolCode(extraToolCodes, toolx.GraphHandoffConversation.Code) { appendixParts = append(appendixParts, strings.TrimSpace(` 你可以在确认需要人工介入后调用 handoff_to_human 这个 Graph Tool 来转人工,但必须遵守以下规则: 1. 只有在用户明确要求人工客服,或你已经判断该问题必须由人工继续处理时,才调用该工具。 @@ -311,7 +275,7 @@ func buildSelectedSkillActivationInstruction(skill *models.SkillDefinition) stri if desc := strings.TrimSpace(skill.Description); desc != "" { lines = append(lines, fmt.Sprintf("- description: %s", desc)) } - lines = append(lines, "", "执行要求:", "- 本轮优先处理该技能范围内的问题。", fmt.Sprintf("- 需要专项处理细节时,优先调用 %s 工具加载该技能说明后再继续。", toolx.BuiltinSkillToolName), "- 如果关键信息不足,先向用户追问。", "- 不得调用当前技能未授权的工具。") + lines = append(lines, "", "执行要求:", "- 本轮优先处理该技能范围内的问题。", fmt.Sprintf("- 需要专项处理细节时,优先调用 %s 工具加载该技能说明后再继续。", toolx.BuiltinSkill.Name), "- 如果关键信息不足,先向用户追问。", "- 不得调用当前技能未授权的工具。") return strings.TrimSpace(strings.Join(lines, "\n")) } diff --git a/internal/ai/runtime/registry/registry.go b/internal/ai/runtime/registry/registry.go index 3d283fe..3941a2d 100644 --- a/internal/ai/runtime/registry/registry.go +++ b/internal/ai/runtime/registry/registry.go @@ -59,24 +59,24 @@ func isAllowedToolCode(toolCode string, allowedToolCodes map[string]struct{}) bo if isAlwaysAllowedToolCode(toolCode) { return true } - if strings.TrimSpace(toolCode) == toolx.GraphTriageServiceRequestToolCode { - if _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirmToolCode]; ok { + if strings.TrimSpace(toolCode) == toolx.GraphTriageServiceRequest.Code { + if _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirm.Code]; ok { return true } - if _, ok := allowedToolCodes[toolx.GraphHandoffConversationToolCode]; ok { + if _, ok := allowedToolCodes[toolx.GraphHandoffConversation.Code]; ok { return true } } - if strings.TrimSpace(toolCode) == toolx.GraphAnalyzeConversationToolCode { - if _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirmToolCode]; ok { + if strings.TrimSpace(toolCode) == toolx.GraphAnalyzeConversation.Code { + if _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirm.Code]; ok { return true } - if _, ok := allowedToolCodes[toolx.GraphHandoffConversationToolCode]; ok { + if _, ok := allowedToolCodes[toolx.GraphHandoffConversation.Code]; ok { return true } } - if strings.TrimSpace(toolCode) == toolx.GraphPrepareTicketDraftToolCode { - _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirmToolCode] + if strings.TrimSpace(toolCode) == toolx.GraphPrepareTicketDraft.Code { + _, ok := allowedToolCodes[toolx.GraphCreateTicketConfirm.Code] return ok } return false @@ -98,5 +98,5 @@ func makeAllowedToolCodeSet(input []string) map[string]struct{} { } func isAlwaysAllowedToolCode(toolCode string) bool { - return strings.TrimSpace(toolCode) == toolx.GraphHandoffConversationToolCode + return strings.TrimSpace(toolCode) == toolx.GraphHandoffConversation.Code } diff --git a/internal/ai/runtime/reply_service.go b/internal/ai/runtime/reply_service.go index 2f286a1..ca76205 100644 --- a/internal/ai/runtime/reply_service.go +++ b/internal/ai/runtime/reply_service.go @@ -549,7 +549,7 @@ func summaryPrimaryToolCode(summary *Summary) string { return "" } toolCode := firstInvokedToolCode(summary) - if toolCode != toolx.BuiltinToolSearchToolCode { + if toolCode != toolx.BuiltinToolSearch.Code { return toolCode } if targetToolCode := firstToolSearchTargetToolCode(summary); targetToolCode != "" { @@ -611,7 +611,7 @@ func firstGraphToolCode(summary *Summary) string { func extractHandoffReason(summary *Summary) string { trace := parseRuntimeTraceData(summary.TraceData) for _, item := range trace.GraphTools.Items { - if strings.TrimSpace(item.ToolCode) != toolx.GraphHandoffConversationToolCode { + if strings.TrimSpace(item.ToolCode) != toolx.GraphHandoffConversation.Code { continue } if len(item.Arguments) == 0 { @@ -628,7 +628,7 @@ func graphPlanReason(summary *Summary) string { for _, item := range trace.GraphTools.Items { toolCode := strings.TrimSpace(item.ToolCode) switch toolCode { - case toolx.GraphTriageServiceRequestToolCode: + case toolx.GraphTriageServiceRequest.Code: recommendedAction := strings.TrimSpace(item.RecommendedAction) if recommendedAction == "" { return "graph tool triaged service request" @@ -637,7 +637,7 @@ func graphPlanReason(summary *Summary) string { return "graph tool triaged service request: " + recommendedAction + " with ready ticket draft" } return "graph tool triaged service request: " + recommendedAction - case toolx.GraphAnalyzeConversationToolCode: + case toolx.GraphAnalyzeConversation.Code: recommendedAction := strings.TrimSpace(item.RecommendedAction) riskLevel := strings.TrimSpace(item.RiskLevel) switch { diff --git a/internal/ai/runtime/tools/analyze_conversation_tool.go b/internal/ai/runtime/tools/analyze_conversation_tool.go index 50e2293..3001305 100644 --- a/internal/ai/runtime/tools/analyze_conversation_tool.go +++ b/internal/ai/runtime/tools/analyze_conversation_tool.go @@ -15,9 +15,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - AnalyzeConversationToolCode = toolx.GraphAnalyzeConversationToolCode - AnalyzeConversationToolName = toolx.GraphAnalyzeConversationToolName +var ( + AnalyzeConversationToolCode = toolx.GraphAnalyzeConversation.Code + AnalyzeConversationToolName = toolx.GraphAnalyzeConversation.Name ) type AnalyzeConversationTool struct { diff --git a/internal/ai/runtime/tools/create_ticket_confirm_tool.go b/internal/ai/runtime/tools/create_ticket_confirm_tool.go index 7bc8db0..60debcf 100644 --- a/internal/ai/runtime/tools/create_ticket_confirm_tool.go +++ b/internal/ai/runtime/tools/create_ticket_confirm_tool.go @@ -15,9 +15,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - CreateTicketConfirmToolCode = toolx.GraphCreateTicketConfirmToolCode - CreateTicketConfirmToolName = toolx.GraphCreateTicketConfirmToolName +var ( + CreateTicketConfirmToolCode = toolx.GraphCreateTicketConfirm.Code + CreateTicketConfirmToolName = toolx.GraphCreateTicketConfirm.Name ) type CreateTicketGraphTool struct { diff --git a/internal/ai/runtime/tools/handoff_graph_tool.go b/internal/ai/runtime/tools/handoff_graph_tool.go index 09bd5cf..5bc9d2b 100644 --- a/internal/ai/runtime/tools/handoff_graph_tool.go +++ b/internal/ai/runtime/tools/handoff_graph_tool.go @@ -15,9 +15,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - HandoffConversationToolCode = toolx.GraphHandoffConversationToolCode - HandoffConversationToolName = toolx.GraphHandoffConversationToolName +var ( + HandoffConversationToolCode = toolx.GraphHandoffConversation.Code + HandoffConversationToolName = toolx.GraphHandoffConversation.Name ) type HandoffGraphTool struct { diff --git a/internal/ai/runtime/tools/prepare_ticket_draft_tool.go b/internal/ai/runtime/tools/prepare_ticket_draft_tool.go index 5c71a84..b38b1f0 100644 --- a/internal/ai/runtime/tools/prepare_ticket_draft_tool.go +++ b/internal/ai/runtime/tools/prepare_ticket_draft_tool.go @@ -15,9 +15,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - PrepareTicketDraftToolCode = toolx.GraphPrepareTicketDraftToolCode - PrepareTicketDraftToolName = toolx.GraphPrepareTicketDraftToolName +var ( + PrepareTicketDraftToolCode = toolx.GraphPrepareTicketDraft.Code + PrepareTicketDraftToolName = toolx.GraphPrepareTicketDraft.Name ) type PrepareTicketDraftTool struct { diff --git a/internal/ai/runtime/tools/tool_search_tool.go b/internal/ai/runtime/tools/tool_search_tool.go index 3fa025f..4d58d1e 100644 --- a/internal/ai/runtime/tools/tool_search_tool.go +++ b/internal/ai/runtime/tools/tool_search_tool.go @@ -18,9 +18,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - ToolSearchToolCode = toolx.BuiltinToolSearchToolCode - ToolSearchToolName = toolx.BuiltinToolSearchToolName +var ( + ToolSearchToolCode = toolx.BuiltinToolSearch.Code + ToolSearchToolName = toolx.BuiltinToolSearch.Name ) type ToolSearchTool struct { diff --git a/internal/ai/runtime/tools/triage_service_request_tool.go b/internal/ai/runtime/tools/triage_service_request_tool.go index d0118be..2105e81 100644 --- a/internal/ai/runtime/tools/triage_service_request_tool.go +++ b/internal/ai/runtime/tools/triage_service_request_tool.go @@ -15,9 +15,9 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) -const ( - TriageServiceRequestToolCode = toolx.GraphTriageServiceRequestToolCode - TriageServiceRequestToolName = toolx.GraphTriageServiceRequestToolName +var ( + TriageServiceRequestToolCode = toolx.GraphTriageServiceRequest.Code + TriageServiceRequestToolName = toolx.GraphTriageServiceRequest.Name ) type TriageServiceRequestTool struct { diff --git a/internal/controllers/console/ai_agent_controller.go b/internal/controllers/console/ai_agent_controller.go index 8248413..440e82d 100644 --- a/internal/controllers/console/ai_agent_controller.go +++ b/internal/controllers/console/ai_agent_controller.go @@ -177,39 +177,23 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse { } serverCode := strings.TrimSpace(tool.ServerCode) toolName := strings.TrimSpace(tool.ToolName) - if toolCode == toolx.BuiltinToolSearchToolCode { - serverCode = toolx.BuiltinToolCatalogServerCode - toolName = toolx.BuiltinToolSearchToolName - } else if toolCode == toolx.GraphCreateTicketConfirmToolCode { - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphCreateTicketConfirmToolName - } else if toolCode == toolx.GraphHandoffConversationToolCode { - serverCode = toolx.GraphToolCatalogServerCode - toolName = toolx.GraphHandoffConversationToolName + if registeredServerCode, registeredToolName, ok := toolx.GetRegisteredToolIdentity(toolCode); ok { + serverCode = registeredServerCode + toolName = registeredToolName } else if parsedServerCode, parsedToolName := toolx.SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" { serverCode = parsedServerCode toolName = parsedToolName } title := strings.TrimSpace(tool.Title) if title == "" { - switch toolCode { - case toolx.BuiltinToolSearchToolCode: - title = toolx.BuiltinToolSearchToolTitle - case toolx.GraphCreateTicketConfirmToolCode: - title = toolx.GraphCreateTicketConfirmToolTitle - case toolx.GraphHandoffConversationToolCode: - title = toolx.GraphHandoffConversationToolTitle + if registeredTitle := toolx.GetRegisteredToolTitle(toolCode); registeredTitle != "" { + title = registeredTitle } } description := strings.TrimSpace(tool.Description) if description == "" { - switch toolCode { - case toolx.BuiltinToolSearchToolCode: - description = toolx.BuiltinToolSearchToolDescription - case toolx.GraphCreateTicketConfirmToolCode: - description = toolx.GraphCreateTicketConfirmToolDescription - case toolx.GraphHandoffConversationToolCode: - description = toolx.GraphHandoffConversationToolDescription + if registeredDescription := toolx.GetRegisteredToolDescription(toolCode); registeredDescription != "" { + description = registeredDescription } } ret.DirectTools = append(ret.DirectTools, response.AIAgentMCPToolResponse{ diff --git a/internal/pkg/toolx/builtin_tools.go b/internal/pkg/toolx/builtin_tools.go index 26ea0a3..e46ac17 100644 --- a/internal/pkg/toolx/builtin_tools.go +++ b/internal/pkg/toolx/builtin_tools.go @@ -1,51 +1,177 @@ package toolx -const ( - BuiltinToolCatalogServerCode = "builtin" - BuiltinToolSearchToolCode = "builtin/tool_search" - BuiltinToolSearchToolName = "tool_search" - BuiltinToolSearchToolTitle = "搜索并调用动态工具" - BuiltinToolSearchToolDescription = "用于搜索当前允许使用的 MCP 工具,并在确认目标 toolCode 后动态调用该工具。适合处理长尾工具,不应替代固定内置流程工具。" - BuiltinSkillToolCode = "builtin/skill" - BuiltinSkillToolName = "skill" - BuiltinSkillToolTitle = "加载专项技能说明" - BuiltinSkillToolDescription = "用于加载当前命中的专项技能说明文档。仅在本轮已命中 Skill 时可用,适合将专项处理规则按需注入上下文。" - GraphToolCatalogServerCode = "graph" - GraphTriageServiceRequestToolCode = "graph/triage_service_request" - GraphTriageServiceRequestToolName = "triage_service_request" - GraphTriageServiceRequestToolTitle = "升级分流判断" - GraphTriageServiceRequestToolDescription = "Graph Tool。用于综合分析当前对话,判断应继续解答、整理工单草稿还是转人工,并在需要建单时一并整理工单草稿。" - GraphAnalyzeConversationToolCode = "graph/analyze_conversation" - GraphAnalyzeConversationToolName = "analyze_conversation" - GraphAnalyzeConversationToolTitle = "分析对话风险与摘要" - GraphAnalyzeConversationToolDescription = "Graph Tool。用于整理当前对话摘要、识别风险信号,并给出继续解答、建单或转人工的建议。" - GraphPrepareTicketDraftToolCode = "graph/prepare_ticket_draft" - GraphPrepareTicketDraftToolName = "prepare_ticket_draft" - GraphPrepareTicketDraftToolTitle = "整理工单草稿" - GraphPrepareTicketDraftToolDescription = "Graph Tool。用于根据当前会话和已收集信息整理工单草稿,输出建议标题、描述、缺失字段和追问建议。" - GraphCreateTicketConfirmToolCode = "graph/create_ticket_with_confirmation" - GraphCreateTicketConfirmToolName = "create_ticket_with_confirmation" - GraphCreateTicketConfirmToolTitle = "创建工单确认流程" - GraphCreateTicketConfirmToolDescription = "Graph Tool。用于封装建单参数整理、用户确认、真正建单和结果返回的确定性流程。" - GraphHandoffConversationToolCode = "graph/handoff_to_human" - GraphHandoffConversationToolName = "handoff_to_human" - GraphHandoffConversationToolTitle = "转人工确认流程" - GraphHandoffConversationToolDescription = "Graph Tool。用于封装转人工原因整理、用户确认、真正转人工和结果返回的确定性流程。" - BuiltinCreateTicketConfirmToolCode = "builtin/create_ticket_with_confirmation" - BuiltinCreateTicketConfirmToolName = "create_ticket_with_confirmation" - BuiltinCreateTicketConfirmToolTitle = "创建工单并发起确认" - BuiltinCreateTicketConfirmToolDescription = "当用户明确要求创建工单,且标题和描述已经整理清楚后调用。工具会先向用户确认,确认后才真正创建工单。" +import "strings" + +type ToolSpec struct { + Code string + ServerCode string + Name string + Title string + Description string + SourceType string + AutoInjected bool + Aliases []string +} + +var ( + BuiltinToolSearch = ToolSpec{ + Code: "builtin/tool_search", + ServerCode: "builtin", + Name: "tool_search", + Title: "搜索并调用动态工具", + Description: "用于搜索当前允许使用的 MCP 工具,并在确认目标 toolCode 后动态调用该工具。适合处理长尾工具,不应替代固定内置流程工具。", + SourceType: "builtin", + AutoInjected: true, + } + BuiltinSkill = ToolSpec{ + Code: "builtin/skill", + ServerCode: "builtin", + Name: "skill", + Title: "加载专项技能说明", + Description: "用于加载当前命中的专项技能说明文档。仅在本轮已命中 Skill 时可用,适合将专项处理规则按需注入上下文。", + SourceType: "builtin", + AutoInjected: true, + } + GraphTriageServiceRequest = ToolSpec{ + Code: "graph/triage_service_request", + ServerCode: "graph", + Name: "triage_service_request", + Title: "升级分流判断", + Description: "Graph Tool。用于综合分析当前对话,判断应继续解答、整理工单草稿还是转人工,并在需要建单时一并整理工单草稿。", + SourceType: "graph", + } + GraphAnalyzeConversation = ToolSpec{ + Code: "graph/analyze_conversation", + ServerCode: "graph", + Name: "analyze_conversation", + Title: "分析对话风险与摘要", + Description: "Graph Tool。用于整理当前对话摘要、识别风险信号,并给出继续解答、建单或转人工的建议。", + SourceType: "graph", + } + GraphPrepareTicketDraft = ToolSpec{ + Code: "graph/prepare_ticket_draft", + ServerCode: "graph", + Name: "prepare_ticket_draft", + Title: "整理工单草稿", + Description: "Graph Tool。用于根据当前会话和已收集信息整理工单草稿,输出建议标题、描述、缺失字段和追问建议。", + SourceType: "graph", + } + GraphCreateTicketConfirm = ToolSpec{ + Code: "graph/create_ticket_with_confirmation", + ServerCode: "graph", + Name: "create_ticket_with_confirmation", + Title: "创建工单确认流程", + Description: "Graph Tool。用于封装建单参数整理、用户确认、真正建单和结果返回的确定性流程。", + SourceType: "graph", + Aliases: []string{"builtin/create_ticket_with_confirmation"}, + } + GraphHandoffConversation = ToolSpec{ + Code: "graph/handoff_to_human", + ServerCode: "graph", + Name: "handoff_to_human", + Title: "转人工确认流程", + Description: "Graph Tool。用于封装转人工原因整理、用户确认、真正转人工和结果返回的确定性流程。", + SourceType: "graph", + } + RegisteredToolSpecs = []ToolSpec{ + BuiltinToolSearch, + BuiltinSkill, + GraphTriageServiceRequest, + GraphAnalyzeConversation, + GraphPrepareTicketDraft, + GraphCreateTicketConfirm, + GraphHandoffConversation, + } ) +var ( + toolSpecByCode = buildToolSpecByCode() + toolAliasToCanonical = buildToolAliasToCanonical() +) + +func buildToolSpecByCode() map[string]ToolSpec { + ret := make(map[string]ToolSpec, len(RegisteredToolSpecs)) + for _, spec := range RegisteredToolSpecs { + if strings.TrimSpace(spec.Code) == "" { + continue + } + ret[spec.Code] = spec + } + return ret +} + +func buildToolAliasToCanonical() map[string]string { + ret := make(map[string]string) + for _, spec := range RegisteredToolSpecs { + for _, alias := range spec.Aliases { + alias = strings.TrimSpace(alias) + if alias == "" { + continue + } + ret[alias] = spec.Code + } + } + return ret +} + +func ListRegisteredToolSpecs() []ToolSpec { + return append([]ToolSpec(nil), RegisteredToolSpecs...) +} + +func GetRegisteredToolSpec(toolCode string) (ToolSpec, bool) { + toolCode = NormalizeToolCodeAlias(strings.TrimSpace(toolCode)) + spec, ok := toolSpecByCode[toolCode] + return spec, ok +} + +func GetRegisteredToolTitle(toolCode string) string { + spec, ok := GetRegisteredToolSpec(toolCode) + if !ok { + return "" + } + return spec.Title +} + +func GetRegisteredToolDescription(toolCode string) string { + spec, ok := GetRegisteredToolSpec(toolCode) + if !ok { + return "" + } + return spec.Description +} + +func GetRegisteredToolIdentity(toolCode string) (serverCode, toolName string, ok bool) { + spec, ok := GetRegisteredToolSpec(toolCode) + if !ok { + return "", "", false + } + return spec.ServerCode, spec.Name, true +} + +func ResolveToolSourceType(toolCode string) string { + if spec, ok := GetRegisteredToolSpec(toolCode); ok { + return spec.SourceType + } + toolCode = strings.TrimSpace(toolCode) + switch { + case strings.HasPrefix(toolCode, "graph/"): + return "graph" + case strings.HasPrefix(toolCode, "builtin/"): + return "builtin" + default: + return "mcp" + } +} + func IsAutoInjectedToolCode(toolCode string) bool { - return toolCode == BuiltinToolSearchToolCode || toolCode == BuiltinSkillToolCode + spec, ok := GetRegisteredToolSpec(toolCode) + return ok && spec.AutoInjected } func NormalizeToolCodeAlias(toolCode string) string { - switch toolCode { - case BuiltinCreateTicketConfirmToolCode: - return GraphCreateTicketConfirmToolCode - default: - return toolCode + toolCode = strings.TrimSpace(toolCode) + if canonical, ok := toolAliasToCanonical[toolCode]; ok { + return canonical } + return toolCode } diff --git a/internal/services/agent_run_log_service.go b/internal/services/agent_run_log_service.go index 793dec8..353b777 100644 --- a/internal/services/agent_run_log_service.go +++ b/internal/services/agent_run_log_service.go @@ -106,15 +106,15 @@ func (s *agentRunLogService) BuildGraphSummary(aiAgentID int64) *AgentRunGraphSu return cnd } return &AgentRunGraphSummary{ - TriageCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode), - TriagePrepareTicket: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%prepare_ticket%"), - TriagePrepareTicketReady: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%prepare_ticket with ready ticket draft%"), - TriageHandoff: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%handoff_to_human%"), - TriageContinueAnswering: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%continue_answering%"), - AnalyzeCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphAnalyzeConversationToolCode), - PrepareDraftCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphPrepareTicketDraftToolCode), - CreateTicketCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphCreateTicketConfirmToolCode), - HandoffCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphHandoffConversationToolCode), + TriageCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphTriageServiceRequest.Code), + TriagePrepareTicket: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequest.Code, "%prepare_ticket%"), + TriagePrepareTicketReady: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequest.Code, "%prepare_ticket with ready ticket draft%"), + TriageHandoff: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequest.Code, "%handoff_to_human%"), + TriageContinueAnswering: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequest.Code, "%continue_answering%"), + AnalyzeCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphAnalyzeConversation.Code), + PrepareDraftCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphPrepareTicketDraft.Code), + CreateTicketCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphCreateTicketConfirm.Code), + HandoffCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphHandoffConversation.Code), } } diff --git a/internal/services/tool_catalog_service.go b/internal/services/tool_catalog_service.go index 251c298..9eed417 100644 --- a/internal/services/tool_catalog_service.go +++ b/internal/services/tool_catalog_service.go @@ -34,36 +34,38 @@ type MCPToolCatalogItem struct { func (s *toolCatalogService) ListMCPTools(ctx context.Context) ([]MCPToolCatalogItem, error) { cfg := config.Current() ret := make([]MCPToolCatalogItem, 0, 3) - ret = append(ret, MCPToolCatalogItem{ - ToolCode: toolx.GraphCreateTicketConfirmToolCode, - ServerCode: toolx.GraphToolCatalogServerCode, - ToolName: toolx.GraphCreateTicketConfirmToolName, - SourceType: toolx.GraphToolCatalogServerCode, - AutoInjected: false, - Title: toolx.GraphCreateTicketConfirmToolTitle, - Description: toolx.GraphCreateTicketConfirmToolDescription, - }) - ret = append(ret, MCPToolCatalogItem{ - ToolCode: toolx.GraphHandoffConversationToolCode, - ServerCode: toolx.GraphToolCatalogServerCode, - ToolName: toolx.GraphHandoffConversationToolName, - SourceType: toolx.GraphToolCatalogServerCode, - AutoInjected: false, - Title: toolx.GraphHandoffConversationToolTitle, - Description: toolx.GraphHandoffConversationToolDescription, - }) + for _, toolCode := range []string{ + toolx.GraphCreateTicketConfirm.Code, + toolx.GraphHandoffConversation.Code, + } { + spec, ok := toolx.GetRegisteredToolSpec(toolCode) + if !ok { + continue + } + ret = append(ret, MCPToolCatalogItem{ + ToolCode: spec.Code, + ServerCode: spec.ServerCode, + ToolName: spec.Name, + SourceType: spec.SourceType, + AutoInjected: spec.AutoInjected, + Title: spec.Title, + Description: spec.Description, + }) + } if !cfg.MCP.Enabled { return ret, nil } - ret = append(ret, MCPToolCatalogItem{ - ToolCode: toolx.BuiltinToolSearchToolCode, - ServerCode: toolx.BuiltinToolCatalogServerCode, - ToolName: toolx.BuiltinToolSearchToolName, - SourceType: toolx.BuiltinToolCatalogServerCode, - AutoInjected: true, - Title: toolx.BuiltinToolSearchToolTitle, - Description: toolx.BuiltinToolSearchToolDescription, - }) + if spec, ok := toolx.GetRegisteredToolSpec(toolx.BuiltinToolSearch.Code); ok { + ret = append(ret, MCPToolCatalogItem{ + ToolCode: spec.Code, + ServerCode: spec.ServerCode, + ToolName: spec.Name, + SourceType: spec.SourceType, + AutoInjected: spec.AutoInjected, + Title: spec.Title, + Description: spec.Description, + }) + } serverCodes := make([]string, 0, len(cfg.MCP.Servers)) for serverCode, server := range cfg.MCP.Servers { if !server.Enabled { @@ -105,7 +107,7 @@ func (s *toolCatalogService) ValidateToolCode(toolCode string) error { return errorsx.InvalidParam("toolCode不能为空") } switch toolCode { - case toolx.BuiltinToolSearchToolCode, toolx.BuiltinCreateTicketConfirmToolCode, toolx.GraphCreateTicketConfirmToolCode, toolx.GraphHandoffConversationToolCode: + case toolx.BuiltinToolSearch.Code, "builtin/create_ticket_with_confirmation", toolx.GraphCreateTicketConfirm.Code, toolx.GraphHandoffConversation.Code: return nil } serverCode, toolName := toolx.SplitMCPToolCode(toolCode)