diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go index c194825..fdf0041 100644 --- a/internal/ai/runtime/internal/impl/factory/agent_factory.go +++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go @@ -21,11 +21,13 @@ import ( ) type AgentFactory struct { - chatModelFactory *ChatModelFactory - toolFactory *ToolFactory - instructionAssembler *InstructionAssembler - projectInstructionProvider *ProjectInstructionProvider - toolAppendixProvider *ToolAppendixProvider + chatModelFactory *ChatModelFactory + toolFactory *ToolFactory + instructionAssembler *InstructionAssembler + projectInstructionProvider *ProjectInstructionProvider + governanceInstructionProvider *GovernanceInstructionProvider + skillInstructionProvider *SkillInstructionProvider + toolAppendixProvider *ToolAppendixProvider } // BuildCustomerServiceAgentInput 定义客服 Agent 的装配输入。 @@ -59,11 +61,13 @@ type BuildCustomerServiceAgentInput struct { func NewAgentFactory() *AgentFactory { return &AgentFactory{ - chatModelFactory: NewChatModelFactory(), - toolFactory: NewToolFactory(), - instructionAssembler: NewInstructionAssembler(), - projectInstructionProvider: NewProjectInstructionProvider(), - toolAppendixProvider: NewToolAppendixProvider(), + chatModelFactory: NewChatModelFactory(), + toolFactory: NewToolFactory(), + instructionAssembler: NewInstructionAssembler(), + projectInstructionProvider: NewProjectInstructionProvider(), + governanceInstructionProvider: NewGovernanceInstructionProvider(), + skillInstructionProvider: NewSkillInstructionProvider(), + toolAppendixProvider: NewToolAppendixProvider(), } } @@ -153,45 +157,32 @@ func (f *AgentFactory) assembleAgentInstruction(aiAgent *models.AIAgent, selecte if aiAgent != nil { baseInstruction = strings.TrimSpace(aiAgent.SystemPrompt) } - appendixParts := buildInstructionAppendices(selectedSkill, toolDefinitions, extraToolCodes) projectInstruction := "" + governanceInstruction := "" + skillInstruction := "" + appendixParts := buildInstructionAppendices(toolDefinitions, extraToolCodes) if f != nil && f.projectInstructionProvider != nil { projectInstruction = f.projectInstructionProvider.Resolve() } + if f != nil && f.governanceInstructionProvider != nil { + governanceInstruction = f.governanceInstructionProvider.Resolve() + } + if f != nil && f.skillInstructionProvider != nil { + skillInstruction = f.skillInstructionProvider.Resolve(selectedSkill) + } assembler := NewInstructionAssembler() if f != nil && f.instructionAssembler != nil { assembler = f.instructionAssembler } return assembler.Assemble(InstructionAssemblerInput{ - AgentInstruction: baseInstruction, - SkillInstruction: firstAppendixPart(appendixParts), - ToolAppendices: remainingAppendixParts(appendixParts), - ProjectInstruction: projectInstruction, + AgentInstruction: baseInstruction, + GovernanceInstruction: governanceInstruction, + SkillInstruction: skillInstruction, + ToolAppendices: appendixParts, + ProjectInstruction: projectInstruction, }) } -func firstAppendixPart(parts []string) string { - if len(parts) == 0 { - return "" - } - return strings.TrimSpace(parts[0]) -} - -func remainingAppendixParts(parts []string) []string { - if len(parts) <= 1 { - return nil - } - ret := make([]string, 0, len(parts)-1) - for _, item := range parts[1:] { - item = strings.TrimSpace(item) - if item == "" { - continue - } - ret = append(ret, item) - } - return ret -} - func buildSelectedSkillActivationInstruction(skill *models.SkillDefinition) string { if skill == nil { return "" diff --git a/internal/ai/runtime/internal/impl/factory/instruction_assembler.go b/internal/ai/runtime/internal/impl/factory/instruction_assembler.go index 3180a38..8326dec 100644 --- a/internal/ai/runtime/internal/impl/factory/instruction_assembler.go +++ b/internal/ai/runtime/internal/impl/factory/instruction_assembler.go @@ -2,15 +2,22 @@ package factory import "strings" +const defaultGovernanceInstruction = ` +你正在一个有明确工程约束的客服系统中工作。 +执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。 +如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。 +` + type InstructionAssembler struct { governanceInstruction string } type InstructionAssemblerInput struct { - AgentInstruction string - SkillInstruction string - ToolAppendices []string - ProjectInstruction string + AgentInstruction string + GovernanceInstruction string + SkillInstruction string + ToolAppendices []string + ProjectInstruction string } // InstructionAssemblySummary 描述 instruction 各组成部分的来源摘要。 @@ -31,11 +38,7 @@ type InstructionAssemblyResult struct { func NewInstructionAssembler() *InstructionAssembler { return &InstructionAssembler{ - governanceInstruction: strings.TrimSpace(` -你正在一个有明确工程约束的客服系统中工作。 -执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。 -如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。 -`), + governanceInstruction: strings.TrimSpace(defaultGovernanceInstruction), } } @@ -56,8 +59,12 @@ func (a *InstructionAssembler) Assemble(input InstructionAssemblerInput) Instruc summary.HasProjectRule = true summary.SectionTitles = append(summary.SectionTitles, "项目级规则") } - if a != nil && strings.TrimSpace(a.governanceInstruction) != "" { - parts = append(parts, buildInstructionSection("系统治理规则", a.governanceInstruction)) + governanceInstruction := strings.TrimSpace(input.GovernanceInstruction) + if governanceInstruction == "" && a != nil { + governanceInstruction = strings.TrimSpace(a.governanceInstruction) + } + if governanceInstruction != "" { + parts = append(parts, buildInstructionSection("系统治理规则", governanceInstruction)) summary.HasGovernanceRule = true summary.SectionTitles = append(summary.SectionTitles, "系统治理规则") } diff --git a/internal/ai/runtime/internal/impl/factory/instruction_assembler_test.go b/internal/ai/runtime/internal/impl/factory/instruction_assembler_test.go new file mode 100644 index 0000000..3eddc7e --- /dev/null +++ b/internal/ai/runtime/internal/impl/factory/instruction_assembler_test.go @@ -0,0 +1,31 @@ +package factory + +import ( + "strings" + "testing" +) + +func TestInstructionAssemblerRespectsProvidedSources(t *testing.T) { + result := NewInstructionAssembler().Assemble(InstructionAssemblerInput{ + ProjectInstruction: "project-rule", + GovernanceInstruction: "governance-rule", + AgentInstruction: "agent-rule", + SkillInstruction: "skill-rule", + ToolAppendices: []string{"tool-rule-1", "tool-rule-2"}, + }) + if !strings.Contains(result.Text, "项目级规则:\nproject-rule") { + t.Fatalf("missing project instruction: %s", result.Text) + } + if !strings.Contains(result.Text, "系统治理规则:\ngovernance-rule") { + t.Fatalf("missing governance instruction: %s", result.Text) + } + if !strings.Contains(result.Text, "当前技能上下文:\nskill-rule") { + t.Fatalf("missing skill instruction: %s", result.Text) + } + if !strings.Contains(result.Text, "工具补充规则:\ntool-rule-1") { + t.Fatalf("missing tool appendix: %s", result.Text) + } + if !result.Summary.HasProjectRule || !result.Summary.HasGovernanceRule || !result.Summary.HasAgentRule || !result.Summary.HasSkillRule || !result.Summary.HasToolRule { + t.Fatalf("unexpected summary: %#v", result.Summary) + } +} diff --git a/internal/ai/runtime/internal/impl/factory/instruction_providers.go b/internal/ai/runtime/internal/impl/factory/instruction_providers.go index ce3427c..a1f6874 100644 --- a/internal/ai/runtime/internal/impl/factory/instruction_providers.go +++ b/internal/ai/runtime/internal/impl/factory/instruction_providers.go @@ -67,11 +67,28 @@ func NewToolAppendixProvider() *ToolAppendixProvider { return &ToolAppendixProvider{} } -func (p *ToolAppendixProvider) Build(selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { - appendixParts := make([]string, 0, 2) - if skillInstruction := buildSelectedSkillActivationInstruction(selectedSkill); skillInstruction != "" { - appendixParts = append(appendixParts, skillInstruction) - } +type GovernanceInstructionProvider struct{} + +func NewGovernanceInstructionProvider() *GovernanceInstructionProvider { + return &GovernanceInstructionProvider{} +} + +func (p *GovernanceInstructionProvider) Resolve() string { + return strings.TrimSpace(defaultGovernanceInstruction) +} + +type SkillInstructionProvider struct{} + +func NewSkillInstructionProvider() *SkillInstructionProvider { + return &SkillInstructionProvider{} +} + +func (p *SkillInstructionProvider) Resolve(selectedSkill *models.SkillDefinition) string { + return buildSelectedSkillActivationInstruction(selectedSkill) +} + +func (p *ToolAppendixProvider) Build(toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { + appendixParts := make([]string, 0, 1) toolCodes := make([]string, 0, len(toolDefinitions)+len(extraToolCodes)) for _, item := range toolDefinitions { toolCodes = append(toolCodes, item.ToolCode) diff --git a/internal/ai/runtime/internal/impl/factory/tool_helpers.go b/internal/ai/runtime/internal/impl/factory/tool_helpers.go index 930dace..324370d 100644 --- a/internal/ai/runtime/internal/impl/factory/tool_helpers.go +++ b/internal/ai/runtime/internal/impl/factory/tool_helpers.go @@ -56,6 +56,6 @@ func buildRuntimeTraceToolMetadata( return ret } -func buildInstructionAppendices(selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { - return NewToolAppendixProvider().Build(selectedSkill, toolDefinitions, extraToolCodes) +func buildInstructionAppendices(toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { + return NewToolAppendixProvider().Build(toolDefinitions, extraToolCodes) }