Skip to content

Commit 8dccf86

Browse files
committed
fix: support thinking anthropic models that return json tool calls
1 parent 97997b9 commit 8dccf86

File tree

1 file changed

+32
-2
lines changed

1 file changed

+32
-2
lines changed

runner/codegen/ai-sdk/anthropic_thinking_patch.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,43 @@ export const anthropicThinkingWithStructuredResponseMiddleware: LanguageModelV3M
3737
wrapGenerate: async ({doGenerate}) => {
3838
const result = await doGenerate();
3939

40-
// Extract the JSON tool call (conforming to the schema) and return it as text response.
40+
// Anthropic's Thinking mode cannot be used with forced tool calls. To work around this,
41+
// we use `toolChoice: 'auto'` in the `transformParams` above. However, this causes
42+
// the model to return a `finishReason` of `tool-calls` instead of `stop`.
43+
//
44+
// The Vercel AI SDK's high-level `generateText` (and `generateObject`) logic is strict:
45+
// it only attempts to parse structured output if the `finishReason` is exactly `stop`.
46+
// If it sees `tool-calls`, it assumes the conversation is ongoing (waiting for tool
47+
// execution) and returns an empty output, which triggers `AI_NoOutputGeneratedError`.
48+
//
49+
// This fixes this by:
50+
// 1. Finding the optional 'json' tool call.
51+
// 2. Extracting its raw JSON input and injecting it as a standard text response.
52+
// 3. Manually overriding the `finishReason` to `stop`.
53+
// 4. Removing the tool call metadata so the SDK doesn't expect a tool result.
54+
const newContent: typeof result.content = [];
55+
let jsonToolCallFound = false;
56+
4157
for (const r of result.content) {
4258
if (r.type === 'tool-call' && r.toolName === 'json') {
43-
result.content.push({type: 'text', text: r.input});
59+
newContent.push({type: 'text', text: r.input});
60+
jsonToolCallFound = true;
61+
} else {
62+
newContent.push(r);
4463
}
4564
}
4665

66+
if (jsonToolCallFound) {
67+
result.content = newContent;
68+
69+
// We override the finish reason to 'stop' to allow the AI SDK to parse the
70+
// text content as a structured object.
71+
result.finishReason = {
72+
unified: 'stop',
73+
raw: result.finishReason.raw,
74+
};
75+
}
76+
4777
return result;
4878
},
4979
};

0 commit comments

Comments
 (0)