From 56ba6bb5a3f2a3c110650999e0112ad320e6f284 Mon Sep 17 00:00:00 2001 From: FennoAI Date: Fri, 5 Jun 2026 16:15:09 +0000 Subject: [PATCH] Simplify context builder source handling Generated with [FennoAI](https://github.com/apps/fennoai) Co-authored-by: minorcell <120795714+minorcell@users.noreply.github.com> --- internal/context/builder.go | 36 ++++++++-------------------- internal/context/builder_test.go | 40 +++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/internal/context/builder.go b/internal/context/builder.go index e81f9e7e..96d520ba 100644 --- a/internal/context/builder.go +++ b/internal/context/builder.go @@ -8,21 +8,18 @@ import ( type DefaultBuilder struct { stablePromptSources []promptSectionSource dynamicPromptSources []promptSectionSource - promptSources []promptSectionSource trimPolicy messageTrimPolicy } // newStablePromptSources 返回稳定提示词来源列表,适合作为缓存前缀。 -// extra 中的非 nil SectionSource 也会追加到 stable 中(如 memo 持久记忆索引)。 +// extra 会追加到 stable 中(如 memo 持久记忆索引)。 func newStablePromptSources(extra ...SectionSource) []promptSectionSource { sources := []promptSectionSource{ corePromptSource{}, newRulesPromptSource(nil), } for _, src := range extra { - if src != nil { - sources = append(sources, src) - } + sources = append(sources, src) } return sources } @@ -41,7 +38,6 @@ func newDynamicPromptSources() []promptSectionSource { } // NewConfiguredBuilder 基于可选 SectionSource 列表构建上下文构建器,是推荐的统一构造入口。 -// sources 中 nil 元素会被跳过。 func NewConfiguredBuilder(sources ...SectionSource) Builder { return &DefaultBuilder{ stablePromptSources: newStablePromptSources(sources...), @@ -74,29 +70,17 @@ func (b *DefaultBuilder) Build(ctx context.Context, input BuildInput) (BuildResu return BuildResult{}, err } - stableSources := b.stablePromptSources - dynamicSources := b.dynamicPromptSources - - // 兼容旧构造方式:如果新字段未设置但旧 promptSources 有内容,回退到旧单列表。 - if len(stableSources) == 0 && len(dynamicSources) == 0 && len(b.promptSources) > 0 { - stableSources = b.promptSources + stableSections, err := collectPromptSections(ctx, input, b.stablePromptSources) + if err != nil { + return BuildResult{}, err } + stablePrompt := composeSystemPrompt(stableSections...) - var stablePrompt, dynamicPrompt string - if stableSources != nil { - stableSections, err := collectPromptSections(ctx, input, stableSources) - if err != nil { - return BuildResult{}, err - } - stablePrompt = composeSystemPrompt(stableSections...) - } - if dynamicSources != nil { - dynamicSections, err := collectPromptSections(ctx, input, dynamicSources) - if err != nil { - return BuildResult{}, err - } - dynamicPrompt = composeSystemPrompt(dynamicSections...) + dynamicSections, err := collectPromptSections(ctx, input, b.dynamicPromptSources) + if err != nil { + return BuildResult{}, err } + dynamicPrompt := composeSystemPrompt(dynamicSections...) systemPrompt := joinSystemPromptParts(stablePrompt, dynamicPrompt) diff --git a/internal/context/builder_test.go b/internal/context/builder_test.go index acd85d32..2e9c6af6 100644 --- a/internal/context/builder_test.go +++ b/internal/context/builder_test.go @@ -263,10 +263,12 @@ func TestDefaultBuilderBuildPlacesRulesBeforeMemo(t *testing.T) { } builder := &DefaultBuilder{ - promptSources: []promptSectionSource{ + stablePromptSources: []promptSectionSource{ corePromptSource{}, newRulesPromptSource(rules.NewLoader(baseDir)), stubPromptSectionSource{sections: []promptSection{{Title: "Memo", Content: "remember this"}}}, + }, + dynamicPromptSources: []promptSectionSource{ &systemStateSource{}, }, } @@ -312,7 +314,7 @@ func TestDefaultBuilderBuildUsesSpanTrimPolicyWhenTrimPolicyIsUnset(t *testing.T } builder := &DefaultBuilder{ - promptSources: []promptSectionSource{ + stablePromptSources: []promptSectionSource{ stubPromptSectionSource{sections: []promptSection{{Title: "Stub", Content: "body"}}}, }, } @@ -337,7 +339,7 @@ func TestDefaultBuilderBuildReturnsPromptSourceError(t *testing.T) { t.Parallel() builder := &DefaultBuilder{ - promptSources: []promptSectionSource{ + stablePromptSources: []promptSectionSource{ stubPromptSectionSource{err: fmt.Errorf("source failed")}, }, } @@ -643,10 +645,12 @@ func TestNewConfiguredBuilder(t *testing.T) { } }) - t.Run("nil section sources are skipped", func(t *testing.T) { - builder := NewConfiguredBuilder(nil, stubPromptSectionSource{ + t.Run("multiple extra section sources are appended", func(t *testing.T) { + builder := NewConfiguredBuilder(stubPromptSectionSource{ + sections: []promptSection{{Title: "First", Content: "first body"}}, + }, stubPromptSectionSource{ sections: []promptSection{{Title: "Extra", Content: "extra body"}}, - }, nil) + }) input := BuildInput{ Messages: []providertypes.Message{{Role: "user", Parts: []providertypes.ContentPart{providertypes.NewTextPart("hello")}}}, Metadata: testMetadata(t.TempDir()), @@ -658,6 +662,9 @@ func TestNewConfiguredBuilder(t *testing.T) { if !strings.Contains(result.SystemPrompt, "## Extra") { t.Errorf("expected Extra section in system prompt") } + if !strings.Contains(result.SystemPrompt, "## First") { + t.Errorf("expected First section in system prompt") + } }) } @@ -895,23 +902,30 @@ func TestDefaultBuilderBuildMemoIsStable(t *testing.T) { } } -func TestDefaultBuilderBuildStableAndDynamicPreservesBackwardCompat(t *testing.T) { +func TestDefaultBuilderBuildStableAndDynamicFields(t *testing.T) { t.Parallel() builder := &DefaultBuilder{ - promptSources: []promptSectionSource{ - stubPromptSectionSource{sections: []promptSection{{Title: "Old", Content: "old style"}}}, + stablePromptSources: []promptSectionSource{ + stubPromptSectionSource{sections: []promptSection{{Title: "Stable", Content: "stable style"}}}, }, + dynamicPromptSources: []promptSectionSource{ + stubPromptSectionSource{sections: []promptSection{{Title: "Dynamic", Content: "dynamic style"}}}, + }, + trimPolicy: spanMessageTrimPolicy{}, } result, err := builder.Build(stdcontext.Background(), BuildInput{}) if err != nil { t.Fatalf("Build() error = %v", err) } - if !strings.Contains(result.SystemPrompt, "old style") { - t.Fatalf("expected old style content in system prompt, got %q", result.SystemPrompt) + if !strings.Contains(result.SystemPrompt, "stable style") || !strings.Contains(result.SystemPrompt, "dynamic style") { + t.Fatalf("expected stable and dynamic content in system prompt, got %q", result.SystemPrompt) + } + if !strings.Contains(result.StableSystemPrompt, "stable style") { + t.Fatalf("expected stable content in StableSystemPrompt, got %q", result.StableSystemPrompt) } - if !strings.Contains(result.StableSystemPrompt, "old style") { - t.Fatalf("expected old style content in StableSystemPrompt, got %q", result.StableSystemPrompt) + if !strings.Contains(result.DynamicSystemPrompt, "dynamic style") { + t.Fatalf("expected dynamic content in DynamicSystemPrompt, got %q", result.DynamicSystemPrompt) } }