[PR #14825] [MERGED] anthropic: close thinking block before tool_use when no text in between #77153

Closed
opened 2026-05-05 09:50:41 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/ollama/ollama/pull/14825
Author: @shivamtiwari3
Created: 3/13/2026
Status: Merged
Merged: 3/13/2026
Merged by: @ParthSareen

Base: mainHead: fix/anthropic-thinking-tool-call-index-reuse


📝 Commits (2)

  • 5338084 fix(anthropic): close thinking block before tool_use when no text in between
  • 66ed0c1 fix lint

📊 Changes

2 files changed (+114 additions, -0 deletions)

View changed files

📝 anthropic/anthropic.go (+13 -0)
📝 anthropic/anthropic_test.go (+101 -0)

📄 Description

Summary

Fixes #14816.

When a model emits a thinking block followed directly by a tool_use block (no text block in between), the StreamConverter.Process() method was reusing contentIndex=0 for the tool_use content_block_start. This caused downstream clients to receive content_block_start events with duplicate indices, leading to "Content block not found" errors.


Root Cause

StreamConverter tracks thinkingStarted, thinkingDone, textStarted, and contentIndex to sequence content blocks. The existing logic correctly closes a thinking block when text content arrives:

// anthropic.go lines 810–820 (before fix)
if r.Message.Content != "" {
    if c.thinkingStarted && !c.thinkingDone {
        c.thinkingDone = true
        // emit content_block_stop at contentIndex
        c.contentIndex++
    }
    // ... emit text block at new contentIndex
}

However, the tool call loop only checked textStarted before opening a tool_use block:

// anthropic.go line 854 (before fix) — missing thinking check
for _, tc := range r.Message.ToolCalls {
    if c.textStarted {
        // close text block and increment index
    }
    // emit tool_use at contentIndex — WRONG: still 0 if thinking never ended
}

When the model response is thinking + tool_use with no text, thinkingDone was never set and contentIndex was never incremented, so the tool_use content_block_start reused index 0 — the same index as the still-open thinking block.

The bug matches exactly the packet trace in the issue:

content_block_start index=0 type=thinking
content_block_delta index=0 thinking_delta
content_block_start index=0 type=tool_use   ← BUG: reuses index 0
content_block_stop  index=0
content_block_stop  index=1                  ← no matching start for index 1

Solution

In the tool call loop, close any still-open thinking block before opening the tool_use block, mirroring the existing textStarted close logic:

// Close thinking block if still open (thinking → tool_use without text)
if c.thinkingStarted && !c.thinkingDone {
    c.thinkingDone = true
    events = append(events, content_block_stop at contentIndex)
    c.contentIndex++
}

After the fix the event sequence is correct:

content_block_start index=0 type=thinking
content_block_delta index=0 thinking_delta
content_block_stop  index=0   ← thinking closed correctly
content_block_start index=1 type=tool_use   ← tool at next index
content_block_delta index=1 input_json_delta
content_block_stop  index=1

Testing

Added TestStreamConverter_ThinkingDirectlyFollowedByToolCall in anthropic/anthropic_test.go that:

  1. Sends a thinking-only chunk (no text, no tool calls)
  2. Follows with a tool-call-only chunk (no text)
  3. Verifies the thinking block is closed at index 0
  4. Verifies the tool_use block opens at index 1
  5. Verifies all delta/stop events use the correct index
go test ./anthropic/... -run TestStreamConverter

All existing tests continue to pass.


Checklist

  • Fixes the root cause (not just a symptom)
  • New test covers the exact failing scenario from the issue
  • All existing tests pass (go test ./anthropic/...)
  • No unrelated changes
  • Code style matches surrounding code

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/ollama/ollama/pull/14825 **Author:** [@shivamtiwari3](https://github.com/shivamtiwari3) **Created:** 3/13/2026 **Status:** ✅ Merged **Merged:** 3/13/2026 **Merged by:** [@ParthSareen](https://github.com/ParthSareen) **Base:** `main` ← **Head:** `fix/anthropic-thinking-tool-call-index-reuse` --- ### 📝 Commits (2) - [`5338084`](https://github.com/ollama/ollama/commit/5338084f2ec89ec05ea8f01d7c6fe09ba71a0e0f) fix(anthropic): close thinking block before tool_use when no text in between - [`66ed0c1`](https://github.com/ollama/ollama/commit/66ed0c1416b270c28d3b251190c353942621aee6) fix lint ### 📊 Changes **2 files changed** (+114 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `anthropic/anthropic.go` (+13 -0) 📝 `anthropic/anthropic_test.go` (+101 -0) </details> ### 📄 Description ## Summary Fixes #14816. When a model emits a `thinking` block followed directly by a `tool_use` block (no `text` block in between), the `StreamConverter.Process()` method was reusing `contentIndex=0` for the `tool_use` `content_block_start`. This caused downstream clients to receive `content_block_start` events with duplicate indices, leading to "Content block not found" errors. --- ## Root Cause `StreamConverter` tracks `thinkingStarted`, `thinkingDone`, `textStarted`, and `contentIndex` to sequence content blocks. The existing logic correctly closes a thinking block when **text content** arrives: ```go // anthropic.go lines 810–820 (before fix) if r.Message.Content != "" { if c.thinkingStarted && !c.thinkingDone { c.thinkingDone = true // emit content_block_stop at contentIndex c.contentIndex++ } // ... emit text block at new contentIndex } ``` However, the tool call loop only checked `textStarted` before opening a `tool_use` block: ```go // anthropic.go line 854 (before fix) — missing thinking check for _, tc := range r.Message.ToolCalls { if c.textStarted { // close text block and increment index } // emit tool_use at contentIndex — WRONG: still 0 if thinking never ended } ``` When the model response is `thinking + tool_use` with no `text`, `thinkingDone` was never set and `contentIndex` was never incremented, so the `tool_use` `content_block_start` reused index 0 — the same index as the still-open thinking block. The bug matches exactly the packet trace in the issue: ``` content_block_start index=0 type=thinking content_block_delta index=0 thinking_delta content_block_start index=0 type=tool_use ← BUG: reuses index 0 content_block_stop index=0 content_block_stop index=1 ← no matching start for index 1 ``` --- ## Solution In the tool call loop, close any still-open thinking block before opening the `tool_use` block, mirroring the existing `textStarted` close logic: ```go // Close thinking block if still open (thinking → tool_use without text) if c.thinkingStarted && !c.thinkingDone { c.thinkingDone = true events = append(events, content_block_stop at contentIndex) c.contentIndex++ } ``` After the fix the event sequence is correct: ``` content_block_start index=0 type=thinking content_block_delta index=0 thinking_delta content_block_stop index=0 ← thinking closed correctly content_block_start index=1 type=tool_use ← tool at next index content_block_delta index=1 input_json_delta content_block_stop index=1 ``` --- ## Testing Added `TestStreamConverter_ThinkingDirectlyFollowedByToolCall` in `anthropic/anthropic_test.go` that: 1. Sends a thinking-only chunk (no text, no tool calls) 2. Follows with a tool-call-only chunk (no text) 3. Verifies the thinking block is closed at index 0 4. Verifies the tool_use block opens at index 1 5. Verifies all delta/stop events use the correct index ``` go test ./anthropic/... -run TestStreamConverter ``` All existing tests continue to pass. --- ## Checklist - [x] Fixes the root cause (not just a symptom) - [x] New test covers the exact failing scenario from the issue - [x] All existing tests pass (`go test ./anthropic/...`) - [x] No unrelated changes - [x] Code style matches surrounding code --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-05 09:50:41 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#77153