From 8bfb59ad56e043552e173754534855ed453df9fe Mon Sep 17 00:00:00 2001 From: ElliotJLT Date: Thu, 26 Mar 2026 16:21:23 +0000 Subject: [PATCH] Add extended thinking examples for multi-turn and tool use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new examples demonstrating common thinking patterns that are currently missing from the examples directory (requested in #85): - thinking_multi_turn.rb: Shows how to carry thinking blocks forward across conversation turns, a common gotcha where the API rejects requests that strip thinking blocks from prior assistant messages. - thinking_with_tools.rb: Shows extended thinking combined with tool use, including the three-step flow of request → tool execution → tool result with all content blocks preserved. --- examples/thinking_multi_turn.rb | 60 +++++++++++++++++++ examples/thinking_with_tools.rb | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 examples/thinking_multi_turn.rb create mode 100644 examples/thinking_with_tools.rb diff --git a/examples/thinking_multi_turn.rb b/examples/thinking_multi_turn.rb new file mode 100644 index 000000000..e546b15e2 --- /dev/null +++ b/examples/thinking_multi_turn.rb @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# typed: strong + +require_relative "../lib/anthropic" + +# gets API credentials from environment variable `ANTHROPIC_API_KEY` +anthropic = Anthropic::Client.new + +# --- Turn 1: Ask an initial question with extended thinking enabled --- +puts("--- Turn 1: Initial question ---") + +first_response = anthropic.messages.create( + model: "claude-sonnet-4-5-20250929", + max_tokens: 8000, + thinking: {type: :enabled, budget_tokens: 5000}, + messages: [{role: :user, content: "What are the three largest prime numbers below 1000?"}] +) + +first_response.content.each do |block| + case block + when Anthropic::ThinkingBlock + puts("Thinking: #{block.thinking[0..200]}...") + when Anthropic::TextBlock + puts("Response: #{block.text}") + end +end + +# --- Turn 2: Follow up, carrying forward thinking blocks --- +# +# IMPORTANT: When using extended thinking in multi-turn conversations, you must +# include ALL content blocks from the assistant's previous response — including +# `thinking` and `redacted_thinking` blocks — in the messages array. The API +# will reject requests that strip thinking blocks from prior assistant turns. +# +# The response's `.content` already contains the correctly typed blocks +# (ThinkingBlock, RedactedThinkingBlock, TextBlock), so you can pass them +# directly as the assistant message content. +puts("\n--- Turn 2: Follow-up question (thinking blocks carried forward) ---") + +second_response = anthropic.messages.create( + model: "claude-sonnet-4-5-20250929", + max_tokens: 8000, + thinking: {type: :enabled, budget_tokens: 5000}, + messages: [ + {role: :user, content: "What are the three largest prime numbers below 1000?"}, + # Pass ALL content blocks (thinking + text) from the first response + {role: :assistant, content: first_response.content}, + {role: :user, content: "Now multiply the largest of those primes by 2. Is the result also prime?"} + ] +) + +second_response.content.each do |block| + case block + when Anthropic::ThinkingBlock + puts("Thinking: #{block.thinking[0..200]}...") + when Anthropic::TextBlock + puts("Response: #{block.text}") + end +end diff --git a/examples/thinking_with_tools.rb b/examples/thinking_with_tools.rb new file mode 100644 index 000000000..00a9e7d9c --- /dev/null +++ b/examples/thinking_with_tools.rb @@ -0,0 +1,103 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# typed: false + +require_relative "../lib/anthropic" + +# gets API credentials from environment variable `ANTHROPIC_API_KEY` +client = Anthropic::Client.new + +# Define a simple calculator tool using the SDK's BaseModel pattern. +class CalculatorInput < Anthropic::BaseModel + required :expression, String, doc: "A mathematical expression to evaluate, e.g. '2 + 3 * 4'" + + doc "Evaluate a mathematical expression and return the result" +end + +# --- Step 1: Send a request with both extended thinking and tools --- +puts("--- Step 1: Sending request with thinking + tools ---") + +user_message = { + role: "user", + content: "I have 147 apples and want to split them evenly among 7 baskets. " \ + "How many apples go in each basket, and how many are left over?" +} + +response = client.messages.create( + model: "claude-sonnet-4-5-20250929", + max_tokens: 8000, + thinking: {type: :enabled, budget_tokens: 5000}, + messages: [user_message], + tools: [CalculatorInput] +) + +# Display thinking and identify tool use +response.content.each do |block| + case block + when Anthropic::ThinkingBlock + puts("Thinking: #{block.thinking[0..200]}...") + when Anthropic::TextBlock + puts("Text: #{block.text}") + when Anthropic::Models::ToolUseBlock + puts("Tool call: #{block.name}(#{block.input.inspect})") + end +end + +# --- Step 2: Handle tool use if requested --- +if response.stop_reason == :tool_use + tool_use = response.content.grep(Anthropic::Models::ToolUseBlock).first + raise "Tool use block not found" unless tool_use + + # Simulate executing the calculator tool. + # In a real application, you would evaluate the expression or call an external service. + puts("\n--- Step 2: Executing tool '#{tool_use.name}' ---") + expression = tool_use.input["expression"] + puts("Expression: #{expression}") + + # rubocop:disable Security/Eval + result = eval(expression).to_s + # rubocop:enable Security/Eval + puts("Result: #{result}") + + # --- Step 3: Send the tool result back, including all prior content blocks --- + # + # When combining thinking with tools, the assistant message must include ALL + # content blocks from the response (thinking blocks + tool_use blocks). + puts("\n--- Step 3: Sending tool result back ---") + + final_response = client.messages.create( + model: "claude-sonnet-4-5-20250929", + max_tokens: 8000, + thinking: {type: :enabled, budget_tokens: 5000}, + messages: [ + user_message, + # Include the full assistant response (thinking + tool_use blocks) + {role: :assistant, content: response.content}, + # Provide the tool result + { + role: :user, + content: [ + { + type: :tool_result, + tool_use_id: tool_use.id, + content: [{type: :text, text: result}] + } + ] + } + ], + tools: [CalculatorInput] + ) + + puts("\n--- Final response ---") + final_response.content.each do |block| + case block + when Anthropic::ThinkingBlock + puts("Thinking: #{block.thinking[0..200]}...") + when Anthropic::TextBlock + puts("Response: #{block.text}") + end + end +else + # Model answered directly without using tools + puts("\n--- Model responded without tool use ---") +end