refactor(instruction): remove governance instruction handling and update related tests

This commit is contained in:
mlogclub
2026-04-18 11:25:48 +08:00
parent 9756780aec
commit f166caa11c
11 changed files with 79 additions and 103 deletions
+13 -8
View File
@@ -72,17 +72,22 @@ func buildSeedItems(aiConfigID int64, knowledgeIDs []int64, defaultTeamIDs strin
now := time.Now()
return []models.AIAgent{
{
Name: "测试AI客服",
Description: "本地测试 AI 客服 Agent",
Status: enums.StatusOk,
AIConfigID: aiConfigID,
ServiceMode: enums.IMConversationServiceModeAIFirst,
SystemPrompt: "你是一个友好的客服助手,请用中文回答用户的问题。",
WelcomeMessage: "您好,欢迎咨询!有什么可以帮助您的?",
Name: "测试AI客服",
Description: "本地测试 AI 客服 Agent",
Status: enums.StatusOk,
AIConfigID: aiConfigID,
ServiceMode: enums.IMConversationServiceModeAIFirst,
SystemPrompt: `你正在一个有明确工程约束的客服系统中工作。
执行时必须严格遵守当前注入的 Agent 规则和技能规则。
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
禁止承诺未经系统确认的处理时效、完成时间、回访时间或联系时间。
禁止代表人工团队、技术团队、售后团队承诺后续动作,除非当前上下文已有明确的工具结果、人工确认或知识库事实支持。
当用户只表示已发送资料、邮件、截图或附件时,只能确认已收到当前消息或建议等待人工确认,不能自行补充内部处理流程、SLA 或跟进安排。`,
WelcomeMessage: "您好,有什么可以帮助您的?",
ReplyTimeoutSeconds: 180,
TeamIDs: defaultTeamIDs,
HandoffMode: enums.AIAgentHandoffModeWaitPool,
FallbackMessage: "我暂时没有找到足够准确的信息。你可以补充订单号、产品名或更具体的问题,我再继续帮你查。",
FallbackMessage: "我暂时没有找到足够准确的信息。你可以补充具体的问题,我再继续帮你查。",
KnowledgeIDs: utils.JoinInt64s(knowledgeIDs),
SkillIDs: defaultSkillIDs,
SortNo: 10,
+9 -17
View File
@@ -5,18 +5,16 @@ import "strings"
type Assembler struct{}
type AssemblerInput struct {
AgentInstruction string
GovernanceInstruction string
SkillInstruction string
ToolAppendices []string
AgentInstruction string
SkillInstruction string
ToolAppendices []string
}
type AssemblySummary struct {
SectionTitles []string
HasGovernanceRule bool
HasAgentRule bool
HasSkillRule bool
HasToolRule bool
SectionTitles []string
HasAgentRule bool
HasSkillRule bool
HasToolRule bool
}
type AssemblyResult struct {
@@ -33,14 +31,8 @@ func (a *Assembler) Build(input AssemblerInput) string {
}
func (a *Assembler) Assemble(input AssemblerInput) AssemblyResult {
parts := make([]string, 0, 4)
summary := AssemblySummary{SectionTitles: make([]string, 0, 4)}
governanceInstruction := strings.TrimSpace(input.GovernanceInstruction)
if governanceInstruction != "" {
parts = append(parts, buildInstructionSection("系统治理规则", governanceInstruction))
summary.HasGovernanceRule = true
summary.SectionTitles = append(summary.SectionTitles, "系统治理规则")
}
parts := make([]string, 0, 3)
summary := AssemblySummary{SectionTitles: make([]string, 0, 3)}
if agentInstruction := strings.TrimSpace(input.AgentInstruction); agentInstruction != "" {
parts = append(parts, buildInstructionSection("Agent 规则", agentInstruction))
summary.HasAgentRule = true
@@ -7,13 +7,12 @@ import (
func TestAssemblerRespectsProvidedSources(t *testing.T) {
result := NewAssembler().Assemble(AssemblerInput{
GovernanceInstruction: "governance-rule",
AgentInstruction: "agent-rule",
SkillInstruction: "skill-rule",
ToolAppendices: []string{"tool-rule-1", "tool-rule-2"},
AgentInstruction: "agent-rule",
SkillInstruction: "skill-rule",
ToolAppendices: []string{"tool-rule-1", "tool-rule-2"},
})
if !strings.Contains(result.Text, "系统治理规则:\ngovernance-rule") {
t.Fatalf("missing governance instruction: %s", result.Text)
if !strings.Contains(result.Text, "Agent 规则:\nagent-rule") {
t.Fatalf("missing agent instruction: %s", result.Text)
}
if !strings.Contains(result.Text, "当前技能上下文:\nskill-rule") {
t.Fatalf("missing skill instruction: %s", result.Text)
@@ -21,17 +20,17 @@ func TestAssemblerRespectsProvidedSources(t *testing.T) {
if !strings.Contains(result.Text, "工具补充规则:\ntool-rule-1") {
t.Fatalf("missing tool appendix: %s", result.Text)
}
if !result.Summary.HasGovernanceRule || !result.Summary.HasAgentRule || !result.Summary.HasSkillRule || !result.Summary.HasToolRule {
if !result.Summary.HasAgentRule || !result.Summary.HasSkillRule || !result.Summary.HasToolRule {
t.Fatalf("unexpected summary: %#v", result.Summary)
}
}
func TestAssemblerDoesNotInjectGovernanceInstructionWhenInputIsEmpty(t *testing.T) {
func TestAssemblerReturnsEmptyTextWhenInputIsEmpty(t *testing.T) {
result := NewAssembler().Assemble(AssemblerInput{})
if result.Text != "" {
t.Fatalf("expected empty assembled text, got: %s", result.Text)
}
if result.Summary.HasGovernanceRule {
t.Fatalf("expected no governance rule summary, got %#v", result.Summary)
if len(result.Summary.SectionTitles) != 0 || result.Summary.HasAgentRule || result.Summary.HasSkillRule || result.Summary.HasToolRule {
t.Fatalf("expected empty summary, got %#v", result.Summary)
}
}
+2 -23
View File
@@ -1,38 +1,17 @@
package instruction
import (
"strings"
runtimetooling "cs-agent/internal/ai/runtime/tooling"
"cs-agent/internal/ai/runtime/tooling"
"cs-agent/internal/models"
"cs-agent/internal/pkg/toolx"
)
const defaultGovernanceInstruction = `
你正在一个有明确工程约束的客服系统中工作。
执行时必须严格遵守当前注入的 Agent 规则和技能规则。
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
禁止承诺未经系统确认的处理时效、完成时间、回访时间或联系时间。
禁止代表人工团队、技术团队、售后团队承诺后续动作,除非当前上下文已有明确的工具结果、人工确认或知识库事实支持。
当用户只表示已发送资料、邮件、截图或附件时,只能确认已收到当前消息或建议等待人工确认,不能自行补充内部处理流程、SLA 或跟进安排。
`
type ToolAppendixProvider struct{}
func NewToolAppendixProvider() *ToolAppendixProvider {
return &ToolAppendixProvider{}
}
type GovernanceInstructionProvider struct{}
func NewGovernanceInstructionProvider() *GovernanceInstructionProvider {
return &GovernanceInstructionProvider{}
}
func (p *GovernanceInstructionProvider) Resolve() string {
return strings.TrimSpace(defaultGovernanceInstruction)
}
type SkillInstructionProvider struct{}
func NewSkillInstructionProvider() *SkillInstructionProvider {
@@ -43,7 +22,7 @@ func (p *SkillInstructionProvider) Resolve(selectedSkill *models.SkillDefinition
return BuildSelectedSkillActivationInstruction(selectedSkill)
}
func (p *ToolAppendixProvider) Build(toolDefinitions []runtimetooling.MCPToolDefinition, extraToolCodes map[string]string) []string {
func (p *ToolAppendixProvider) Build(toolDefinitions []tooling.MCPToolDefinition, extraToolCodes map[string]string) []string {
appendixParts := make([]string, 0, 1)
toolCodes := make([]string, 0, len(toolDefinitions)+len(extraToolCodes))
for _, item := range toolDefinitions {
+25 -19
View File
@@ -3,38 +3,45 @@ package instruction
import (
"strings"
"cs-agent/internal/ai/runtime/tooling"
runtimetooling "cs-agent/internal/ai/runtime/tooling"
"cs-agent/internal/models"
)
type Service struct {
assembler *Assembler
governanceInstructionProvider *GovernanceInstructionProvider
skillInstructionProvider *SkillInstructionProvider
toolAppendixProvider *ToolAppendixProvider
assembler *Assembler
skillInstructionProvider *SkillInstructionProvider
toolAppendixProvider *ToolAppendixProvider
}
func NewService() *Service {
func NewService(
assembler *Assembler,
skillProvider *SkillInstructionProvider,
toolProvider *ToolAppendixProvider,
) *Service {
if assembler == nil {
assembler = NewAssembler()
}
if skillProvider == nil {
skillProvider = NewSkillInstructionProvider()
}
if toolProvider == nil {
toolProvider = NewToolAppendixProvider()
}
return &Service{
assembler: NewAssembler(),
governanceInstructionProvider: NewGovernanceInstructionProvider(),
skillInstructionProvider: NewSkillInstructionProvider(),
toolAppendixProvider: NewToolAppendixProvider(),
assembler: assembler,
skillInstructionProvider: skillProvider,
toolAppendixProvider: toolProvider,
}
}
func (s *Service) Build(
aiAgent models.AIAgent,
selectedSkill *models.SkillDefinition,
toolDefinitions []tooling.MCPToolDefinition,
toolDefinitions []runtimetooling.MCPToolDefinition,
extraToolCodes map[string]string,
) AssemblyResult {
governanceInstruction := ""
skillInstruction := ""
toolAppendices := make([]string, 0)
if s != nil && s.governanceInstructionProvider != nil {
governanceInstruction = s.governanceInstructionProvider.Resolve()
}
if s != nil && s.skillInstructionProvider != nil {
skillInstruction = s.skillInstructionProvider.Resolve(selectedSkill)
}
@@ -46,9 +53,8 @@ func (s *Service) Build(
assembler = s.assembler
}
return assembler.Assemble(AssemblerInput{
AgentInstruction: strings.TrimSpace(aiAgent.SystemPrompt),
GovernanceInstruction: governanceInstruction,
SkillInstruction: skillInstruction,
ToolAppendices: toolAppendices,
AgentInstruction: strings.TrimSpace(aiAgent.SystemPrompt),
SkillInstruction: skillInstruction,
ToolAppendices: toolAppendices,
})
}
@@ -48,7 +48,6 @@ func (c *RuntimeTraceCollector) SetInstructionSummary(summary InstructionTraceSu
c.mu.Lock()
defer c.mu.Unlock()
c.Data.Instruction.SectionTitles = append([]string(nil), summary.SectionTitles...)
c.Data.Instruction.HasGovernanceRule = summary.HasGovernanceRule
c.Data.Instruction.HasAgentRule = summary.HasAgentRule
c.Data.Instruction.HasSkillRule = summary.HasSkillRule
c.Data.Instruction.HasToolRule = summary.HasToolRule
@@ -70,11 +70,10 @@ type RetrieverPolicyTraceItem struct {
}
type InstructionTraceSummary struct {
SectionTitles []string
HasGovernanceRule bool
HasAgentRule bool
HasSkillRule bool
HasToolRule bool
SectionTitles []string
HasAgentRule bool
HasSkillRule bool
HasToolRule bool
}
type RuntimeTraceData struct {
@@ -91,11 +90,10 @@ type RuntimeTraceData struct {
Name string `json:"name,omitempty"`
} `json:"model"`
Instruction struct {
SectionTitles []string `json:"sectionTitles,omitempty"`
HasGovernanceRule bool `json:"hasGovernanceRule,omitempty"`
HasAgentRule bool `json:"hasAgentRule,omitempty"`
HasSkillRule bool `json:"hasSkillRule,omitempty"`
HasToolRule bool `json:"hasToolRule,omitempty"`
SectionTitles []string `json:"sectionTitles,omitempty"`
HasAgentRule bool `json:"hasAgentRule,omitempty"`
HasSkillRule bool `json:"hasSkillRule,omitempty"`
HasToolRule bool `json:"hasToolRule,omitempty"`
} `json:"instruction"`
Input struct {
HistoryMessageCount int `json:"historyMessageCount,omitempty"`
@@ -56,7 +56,7 @@ func NewAgentFactory() *AgentFactory {
return &AgentFactory{
chatModelFactory: NewChatModelFactory(),
toolFactory: NewToolFactory(),
instructionService: instruction.NewService(),
instructionService: instruction.NewService(nil, nil, nil),
handlerService: NewAgentHandlerService(nil),
}
}
@@ -14,8 +14,8 @@ func TestAgentHandlerServiceBuildWithCollectorOnly(t *testing.T) {
handlers, err := service.Build(context.Background(), BuildAgentHandlersInput{
Collector: collector,
InstructionSummary: einocallbacks.InstructionTraceSummary{
SectionTitles: []string{"系统治理规则"},
HasGovernanceRule: true,
SectionTitles: []string{"Agent 规则"},
HasAgentRule: true,
},
})
if err != nil {
@@ -24,7 +24,7 @@ func TestAgentHandlerServiceBuildWithCollectorOnly(t *testing.T) {
if len(handlers) != 1 {
t.Fatalf("expected 1 handler, got %d", len(handlers))
}
if !collector.Data.Instruction.HasGovernanceRule {
if !collector.Data.Instruction.HasAgentRule {
t.Fatalf("instruction summary was not written to collector: %#v", collector.Data.Instruction)
}
if len(collector.Data.Instruction.SectionTitles) != 1 {
@@ -14,11 +14,10 @@ import (
func buildInstructionTraceSummary(summary runtimeinstruction.AssemblySummary) einocallbacks.InstructionTraceSummary {
return einocallbacks.InstructionTraceSummary{
SectionTitles: append([]string(nil), summary.SectionTitles...),
HasGovernanceRule: summary.HasGovernanceRule,
HasAgentRule: summary.HasAgentRule,
HasSkillRule: summary.HasSkillRule,
HasToolRule: summary.HasToolRule,
SectionTitles: append([]string(nil), summary.SectionTitles...),
HasAgentRule: summary.HasAgentRule,
HasSkillRule: summary.HasSkillRule,
HasToolRule: summary.HasToolRule,
}
}
@@ -8,17 +8,16 @@ import (
func TestBuildInstructionTraceSummary(t *testing.T) {
got := buildInstructionTraceSummary(runtimeinstruction.AssemblySummary{
SectionTitles: []string{"系统治理规则", "当前技能上下文"},
HasGovernanceRule: true,
HasAgentRule: true,
HasSkillRule: true,
HasToolRule: false,
SectionTitles: []string{"Agent 规则", "当前技能上下文"},
HasAgentRule: true,
HasSkillRule: true,
HasToolRule: false,
})
if len(got.SectionTitles) != 2 {
t.Fatalf("unexpected section titles: %#v", got.SectionTitles)
}
if !got.HasGovernanceRule || !got.HasAgentRule || !got.HasSkillRule {
if !got.HasAgentRule || !got.HasSkillRule {
t.Fatalf("unexpected summary flags: %#v", got)
}
if got.HasToolRule {