feat: add governance and skill instruction providers, update instruction assembly logic

This commit is contained in:
mlogclub
2026-04-14 10:24:13 +08:00
parent bea080d23c
commit 1683ed6ed5
5 changed files with 101 additions and 55 deletions
@@ -21,11 +21,13 @@ import (
) )
type AgentFactory struct { type AgentFactory struct {
chatModelFactory *ChatModelFactory chatModelFactory *ChatModelFactory
toolFactory *ToolFactory toolFactory *ToolFactory
instructionAssembler *InstructionAssembler instructionAssembler *InstructionAssembler
projectInstructionProvider *ProjectInstructionProvider projectInstructionProvider *ProjectInstructionProvider
toolAppendixProvider *ToolAppendixProvider governanceInstructionProvider *GovernanceInstructionProvider
skillInstructionProvider *SkillInstructionProvider
toolAppendixProvider *ToolAppendixProvider
} }
// BuildCustomerServiceAgentInput 定义客服 Agent 的装配输入。 // BuildCustomerServiceAgentInput 定义客服 Agent 的装配输入。
@@ -59,11 +61,13 @@ type BuildCustomerServiceAgentInput struct {
func NewAgentFactory() *AgentFactory { func NewAgentFactory() *AgentFactory {
return &AgentFactory{ return &AgentFactory{
chatModelFactory: NewChatModelFactory(), chatModelFactory: NewChatModelFactory(),
toolFactory: NewToolFactory(), toolFactory: NewToolFactory(),
instructionAssembler: NewInstructionAssembler(), instructionAssembler: NewInstructionAssembler(),
projectInstructionProvider: NewProjectInstructionProvider(), projectInstructionProvider: NewProjectInstructionProvider(),
toolAppendixProvider: NewToolAppendixProvider(), governanceInstructionProvider: NewGovernanceInstructionProvider(),
skillInstructionProvider: NewSkillInstructionProvider(),
toolAppendixProvider: NewToolAppendixProvider(),
} }
} }
@@ -153,45 +157,32 @@ func (f *AgentFactory) assembleAgentInstruction(aiAgent *models.AIAgent, selecte
if aiAgent != nil { if aiAgent != nil {
baseInstruction = strings.TrimSpace(aiAgent.SystemPrompt) baseInstruction = strings.TrimSpace(aiAgent.SystemPrompt)
} }
appendixParts := buildInstructionAppendices(selectedSkill, toolDefinitions, extraToolCodes)
projectInstruction := "" projectInstruction := ""
governanceInstruction := ""
skillInstruction := ""
appendixParts := buildInstructionAppendices(toolDefinitions, extraToolCodes)
if f != nil && f.projectInstructionProvider != nil { if f != nil && f.projectInstructionProvider != nil {
projectInstruction = f.projectInstructionProvider.Resolve() 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() assembler := NewInstructionAssembler()
if f != nil && f.instructionAssembler != nil { if f != nil && f.instructionAssembler != nil {
assembler = f.instructionAssembler assembler = f.instructionAssembler
} }
return assembler.Assemble(InstructionAssemblerInput{ return assembler.Assemble(InstructionAssemblerInput{
AgentInstruction: baseInstruction, AgentInstruction: baseInstruction,
SkillInstruction: firstAppendixPart(appendixParts), GovernanceInstruction: governanceInstruction,
ToolAppendices: remainingAppendixParts(appendixParts), SkillInstruction: skillInstruction,
ProjectInstruction: projectInstruction, 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 { func buildSelectedSkillActivationInstruction(skill *models.SkillDefinition) string {
if skill == nil { if skill == nil {
return "" return ""
@@ -2,15 +2,22 @@ package factory
import "strings" import "strings"
const defaultGovernanceInstruction = `
你正在一个有明确工程约束的客服系统中工作。
执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
`
type InstructionAssembler struct { type InstructionAssembler struct {
governanceInstruction string governanceInstruction string
} }
type InstructionAssemblerInput struct { type InstructionAssemblerInput struct {
AgentInstruction string AgentInstruction string
SkillInstruction string GovernanceInstruction string
ToolAppendices []string SkillInstruction string
ProjectInstruction string ToolAppendices []string
ProjectInstruction string
} }
// InstructionAssemblySummary 描述 instruction 各组成部分的来源摘要。 // InstructionAssemblySummary 描述 instruction 各组成部分的来源摘要。
@@ -31,11 +38,7 @@ type InstructionAssemblyResult struct {
func NewInstructionAssembler() *InstructionAssembler { func NewInstructionAssembler() *InstructionAssembler {
return &InstructionAssembler{ return &InstructionAssembler{
governanceInstruction: strings.TrimSpace(` governanceInstruction: strings.TrimSpace(defaultGovernanceInstruction),
你正在一个有明确工程约束的客服系统中工作。
执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
`),
} }
} }
@@ -56,8 +59,12 @@ func (a *InstructionAssembler) Assemble(input InstructionAssemblerInput) Instruc
summary.HasProjectRule = true summary.HasProjectRule = true
summary.SectionTitles = append(summary.SectionTitles, "项目级规则") summary.SectionTitles = append(summary.SectionTitles, "项目级规则")
} }
if a != nil && strings.TrimSpace(a.governanceInstruction) != "" { governanceInstruction := strings.TrimSpace(input.GovernanceInstruction)
parts = append(parts, buildInstructionSection("系统治理规则", a.governanceInstruction)) if governanceInstruction == "" && a != nil {
governanceInstruction = strings.TrimSpace(a.governanceInstruction)
}
if governanceInstruction != "" {
parts = append(parts, buildInstructionSection("系统治理规则", governanceInstruction))
summary.HasGovernanceRule = true summary.HasGovernanceRule = true
summary.SectionTitles = append(summary.SectionTitles, "系统治理规则") summary.SectionTitles = append(summary.SectionTitles, "系统治理规则")
} }
@@ -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)
}
}
@@ -67,11 +67,28 @@ func NewToolAppendixProvider() *ToolAppendixProvider {
return &ToolAppendixProvider{} return &ToolAppendixProvider{}
} }
func (p *ToolAppendixProvider) Build(selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { type GovernanceInstructionProvider struct{}
appendixParts := make([]string, 0, 2)
if skillInstruction := buildSelectedSkillActivationInstruction(selectedSkill); skillInstruction != "" { func NewGovernanceInstructionProvider() *GovernanceInstructionProvider {
appendixParts = append(appendixParts, skillInstruction) 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)) toolCodes := make([]string, 0, len(toolDefinitions)+len(extraToolCodes))
for _, item := range toolDefinitions { for _, item := range toolDefinitions {
toolCodes = append(toolCodes, item.ToolCode) toolCodes = append(toolCodes, item.ToolCode)
@@ -56,6 +56,6 @@ func buildRuntimeTraceToolMetadata(
return ret return ret
} }
func buildInstructionAppendices(selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string { func buildInstructionAppendices(toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) []string {
return NewToolAppendixProvider().Build(selectedSkill, toolDefinitions, extraToolCodes) return NewToolAppendixProvider().Build(toolDefinitions, extraToolCodes)
} }