diff --git a/internal/ai/runtime/internal/engine/service.go b/internal/ai/runtime/internal/engine/service.go index 2a2cf9a..563826b 100644 --- a/internal/ai/runtime/internal/engine/service.go +++ b/internal/ai/runtime/internal/engine/service.go @@ -81,6 +81,10 @@ func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) { summary.ToolCodes = append(summary.ToolCodes, item.ToolCode) toolDefsByModelName[item.ModelName] = item.ToolCode } + if len(filteredToolDefs) > 0 { + summary.ToolCodes = appendIfMissing(summary.ToolCodes, "builtin/tool_search") + toolDefsByModelName["tool_search"] = "builtin/tool_search" + } for modelName, toolCode := range req.ExtraToolCodes { toolCode = strings.TrimSpace(toolCode) modelName = strings.TrimSpace(modelName) @@ -221,6 +225,10 @@ func (s *Service) Resume(ctx context.Context, req ResumeRequest) (*Summary, erro summary.ToolCodes = append(summary.ToolCodes, item.ToolCode) toolDefsByModelName[item.ModelName] = item.ToolCode } + if len(toolDefs) > 0 { + summary.ToolCodes = appendIfMissing(summary.ToolCodes, "builtin/tool_search") + toolDefsByModelName["tool_search"] = "builtin/tool_search" + } for modelName, toolCode := range req.ExtraToolCodes { toolCode = strings.TrimSpace(toolCode) modelName = strings.TrimSpace(modelName) 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 87b61f7..dcf8fc5 100644 --- a/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go +++ b/internal/ai/runtime/internal/impl/callbacks/agent_trace_handler.go @@ -59,7 +59,7 @@ func (h *RuntimeTraceHandler) WrapInvokableToolCall(_ context.Context, endpoint } h.collector.AddToolItem(item) if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinToolSearchToolCode { - h.collector.AddToolSearchItem(buildToolSearchTraceItem(argumentsInJSON, result, err)) + h.collector.AddToolSearchItem(h.buildToolSearchTraceItem(argumentsInJSON, result, err)) } return result, err }, nil @@ -73,6 +73,13 @@ func (h *RuntimeTraceHandler) resolveToolMetadata(modelToolName string) (ToolMet if modelToolName == "" { return ToolMetadata{}, false } + if modelToolName == toolx.BuiltinToolSearchToolName { + return ToolMetadata{ + ToolCode: toolx.BuiltinToolSearchToolCode, + ServerCode: toolx.BuiltinToolCatalogServerCode, + ToolName: toolx.BuiltinToolSearchToolName, + }, true + } metadata, ok := h.toolMetadataBy[modelToolName] return metadata, ok } @@ -101,19 +108,13 @@ func previewToolText(text string, limit int) string { return string(runes[:limit]) + "..." } -func buildToolSearchTraceItem(argumentsInJSON string, result string, runErr error) ToolSearchTraceItem { +func (h *RuntimeTraceHandler) buildToolSearchTraceItem(argumentsInJSON string, result string, runErr error) ToolSearchTraceItem { item := ToolSearchTraceItem{ + Action: "search", Status: "ok", } args := parseToolArguments(argumentsInJSON) - item.Query = strings.TrimSpace(readToolSearchString(args, "query")) - item.TargetToolCode = strings.TrimSpace(readToolSearchString(args, "toolCode")) - if item.TargetToolCode != "" { - item.Action = "invoke" - item.TargetServerCode, item.TargetToolName = toolx.SplitMCPToolCode(item.TargetToolCode) - } else { - item.Action = "search" - } + item.Query = strings.TrimSpace(readToolSearchString(args, "regex_pattern")) if runErr != nil { item.Status = "error" item.ErrorMessage = runErr.Error() @@ -123,8 +124,8 @@ func buildToolSearchTraceItem(argumentsInJSON string, result string, runErr erro if err := json.Unmarshal([]byte(strings.TrimSpace(result)), &payload); err != nil { return item } - candidateItems, _ := payload["candidates"].([]any) - item.CandidateToolCodes = extractCandidateToolCodes(candidateItems) + selectedTools, _ := payload["selectedTools"].([]any) + item.CandidateToolCodes = h.extractSelectedToolCodes(selectedTools) return item } @@ -140,18 +141,24 @@ func readToolSearchString(data map[string]any, key string) string { return text } -func extractCandidateToolCodes(items []any) []string { +func (h *RuntimeTraceHandler) extractSelectedToolCodes(items []any) []string { if len(items) == 0 { return nil } ret := make([]string, 0, len(items)) for _, item := range items { - payload, ok := item.(map[string]any) + toolName, ok := item.(string) if !ok { continue } - toolCode, _ := payload["toolCode"].(string) - toolCode = strings.TrimSpace(toolCode) + toolName = strings.TrimSpace(toolName) + if toolName == "" { + continue + } + toolCode := toolName + if metadata, ok := h.resolveToolMetadata(toolName); ok && strings.TrimSpace(metadata.ToolCode) != "" { + toolCode = strings.TrimSpace(metadata.ToolCode) + } if toolCode == "" { continue } diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go index b1245f2..2f16f6d 100644 --- a/internal/ai/runtime/internal/impl/factory/agent_factory.go +++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go @@ -14,6 +14,7 @@ import ( "cs-agent/internal/pkg/toolx" "github.com/cloudwego/eino/adk" + einotoolsearch "github.com/cloudwego/eino/adk/middlewares/dynamictool/toolsearch" einobasetool "github.com/cloudwego/eino/components/tool" "github.com/cloudwego/eino/compose" ) @@ -43,14 +44,22 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m if err != nil { return nil, err } - baseTools, err := f.toolFactory.BuildBaseToolsByDefinitions(ctx, mcpToolDefinitions) + dynamicTools, err := f.toolFactory.BuildBaseToolsByDefinitions(ctx, mcpToolDefinitions) if err != nil { return nil, err } - allTools := make([]einobasetool.BaseTool, 0, len(baseTools)+len(extraTools)) + allTools := make([]einobasetool.BaseTool, 0, len(extraTools)) allTools = append(allTools, extraTools...) - allTools = append(allTools, baseTools...) handlers := make([]adk.ChatModelAgentMiddleware, 0, 1) + if len(dynamicTools) > 0 { + toolSearchHandler, toolSearchErr := einotoolsearch.New(ctx, &einotoolsearch.Config{ + DynamicTools: dynamicTools, + }) + if toolSearchErr != nil { + return nil, toolSearchErr + } + handlers = append(handlers, toolSearchHandler) + } if collector != nil { toolMetadataBy := make(map[string]einocallbacks.ToolMetadata, len(mcpToolDefinitions)+len(extraToolCodes)) for _, item := range mcpToolDefinitions { @@ -109,13 +118,12 @@ func buildAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.SkillD if skillInstruction := buildSelectedSkillInstruction(selectedSkill, toolDefinitions); skillInstruction != "" { appendixParts = append(appendixParts, skillInstruction) } - if hasToolCode(extraToolCodes, toolx.BuiltinToolSearchToolCode) { + if len(toolDefinitions) > 0 { appendixParts = append(appendixParts, strings.TrimSpace(` 当你需要使用长尾 MCP 能力时,优先使用 tool_search 工具,并遵守以下规则: -1. 先用 query 搜索候选工具,再根据返回的 toolCode 选择真正的目标工具。 -2. 只有在你已经明确要调用哪个动态工具时,才传入 toolCode 和 arguments 进行执行。 -3. 不要臆造 toolCode;必须以 tool_search 返回的候选结果为准。 -4. 如果当前已有固定内置工具可以完成任务,优先使用固定工具,不要滥用 tool_search。 +1. 先调用 tool_search 搜索需要的动态工具,再继续使用已选中的真实工具。 +2. 不要假设所有长尾工具一开始就可见;只有被 tool_search 选中的工具,后续模型调用才会暴露出来。 +3. 如果当前已有固定内置工具可以完成任务,优先使用固定工具,不要滥用 tool_search。 `)) } if hasToolCode(extraToolCodes, toolx.BuiltinCreateTicketConfirmToolCode) { diff --git a/internal/ai/runtime/reply_service.go b/internal/ai/runtime/reply_service.go index 82c41ca..9362f4e 100644 --- a/internal/ai/runtime/reply_service.go +++ b/internal/ai/runtime/reply_service.go @@ -580,6 +580,12 @@ func firstToolSearchTargetToolCode(summary *Summary) string { if toolCode != "" { return toolCode } + if len(item.CandidateToolCodes) == 1 { + toolCode = strings.TrimSpace(item.CandidateToolCodes[0]) + if toolCode != "" { + return toolCode + } + } } return "" } @@ -588,7 +594,8 @@ type runtimeTraceProjection struct { ToolSearch struct { Raw json.RawMessage `json:"-"` Items []struct { - TargetToolCode string `json:"targetToolCode"` + TargetToolCode string `json:"targetToolCode"` + CandidateToolCodes []string `json:"candidateToolCodes"` } `json:"items"` } `json:"toolSearch"` } diff --git a/internal/ai/runtime/service.go b/internal/ai/runtime/service.go index c354cbb..a170e4b 100644 --- a/internal/ai/runtime/service.go +++ b/internal/ai/runtime/service.go @@ -19,7 +19,6 @@ func newService() *service { return &service{ runtime: engine.NewService(), registry: registry.NewRegistry( - tools.NewToolSearchTool(), tools.NewCreateTicketConfirmTool(), ), } @@ -95,7 +94,7 @@ func (s *service) prepareToolsForRun(req *Request) error { AIAgent: req.AIAgent, AIConfig: req.AIConfig, UserMessage: req.UserMessage, - AllowedToolCodes: expandRuntimeAllowedToolCodes(resolveAllowedToolCodes(req.AIAgent, req.SelectedSkill)), + AllowedToolCodes: resolveAllowedToolCodes(req.AIAgent, req.SelectedSkill), }) if err != nil { return err @@ -113,7 +112,7 @@ func (s *service) prepareToolsForResume(req *ResumeRequest) error { Conversation: req.Conversation, AIAgent: req.AIAgent, AIConfig: req.AIConfig, - AllowedToolCodes: expandRuntimeAllowedToolCodes(parseAgentAllowedToolCodes(req.AIAgent)), + AllowedToolCodes: parseAgentAllowedToolCodes(req.AIAgent), }) if err != nil { return err @@ -277,36 +276,3 @@ func resolveAllowedToolCodes(aiAgent *models.AIAgent, skill *models.SkillDefinit return ret } } - -func expandRuntimeAllowedToolCodes(items []string) []string { - ret := make([]string, 0, len(items)+1) - hasMCPTool := false - for _, item := range items { - item = strings.TrimSpace(item) - if item == "" { - continue - } - ret = append(ret, item) - serverCode, toolName := toolx.SplitMCPToolCode(item) - if serverCode != "" && toolName != "" { - hasMCPTool = true - } - } - if hasMCPTool { - ret = appendIfMissingString(ret, toolx.BuiltinToolSearchToolCode) - } - return ret -} - -func appendIfMissingString(items []string, value string) []string { - value = strings.TrimSpace(value) - if value == "" { - return items - } - for _, item := range items { - if strings.TrimSpace(item) == value { - return items - } - } - return append(items, value) -}