Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 10 additions & 26 deletions internal/context/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -41,7 +38,6 @@ func newDynamicPromptSources() []promptSectionSource {
}

// NewConfiguredBuilder 基于可选 SectionSource 列表构建上下文构建器,是推荐的统一构造入口。
// sources 中 nil 元素会被跳过。
func NewConfiguredBuilder(sources ...SectionSource) Builder {
return &DefaultBuilder{
stablePromptSources: newStablePromptSources(sources...),
Expand Down Expand Up @@ -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)

Expand Down
40 changes: 27 additions & 13 deletions internal/context/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
},
}
Expand Down Expand Up @@ -312,7 +314,7 @@ func TestDefaultBuilderBuildUsesSpanTrimPolicyWhenTrimPolicyIsUnset(t *testing.T
}

builder := &DefaultBuilder{
promptSources: []promptSectionSource{
stablePromptSources: []promptSectionSource{
stubPromptSectionSource{sections: []promptSection{{Title: "Stub", Content: "body"}}},
},
}
Expand All @@ -337,7 +339,7 @@ func TestDefaultBuilderBuildReturnsPromptSourceError(t *testing.T) {
t.Parallel()

builder := &DefaultBuilder{
promptSources: []promptSectionSource{
stablePromptSources: []promptSectionSource{
stubPromptSectionSource{err: fmt.Errorf("source failed")},
},
}
Expand Down Expand Up @@ -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()),
Expand All @@ -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")
}
})
}

Expand Down Expand Up @@ -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)
}
}
Loading