refactor(v2/anthropic): use MultiSystem for system prompts
Switches buildRequest to emit anthReq.MultiSystem instead of anthReq.System whenever a system message is present. Upstream's MarshalJSON prefers MultiSystem when non-empty, so the wire format is unchanged for requests without cache_control. This refactor is a prerequisite for attaching cache_control markers to system parts in the next commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,13 +82,14 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var msgs []anth.Message
|
var msgs []anth.Message
|
||||||
|
var systemText string
|
||||||
|
|
||||||
for _, msg := range req.Messages {
|
for _, msg := range req.Messages {
|
||||||
if msg.Role == "system" {
|
if msg.Role == "system" {
|
||||||
if len(anthReq.System) > 0 {
|
if len(systemText) > 0 {
|
||||||
anthReq.System += "\n"
|
systemText += "\n"
|
||||||
}
|
}
|
||||||
anthReq.System += msg.Content
|
systemText += msg.Content
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +225,12 @@ func (p *Provider) buildRequest(req provider.Request) anth.MessagesRequest {
|
|||||||
|
|
||||||
anthReq.Messages = msgs
|
anthReq.Messages = msgs
|
||||||
|
|
||||||
|
if systemText != "" {
|
||||||
|
anthReq.MultiSystem = []anth.MessageSystemPart{
|
||||||
|
anth.NewSystemMessagePart(systemText),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.Temperature != nil {
|
if req.Temperature != nil {
|
||||||
f := float32(*req.Temperature)
|
f := float32(*req.Temperature)
|
||||||
anthReq.Temperature = &f
|
anthReq.Temperature = &f
|
||||||
|
|||||||
84
v2/anthropic/cache_test.go
Normal file
84
v2/anthropic/cache_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package anthropic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/go-llm/v2/provider"
|
||||||
|
anth "github.com/liushuangls/go-anthropic/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBuildRequest_MultiSystemUsedWhenSystemPresent verifies that after the
|
||||||
|
// refactor, the Anthropic provider uses MultiSystem (multi-part) rather than
|
||||||
|
// the flat System string when a system message is present. This is
|
||||||
|
// behavior-preserving — the upstream client's MarshalJSON prefers
|
||||||
|
// MultiSystem when both are set.
|
||||||
|
func TestBuildRequest_MultiSystemUsedWhenSystemPresent(t *testing.T) {
|
||||||
|
p := New("test-key")
|
||||||
|
req := provider.Request{
|
||||||
|
Model: "claude-sonnet-4-6",
|
||||||
|
Messages: []provider.Message{
|
||||||
|
{Role: "system", Content: "you are helpful"},
|
||||||
|
{Role: "user", Content: "hello"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anthReq := p.buildRequest(req)
|
||||||
|
|
||||||
|
if len(anthReq.MultiSystem) != 1 {
|
||||||
|
t.Fatalf("expected 1 MultiSystem part, got %d", len(anthReq.MultiSystem))
|
||||||
|
}
|
||||||
|
if anthReq.MultiSystem[0].Text != "you are helpful" {
|
||||||
|
t.Errorf("expected MultiSystem text 'you are helpful', got %q", anthReq.MultiSystem[0].Text)
|
||||||
|
}
|
||||||
|
if anthReq.MultiSystem[0].Type != "text" {
|
||||||
|
t.Errorf("expected MultiSystem type 'text', got %q", anthReq.MultiSystem[0].Type)
|
||||||
|
}
|
||||||
|
if anthReq.System != "" {
|
||||||
|
t.Errorf("expected System string to be empty when MultiSystem is used, got %q", anthReq.System)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildRequest_MultipleSystemMessagesConcatenated verifies that multiple
|
||||||
|
// system messages are joined into a single MultiSystem part (preserving
|
||||||
|
// existing newline-joined behavior from the old code).
|
||||||
|
func TestBuildRequest_MultipleSystemMessagesConcatenated(t *testing.T) {
|
||||||
|
p := New("test-key")
|
||||||
|
req := provider.Request{
|
||||||
|
Model: "claude-sonnet-4-6",
|
||||||
|
Messages: []provider.Message{
|
||||||
|
{Role: "system", Content: "part A"},
|
||||||
|
{Role: "system", Content: "part B"},
|
||||||
|
{Role: "user", Content: "hello"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anthReq := p.buildRequest(req)
|
||||||
|
|
||||||
|
if len(anthReq.MultiSystem) != 1 {
|
||||||
|
t.Fatalf("expected 1 MultiSystem part after concat, got %d", len(anthReq.MultiSystem))
|
||||||
|
}
|
||||||
|
expected := "part A\npart B"
|
||||||
|
if anthReq.MultiSystem[0].Text != expected {
|
||||||
|
t.Errorf("expected MultiSystem text %q, got %q", expected, anthReq.MultiSystem[0].Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildRequest_NoSystemMessage verifies that when there's no system
|
||||||
|
// message, both System and MultiSystem are empty.
|
||||||
|
func TestBuildRequest_NoSystemMessage(t *testing.T) {
|
||||||
|
p := New("test-key")
|
||||||
|
req := provider.Request{
|
||||||
|
Model: "claude-sonnet-4-6",
|
||||||
|
Messages: []provider.Message{
|
||||||
|
{Role: "user", Content: "hello"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anthReq := p.buildRequest(req)
|
||||||
|
|
||||||
|
if len(anthReq.MultiSystem) != 0 {
|
||||||
|
t.Errorf("expected empty MultiSystem when no system message, got %d parts", len(anthReq.MultiSystem))
|
||||||
|
}
|
||||||
|
if anthReq.System != "" {
|
||||||
|
t.Errorf("expected empty System string, got %q", anthReq.System)
|
||||||
|
}
|
||||||
|
// Silences the unused import warning in this file until Task 5.
|
||||||
|
_ = anth.CacheControlTypeEphemeral
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user