feat: refactor BuildCustomerServiceAgent to use structured input for improved clarity and extensibility

This commit is contained in:
mlogclub
2026-04-11 22:40:04 +08:00
parent a46103c00d
commit a8a33ec6e0
3 changed files with 62 additions and 20 deletions
+1 -1
Submodule docs updated: 13b002c6a6...d24b234349
+19 -2
View File
@@ -113,7 +113,16 @@ func (s *Service) Run(ctx context.Context, req Request) (*Summary, error) {
collector.Data.Skill.RouteReason = summary.SkillRouteReason
collector.Data.Skill.RouteTrace = summary.SkillRouteTrace
agent, err := s.agentFactory.BuildCustomerServiceAgent(ctx, req.AIAgent, req.AIConfig, req.SelectedSkill, filteredToolDefs, nil, req.ExtraTools, req.ExtraToolCodes, collector)
agent, err := s.agentFactory.BuildCustomerServiceAgent(ctx, factory.BuildCustomerServiceAgentInput{
AIAgent: req.AIAgent,
AIConfig: req.AIConfig,
SelectedSkill: req.SelectedSkill,
InstructionToolDefinitions: filteredToolDefs,
DynamicMCPToolDefinitions: filteredToolDefs,
StaticTools: req.ExtraTools,
StaticToolCodes: req.ExtraToolCodes,
Collector: collector,
})
if err != nil {
summary.Status = "error"
summary.ErrorMessage = err.Error()
@@ -241,7 +250,15 @@ func (s *Service) Resume(ctx context.Context, req ResumeRequest) (*Summary, erro
collector.Data.Input.ToolCodes = append(collector.Data.Input.ToolCodes, summary.ToolCodes...)
collector.Data.Model.Provider = string(req.AIConfig.Provider)
collector.Data.Model.Name = req.AIConfig.ModelName
agent, err := s.agentFactory.BuildCustomerServiceAgent(ctx, req.AIAgent, req.AIConfig, nil, nil, nil, req.ExtraTools, req.ExtraToolCodes, collector)
agent, err := s.agentFactory.BuildCustomerServiceAgent(ctx, factory.BuildCustomerServiceAgentInput{
AIAgent: req.AIAgent,
AIConfig: req.AIConfig,
InstructionToolDefinitions: toolDefs,
DynamicMCPToolDefinitions: toolDefs,
StaticTools: req.ExtraTools,
StaticToolCodes: req.ExtraToolCodes,
Collector: collector,
})
if err != nil {
summary.Status = "error"
summary.ErrorMessage = err.Error()
@@ -25,6 +25,33 @@ type AgentFactory struct {
instructionAssembler *InstructionAssembler
}
// BuildCustomerServiceAgentInput 定义客服 Agent 的装配输入。
//
// 之所以收敛成单一输入对象,而不是继续堆叠函数参数,是为了避免:
// 1. 调用点无法看懂每个位置参数的语义;
// 2. instruction 用工具、动态工具、中间件工具之间职责混淆;
// 3. 后续扩展装配项时继续拉长函数签名。
type BuildCustomerServiceAgentInput struct {
// AIAgent 为当前运行的业务 Agent 配置,提供名称、描述、系统提示词等基础信息。
AIAgent *models.AIAgent
// AIConfig 为模型配置,决定底层使用哪个 ChatModel。
AIConfig *models.AIConfig
// SelectedSkill 为当前命中的技能;为空表示本次运行未命中专项技能。
SelectedSkill *models.SkillDefinition
// InstructionToolDefinitions 用于生成 instruction 中的工具说明。
// 它描述“当前允许模型理解和使用的 MCP 工具范围”。
InstructionToolDefinitions []einoadapter.MCPToolDefinition
// DynamicMCPToolDefinitions 用于接入 Eino tool_search middleware 的动态工具集合。
// 这些工具默认不直接挂在 ToolsNode 上,而是经 tool_search 选择后再暴露给模型。
DynamicMCPToolDefinitions []einoadapter.MCPToolDefinition
// StaticTools 为当前运行时直接挂载到 ToolsNode 的固定工具,例如 Graph Tool。
StaticTools []einobasetool.BaseTool
// StaticToolCodes 为固定工具的 modelName -> toolCode 映射,用于 trace 和运行日志归因。
StaticToolCodes map[string]string
// Collector 用于收集运行链路中的 tool trace、graph trace 等调试信息。
Collector *einocallbacks.RuntimeTraceCollector
}
func NewAgentFactory() *AgentFactory {
return &AgentFactory{
chatModelFactory: NewChatModelFactory(),
@@ -33,23 +60,21 @@ func NewAgentFactory() *AgentFactory {
}
}
func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *models.AIAgent, aiConfig *models.AIConfig,
selectedSkill *models.SkillDefinition, instructionToolDefinitions []einoadapter.MCPToolDefinition, mcpToolDefinitions []einoadapter.MCPToolDefinition,
extraTools []einobasetool.BaseTool, extraToolCodes map[string]string,
collector *einocallbacks.RuntimeTraceCollector) (*einoagents.CustomerServiceAgent, error) {
if aiAgent == nil || aiConfig == nil {
// BuildCustomerServiceAgent 根据装配输入构建客服 ChatModelAgent。
func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, input BuildCustomerServiceAgentInput) (*einoagents.CustomerServiceAgent, error) {
if input.AIAgent == nil || input.AIConfig == nil {
return nil, nil
}
chatModel, err := f.chatModelFactory.Build(ctx, aiConfig)
chatModel, err := f.chatModelFactory.Build(ctx, input.AIConfig)
if err != nil {
return nil, err
}
dynamicTools, err := f.toolFactory.BuildBaseToolsByDefinitions(ctx, mcpToolDefinitions)
dynamicTools, err := f.toolFactory.BuildBaseToolsByDefinitions(ctx, input.DynamicMCPToolDefinitions)
if err != nil {
return nil, err
}
allTools := make([]einobasetool.BaseTool, 0, len(extraTools))
allTools = append(allTools, extraTools...)
allTools := make([]einobasetool.BaseTool, 0, len(input.StaticTools))
allTools = append(allTools, input.StaticTools...)
handlers := make([]adk.ChatModelAgentMiddleware, 0, 1)
if len(dynamicTools) > 0 {
toolSearchHandler, toolSearchErr := einotoolsearch.New(ctx, &einotoolsearch.Config{
@@ -60,9 +85,9 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
}
handlers = append(handlers, toolSearchHandler)
}
if collector != nil {
toolMetadataBy := make(map[string]einocallbacks.ToolMetadata, len(mcpToolDefinitions)+len(extraToolCodes))
for _, item := range mcpToolDefinitions {
if input.Collector != nil {
toolMetadataBy := make(map[string]einocallbacks.ToolMetadata, len(input.DynamicMCPToolDefinitions)+len(input.StaticToolCodes))
for _, item := range input.DynamicMCPToolDefinitions {
toolMetadataBy[item.ModelName] = einocallbacks.ToolMetadata{
ToolCode: item.ToolCode,
ServerCode: item.ServerCode,
@@ -70,7 +95,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
SourceType: "mcp",
}
}
for modelName, toolCode := range extraToolCodes {
for modelName, toolCode := range input.StaticToolCodes {
modelName = strings.TrimSpace(modelName)
toolCode = strings.TrimSpace(toolCode)
if modelName == "" || toolCode == "" {
@@ -94,12 +119,12 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
SourceType: resolveToolSourceType(toolCode),
}
}
handlers = append(handlers, einocallbacks.NewRuntimeTraceHandler(collector, toolMetadataBy))
handlers = append(handlers, einocallbacks.NewRuntimeTraceHandler(input.Collector, toolMetadataBy))
}
inner, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: strings.TrimSpace(aiAgent.Name),
Description: strings.TrimSpace(aiAgent.Description),
Instruction: buildAgentInstruction(aiAgent, selectedSkill, instructionToolDefinitions, extraToolCodes),
Name: strings.TrimSpace(input.AIAgent.Name),
Description: strings.TrimSpace(input.AIAgent.Description),
Instruction: buildAgentInstruction(input.AIAgent, input.SelectedSkill, input.InstructionToolDefinitions, input.StaticToolCodes),
Model: chatModel,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{