From f66b3370165406f4888c3838fdbfc5175633a088 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Mon, 13 Apr 2026 19:23:53 +0800 Subject: [PATCH] feat: add StaticToolMetadata to ToolSet and update related logic for improved tool metadata handling --- .../ai/runtime/internal/executor/service.go | 40 +++++++++-- .../internal/impl/factory/agent_factory.go | 23 +++--- internal/ai/runtime/registry/registry.go | 15 +++- internal/ai/runtime/registry/registry_test.go | 71 +++++++++++++++++++ internal/ai/runtime/registry/types.go | 9 +++ 5 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 internal/ai/runtime/registry/registry_test.go diff --git a/internal/ai/runtime/internal/executor/service.go b/internal/ai/runtime/internal/executor/service.go index 20062bd..676d161 100644 --- a/internal/ai/runtime/internal/executor/service.go +++ b/internal/ai/runtime/internal/executor/service.go @@ -128,6 +128,7 @@ func (s *Service) ExecuteRun(ctx context.Context, req RunInput) (*RunResult, err DynamicMCPToolDefinitions: filteredToolDefs, StaticTools: toolSetStaticTools(req.ToolSet), StaticToolCodes: toolSetStaticToolCodes(req.ToolSet), + StaticToolMetadata: toolSetStaticToolMetadata(req.ToolSet), Collector: collector, }) if err != nil { @@ -260,6 +261,7 @@ func (s *Service) ExecuteResume(ctx context.Context, req ResumeInput) (*RunResul DynamicMCPToolDefinitions: toolDefs, StaticTools: toolSetStaticTools(req.ToolSet), StaticToolCodes: toolSetStaticToolCodes(req.ToolSet), + StaticToolMetadata: toolSetStaticToolMetadata(req.ToolSet), Collector: collector, }) if err != nil { @@ -436,16 +438,27 @@ func appendIfMissing(items []string, item string) []string { } func staticToolCodeList(toolSet *registry.ToolSet) []string { - if toolSet == nil || len(toolSet.StaticToolCodes) == 0 { + if toolSet == nil { return nil } - ret := make([]string, 0, len(toolSet.StaticToolCodes)) - for _, code := range toolSet.StaticToolCodes { + metadata := toolSetStaticToolMetadata(toolSet) + ret := make([]string, 0, len(metadata)) + for _, item := range metadata { + code := strings.TrimSpace(item.ToolCode) + if code == "" { + continue + } + ret = appendIfMissing(ret, code) + } + if len(ret) > 0 { + return ret + } + for _, code := range toolSetStaticToolCodes(toolSet) { code = strings.TrimSpace(code) if code == "" { continue } - ret = append(ret, code) + ret = appendIfMissing(ret, code) } return ret } @@ -468,6 +481,25 @@ func toolSetStaticToolCodes(toolSet *registry.ToolSet) map[string]string { return ret } +func toolSetStaticToolMetadata(toolSet *registry.ToolSet) map[string]registry.ToolMetadata { + if toolSet == nil || len(toolSet.StaticToolMetadata) == 0 { + return nil + } + ret := make(map[string]registry.ToolMetadata, len(toolSet.StaticToolMetadata)) + for name, item := range toolSet.StaticToolMetadata { + trimmedName := strings.TrimSpace(name) + if trimmedName == "" { + continue + } + item.ToolCode = strings.TrimSpace(item.ToolCode) + item.ServerCode = strings.TrimSpace(item.ServerCode) + item.ToolName = strings.TrimSpace(item.ToolName) + item.SourceType = strings.TrimSpace(item.SourceType) + ret[trimmedName] = item + } + return ret +} + func definitionToolCodes(defs []adapter.MCPToolDefinition) []string { ret := make([]string, 0, len(defs)) for _, item := range defs { diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go index cc2429d..4c41394 100644 --- a/internal/ai/runtime/internal/impl/factory/agent_factory.go +++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go @@ -9,6 +9,7 @@ import ( einoadapter "cs-agent/internal/ai/runtime/internal/impl/adapter" einoagents "cs-agent/internal/ai/runtime/internal/impl/agents" einocallbacks "cs-agent/internal/ai/runtime/internal/impl/callbacks" + "cs-agent/internal/ai/runtime/registry" "cs-agent/internal/models" "cs-agent/internal/pkg/toolx" @@ -48,6 +49,8 @@ type BuildCustomerServiceAgentInput struct { StaticTools []einobasetool.BaseTool // StaticToolCodes 为固定工具的 modelName -> toolCode 映射,用于 trace 和运行日志归因。 StaticToolCodes map[string]string + // StaticToolMetadata 为固定工具的 modelName -> metadata 映射,用于 trace 和运行日志归因。 + StaticToolMetadata map[string]registry.ToolMetadata // Collector 用于收集运行链路中的 tool trace、graph trace 等调试信息。 Collector *einocallbacks.RuntimeTraceCollector } @@ -93,7 +96,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, input Buil handlers = append(handlers, skillHandler) } if input.Collector != nil { - toolMetadataBy := make(map[string]einocallbacks.ToolMetadata, len(input.DynamicMCPToolDefinitions)+len(input.StaticToolCodes)) + toolMetadataBy := make(map[string]einocallbacks.ToolMetadata, len(input.DynamicMCPToolDefinitions)+len(input.StaticToolMetadata)) for _, item := range input.DynamicMCPToolDefinitions { toolMetadataBy[item.ModelName] = einocallbacks.ToolMetadata{ ToolCode: item.ToolCode, @@ -102,18 +105,20 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, input Buil SourceType: "mcp", } } - for modelName, toolCode := range input.StaticToolCodes { + for modelName, metadata := range input.StaticToolMetadata { modelName = strings.TrimSpace(modelName) - toolCode = strings.TrimSpace(toolCode) - if modelName == "" || toolCode == "" { + metadata.ToolCode = strings.TrimSpace(metadata.ToolCode) + metadata.ServerCode = strings.TrimSpace(metadata.ServerCode) + metadata.ToolName = strings.TrimSpace(metadata.ToolName) + metadata.SourceType = strings.TrimSpace(metadata.SourceType) + if modelName == "" || metadata.ToolCode == "" { continue } - serverCode, toolName, sourceType, _ := toolx.BuildToolMetadata(toolCode) toolMetadataBy[modelName] = einocallbacks.ToolMetadata{ - ToolCode: toolCode, - ServerCode: serverCode, - ToolName: toolName, - SourceType: sourceType, + ToolCode: metadata.ToolCode, + ServerCode: metadata.ServerCode, + ToolName: metadata.ToolName, + SourceType: metadata.SourceType, } } if input.SelectedSkill != nil { diff --git a/internal/ai/runtime/registry/registry.go b/internal/ai/runtime/registry/registry.go index 2fdb055..76a5ce7 100644 --- a/internal/ai/runtime/registry/registry.go +++ b/internal/ai/runtime/registry/registry.go @@ -20,8 +20,9 @@ func NewRegistry(tools ...Tool) *Registry { func (r *Registry) Resolve(ctx Context) (*ToolSet, error) { ret := &ToolSet{ - StaticTools: make([]einotool.BaseTool, 0, len(r.tools)), - StaticToolCodes: make(map[string]string), + StaticTools: make([]einotool.BaseTool, 0, len(r.tools)), + StaticToolCodes: make(map[string]string), + StaticToolMetadata: make(map[string]ToolMetadata), } allowedToolCodes := makeAllowedToolCodeSet(ctx.AllowedToolCodes) for _, toolDef := range r.tools { @@ -45,6 +46,16 @@ func (r *Registry) Resolve(ctx Context) (*ToolSet, error) { } ret.StaticTools = append(ret.StaticTools, tool) ret.StaticToolCodes[toolName] = toolCode + serverCode, resolvedToolName, sourceType, _ := toolx.BuildToolMetadata(toolCode) + if resolvedToolName == "" { + resolvedToolName = toolName + } + ret.StaticToolMetadata[toolName] = ToolMetadata{ + ToolCode: toolCode, + ServerCode: serverCode, + ToolName: resolvedToolName, + SourceType: sourceType, + } } return ret, nil } diff --git a/internal/ai/runtime/registry/registry_test.go b/internal/ai/runtime/registry/registry_test.go new file mode 100644 index 0000000..60eba89 --- /dev/null +++ b/internal/ai/runtime/registry/registry_test.go @@ -0,0 +1,71 @@ +package registry_test + +import ( + "context" + "testing" + + "cs-agent/internal/ai/runtime/registry" + "cs-agent/internal/models" + "cs-agent/internal/pkg/toolx" + + einotool "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/schema" +) + +type stubTool struct { + name string + code string +} + +func (t stubTool) Name() string { return t.name } +func (t stubTool) Code() string { return t.code } +func (t stubTool) Enabled(registry.Context) bool { + return true +} +func (t stubTool) Build(registry.Context) (einotool.BaseTool, error) { + return stubBaseTool{name: t.name}, nil +} + +type stubBaseTool struct { + name string +} + +func (t stubBaseTool) Info(context.Context) (*schema.ToolInfo, error) { + return &schema.ToolInfo{Name: t.name}, nil +} + +func TestResolveBuildsStaticToolMetadata(t *testing.T) { + r := registry.NewRegistry(stubTool{ + name: toolx.GraphCreateTicketConfirm.Name, + code: toolx.GraphCreateTicketConfirm.Code, + }) + toolSet, err := r.Resolve(registry.Context{ + Conversation: &models.Conversation{ID: 1}, + AIAgent: &models.AIAgent{ID: 1}, + }) + if err != nil { + t.Fatalf("resolve returned error: %v", err) + } + if toolSet == nil { + t.Fatalf("expected tool set") + } + if len(toolSet.StaticToolMetadata) != 1 { + t.Fatalf("expected 1 metadata item, got %d", len(toolSet.StaticToolMetadata)) + } + item, ok := toolSet.StaticToolMetadata[toolx.GraphCreateTicketConfirm.Name] + if !ok { + t.Fatalf("missing metadata for %s", toolx.GraphCreateTicketConfirm.Name) + } + if item.ToolCode != toolx.GraphCreateTicketConfirm.Code { + t.Fatalf("unexpected tool code: %s", item.ToolCode) + } + if item.ServerCode != toolx.GraphCreateTicketConfirm.ServerCode { + t.Fatalf("unexpected server code: %s", item.ServerCode) + } + if item.ToolName != toolx.GraphCreateTicketConfirm.Name { + t.Fatalf("unexpected tool name: %s", item.ToolName) + } + if item.SourceType != toolx.GraphCreateTicketConfirm.SourceType { + t.Fatalf("unexpected source type: %s", item.SourceType) + } +} diff --git a/internal/ai/runtime/registry/types.go b/internal/ai/runtime/registry/types.go index d895528..857f7fa 100644 --- a/internal/ai/runtime/registry/types.go +++ b/internal/ai/runtime/registry/types.go @@ -14,6 +14,13 @@ type Context struct { AllowedToolCodes []string } +type ToolMetadata struct { + ToolCode string + ServerCode string + ToolName string + SourceType string +} + // ToolSet 描述当前运行时可直接挂载到 ToolsNode 上的固定工具集合。 // // 这里不承载通过 tool_search 动态暴露的 MCP 工具;动态工具仍由 engine 层单独装配。 @@ -22,6 +29,8 @@ type ToolSet struct { StaticTools []einotool.BaseTool // StaticToolCodes 为固定工具的 modelName -> toolCode 映射,用于 trace 和运行日志归因。 StaticToolCodes map[string]string + // StaticToolMetadata 为固定工具的 modelName -> metadata 映射,用于 trace、运行日志和后续装配。 + StaticToolMetadata map[string]ToolMetadata } type Tool interface {