Description
When toolChoice: { type: 'none' } is set, the @ai-sdk/anthropic provider strips both tools and tool_choice from the API request. This causes the Anthropic API to return an empty content: [] response when the conversation history contains tool_use/tool_result blocks.
Reproduction
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool } from 'ai';
import { z } from 'zod';
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools: {
search: tool({
description: 'Search for information',
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => `Results for "${query}"`,
}),
},
toolChoice: 'auto',
maxSteps: 3,
prompt: 'Search for "climate change" and summarize the results',
// On the last step, set toolChoice to 'none' to force a text response
// This is a common pattern for agentic loops that need to guarantee
// the final step produces text rather than another tool call
});
// The final step will have empty text because the provider strips tools
// from the request, and the Anthropic API returns content: [] when it
// sees tool_use/tool_result blocks in history but no tools parameter
A more direct reproduction:
import { anthropic } from '@ai-sdk/anthropic';
const model = anthropic('claude-sonnet-4-20250514');
// Simulate a multi-step conversation where step 1 used tools
const result = await model.doGenerate({
inputFormat: 'messages',
mode: {
type: 'regular',
tools: [
{
type: 'function',
name: 'search',
description: 'Search for info',
parameters: { type: 'object', properties: { query: { type: 'string' } } },
},
],
toolChoice: { type: 'none' },
},
prompt: [
{ role: 'user', content: [{ type: 'text', text: 'Search for climate change' }] },
{
role: 'assistant',
content: [
{ type: 'tool-call', toolCallId: 'tc1', toolName: 'search', args: { query: 'climate change' } },
],
},
{
role: 'tool',
content: [{ type: 'tool-result', toolCallId: 'tc1', result: 'Some results' }],
},
{ role: 'user', content: [{ type: 'text', text: 'Now summarize what you found' }] },
],
});
console.log(result.text); // '' (empty string)
console.log(result.finishReason); // 'stop'
// The API returned content: [] with ~8 output tokens (just stop tokens)
Root Cause
In the Anthropic provider's prepareTools function:
case "none":
return { tools: void 0, toolChoice: void 0, toolWarnings, betas };
When toolChoice is 'none', the provider sets both tools and toolChoice to undefined, which removes them from the API request entirely. The Anthropic API then receives:
- Message history with
tool_use and tool_result blocks
- No
tools parameter
- No
tool_choice parameter
This causes the API to return content: [] (empty content array) with stop_reason: "end_turn".
Expected Behavior
When toolChoice is 'none', the provider should send tool_choice: { type: "none" } along with the tools array to the Anthropic API. This tells the model "you have these tools but you must not use them" — producing a normal text response.
The Anthropic API has supported tool_choice: { type: "none" } since the 2025-04-15 API version.
Suggested Fix
case "none":
return { tools, toolChoice: { type: "none" }, toolWarnings, betas };
Keep the tools array and send tool_choice: { type: "none" } instead of stripping both.
Affected Versions
Tested and confirmed in:
@ai-sdk/anthropic@2.0.45
@ai-sdk/anthropic@2.0.60
@ai-sdk/anthropic@3.0.39 (latest)
Workaround
Convert tool_use/tool_result messages to plain text before setting toolChoice: 'none', so the API request doesn't contain tool blocks when tools are stripped:
// Convert tool call/result messages to text descriptions
const cleanedMessages = messages.map(msg => {
// ... replace tool-call/tool-result parts with text descriptions
});
// Now safe to remove tools since history has no tool blocks
Description
When
toolChoice: { type: 'none' }is set, the@ai-sdk/anthropicprovider strips bothtoolsandtool_choicefrom the API request. This causes the Anthropic API to return an emptycontent: []response when the conversation history containstool_use/tool_resultblocks.Reproduction
A more direct reproduction:
Root Cause
In the Anthropic provider's
prepareToolsfunction:When
toolChoiceis'none', the provider sets bothtoolsandtoolChoicetoundefined, which removes them from the API request entirely. The Anthropic API then receives:tool_useandtool_resultblockstoolsparametertool_choiceparameterThis causes the API to return
content: [](empty content array) withstop_reason: "end_turn".Expected Behavior
When
toolChoiceis'none', the provider should sendtool_choice: { type: "none" }along with thetoolsarray to the Anthropic API. This tells the model "you have these tools but you must not use them" — producing a normal text response.The Anthropic API has supported
tool_choice: { type: "none" }since the2025-04-15API version.Suggested Fix
Keep the
toolsarray and sendtool_choice: { type: "none" }instead of stripping both.Affected Versions
Tested and confirmed in:
@ai-sdk/anthropic@2.0.45@ai-sdk/anthropic@2.0.60@ai-sdk/anthropic@3.0.39(latest)Workaround
Convert
tool_use/tool_resultmessages to plain text before settingtoolChoice: 'none', so the API request doesn't contain tool blocks when tools are stripped: