Skip to content

Commit 5ce2a34

Browse files
committed
fix(gom-33): use the last text block
The \n\n preamble text block is not mentioned in docs and is not reliable. Also the docs guarantee the order of thinking blocks followed by text blocks
1 parent 0b1463c commit 5ce2a34

2 files changed

Lines changed: 112 additions & 42 deletions

File tree

internal/providers/anthropic/anthropic.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,14 +591,21 @@ func extractContentFromResponsesInput(content interface{}) string {
591591
return ""
592592
}
593593

594-
// extractTextContent returns the text from the first "text" content block,
595-
// skipping "thinking" blocks that appear when extended thinking is enabled.
594+
// extractTextContent returns the text from the last "text" content block.
595+
// When extended thinking is enabled, Anthropic returns: [text("\n\n"), thinking(...), text(answer)].
596+
// Taking the last text block ensures we get the actual answer, not the empty preamble.
596597
func extractTextContent(blocks []anthropicContent) string {
598+
last := ""
599+
found := false
597600
for _, b := range blocks {
598601
if b.Type == "text" {
599-
return b.Text
602+
last = b.Text
603+
found = true
600604
}
601605
}
606+
if found {
607+
return last
608+
}
602609
if len(blocks) > 0 {
603610
return blocks[0].Text
604611
}
@@ -847,3 +854,4 @@ func (sc *responsesStreamConverter) convertEvent(event *anthropicStreamEvent) st
847854

848855
return ""
849856
}
857+

internal/providers/anthropic/anthropic_test.go

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -615,29 +615,51 @@ func TestConvertFromAnthropicResponse(t *testing.T) {
615615
}
616616

617617
func TestConvertFromAnthropicResponse_WithThinkingBlocks(t *testing.T) {
618-
resp := &anthropicResponse{
619-
ID: "msg_456",
620-
Type: "message",
621-
Role: "assistant",
622-
Model: "claude-opus-4-6",
623-
Content: []anthropicContent{
624-
{Type: "thinking", Text: "Let me think about this..."},
625-
{Type: "text", Text: "The capital of France is Paris."},
618+
tests := []struct {
619+
name string
620+
content []anthropicContent
621+
expectedText string
622+
}{
623+
{
624+
name: "thinking then text",
625+
content: []anthropicContent{
626+
{Type: "thinking", Text: "Let me think about this..."},
627+
{Type: "text", Text: "The capital of France is Paris."},
628+
},
629+
expectedText: "The capital of France is Paris.",
626630
},
627-
StopReason: "end_turn",
628-
Usage: anthropicUsage{
629-
InputTokens: 15,
630-
OutputTokens: 40,
631+
{
632+
name: "preamble text then thinking then answer",
633+
content: []anthropicContent{
634+
{Type: "text", Text: "\n\n"},
635+
{Type: "thinking", Text: ""},
636+
{Type: "text", Text: "The capital of France is Paris."},
637+
},
638+
expectedText: "The capital of France is Paris.",
631639
},
632640
}
633641

634-
result := convertFromAnthropicResponse(resp)
642+
for _, tt := range tests {
643+
t.Run(tt.name, func(t *testing.T) {
644+
resp := &anthropicResponse{
645+
ID: "msg_456",
646+
Type: "message",
647+
Role: "assistant",
648+
Model: "claude-opus-4-6",
649+
Content: tt.content,
650+
StopReason: "end_turn",
651+
Usage: anthropicUsage{InputTokens: 15, OutputTokens: 40},
652+
}
635653

636-
if result.Choices[0].Message.Content != "The capital of France is Paris." {
637-
t.Errorf("expected text content, got %q", result.Choices[0].Message.Content)
638-
}
639-
if result.Usage.CompletionTokens != 40 {
640-
t.Errorf("CompletionTokens = %d, want 40", result.Usage.CompletionTokens)
654+
result := convertFromAnthropicResponse(resp)
655+
656+
if result.Choices[0].Message.Content != tt.expectedText {
657+
t.Errorf("expected %q, got %q", tt.expectedText, result.Choices[0].Message.Content)
658+
}
659+
if result.Usage.CompletionTokens != 40 {
660+
t.Errorf("CompletionTokens = %d, want 40", result.Usage.CompletionTokens)
661+
}
662+
})
641663
}
642664
}
643665

@@ -669,6 +691,24 @@ func TestExtractTextContent(t *testing.T) {
669691
},
670692
expected: "final answer",
671693
},
694+
{
695+
name: "preamble text then thinking then answer text",
696+
blocks: []anthropicContent{
697+
{Type: "text", Text: "\n\n"},
698+
{Type: "thinking", Text: ""},
699+
{Type: "text", Text: "The capital of France is **Paris**."},
700+
},
701+
expected: "The capital of France is **Paris**.",
702+
},
703+
{
704+
name: "preamble text then thinking then answer - picks last text",
705+
blocks: []anthropicContent{
706+
{Type: "text", Text: "preamble"},
707+
{Type: "thinking", Text: "let me think..."},
708+
{Type: "text", Text: "real answer"},
709+
},
710+
expected: "real answer",
711+
},
672712
{
673713
name: "empty blocks",
674714
blocks: []anthropicContent{},
@@ -1274,32 +1314,54 @@ func TestConvertAnthropicResponseToResponses(t *testing.T) {
12741314
}
12751315

12761316
func TestConvertAnthropicResponseToResponses_WithThinkingBlocks(t *testing.T) {
1277-
resp := &anthropicResponse{
1278-
ID: "msg_789",
1279-
Type: "message",
1280-
Role: "assistant",
1281-
Model: "claude-opus-4-6",
1282-
Content: []anthropicContent{
1283-
{Type: "thinking", Text: "The user is asking about geography..."},
1284-
{Type: "text", Text: "The capital of France is Paris."},
1317+
tests := []struct {
1318+
name string
1319+
content []anthropicContent
1320+
expectedText string
1321+
}{
1322+
{
1323+
name: "thinking then text",
1324+
content: []anthropicContent{
1325+
{Type: "thinking", Text: "The user is asking about geography..."},
1326+
{Type: "text", Text: "The capital of France is Paris."},
1327+
},
1328+
expectedText: "The capital of France is Paris.",
12851329
},
1286-
StopReason: "end_turn",
1287-
Usage: anthropicUsage{
1288-
InputTokens: 20,
1289-
OutputTokens: 50,
1330+
{
1331+
name: "preamble text then thinking then answer",
1332+
content: []anthropicContent{
1333+
{Type: "text", Text: "\n\n"},
1334+
{Type: "thinking", Text: ""},
1335+
{Type: "text", Text: "The capital of France is Paris."},
1336+
},
1337+
expectedText: "The capital of France is Paris.",
12901338
},
12911339
}
12921340

1293-
result := convertAnthropicResponseToResponses(resp, "claude-opus-4-6")
1341+
for _, tt := range tests {
1342+
t.Run(tt.name, func(t *testing.T) {
1343+
resp := &anthropicResponse{
1344+
ID: "msg_789",
1345+
Type: "message",
1346+
Role: "assistant",
1347+
Model: "claude-opus-4-6",
1348+
Content: tt.content,
1349+
StopReason: "end_turn",
1350+
Usage: anthropicUsage{InputTokens: 20, OutputTokens: 50},
1351+
}
12941352

1295-
if len(result.Output) != 1 {
1296-
t.Fatalf("len(Output) = %d, want 1", len(result.Output))
1297-
}
1298-
if result.Output[0].Content[0].Text != "The capital of France is Paris." {
1299-
t.Errorf("expected text content, got %q", result.Output[0].Content[0].Text)
1300-
}
1301-
if result.Usage.OutputTokens != 50 {
1302-
t.Errorf("OutputTokens = %d, want 50", result.Usage.OutputTokens)
1353+
result := convertAnthropicResponseToResponses(resp, "claude-opus-4-6")
1354+
1355+
if len(result.Output) != 1 {
1356+
t.Fatalf("len(Output) = %d, want 1", len(result.Output))
1357+
}
1358+
if result.Output[0].Content[0].Text != tt.expectedText {
1359+
t.Errorf("expected %q, got %q", tt.expectedText, result.Output[0].Content[0].Text)
1360+
}
1361+
if result.Usage.OutputTokens != 50 {
1362+
t.Errorf("OutputTokens = %d, want 50", result.Usage.OutputTokens)
1363+
}
1364+
})
13031365
}
13041366
}
13051367

0 commit comments

Comments
 (0)