Skip to content

fix(anthropic): handle thinking response blocks - #1976

Open
ShiroKSH wants to merge 2 commits into
dottxt-ai:mainfrom
ShiroKSH:fix/anthropic-thinking-response
Open

fix(anthropic): handle thinking response blocks#1976
ShiroKSH wants to merge 2 commits into
dottxt-ai:mainfrom
ShiroKSH:fix/anthropic-thinking-response

Conversation

@ShiroKSH

@ShiroKSH ShiroKSH commented Aug 1, 2026

Copy link
Copy Markdown

Summary

  • Read the first text block instead of assuming it is content[0].
  • Return a clear error when an Anthropic response has no text content.

This preserves ordinary responses and supports extended-thinking replies whose first block is reasoning.

Tests

  • uv run --with anthropic --with pytest pytest tests/models/test_anthropic.py -m 'not api_call' -q
  • pre-commit run ruff --files src/outlines/models/anthropic.py tests/models/test_anthropic.py
  • mypy --allow-redefinition src/outlines/models/anthropic.py tests/models/test_anthropic.py

@ErenAta16 ErenAta16 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is right and the problem is broader than the title suggests, which is an argument for merging it rather than against.

completion.content[0].text assumes the first block is text. Thinking is the case that prompted this, but the SDK now defines a lot of block types that can legitimately lead a response:

anthropic 0.117.0, types ending in "Block": 29 of them, including
  ThinkingBlock, RedactedThinkingBlock, ToolUseBlock, ServerToolUseBlock,
  WebSearchToolResultBlock, CodeExecutionResultBlock, DocumentBlock, ...

Any of those in position zero produces AttributeError: 'ThinkingBlock' object has no attribute 'text' today. Scanning for the first type == "text" block handles the whole family rather than special-casing thinking, so it will not need revisiting the next time a server-side tool block type is added.

Raising rather than returning "" when there is no text block is the right call for a generation library. An empty string would flow into whatever the caller does next and fail somewhere less obvious, and a response with no text block genuinely is not a completion.

One thing worth a look before merge: anthropic.types also exports ParsedTextBlock. If that is what comes back on the structured-output path and its discriminator is not literally "text", this loop would skip it and raise on a response that does contain text. I could not confirm the discriminator value without constructing one, since these models have no default on the type field, so it may be fine. Worth a one-line check given the loop is now the only way text is found.

On the test, using real ThinkingBlock and TextBlock instances rather than mocks is the right choice here. A MagicMock would answer to .type and .text for anything and would pass regardless of whether the loop worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants