module RubyLLM::Providers::Anthropic::Chat
Public Instance Methods
# File lib/ruby_llm/providers/anthropic/chat.rb, line 271 def adaptive_thinking_payload(effort) { thinking: { type: 'adaptive' }, output_config: { effort: effort } } end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 61 def add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:, schema: nil) # rubocop:disable Metrics/ParameterLists if tools.any? payload[:tools] = tools.values.map { |t| Tools.function_for(t) } unless tool_prefs[:choice].nil? && tool_prefs[:calls].nil? payload[:tool_choice] = Tools.build_tool_choice(tool_prefs) end end payload[:system] = system_content unless system_content.empty? payload[:temperature] = temperature unless temperature.nil? payload[:output_config] = payload.fetch(:output_config, {}).merge(build_output_config(schema)) if schema end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 233 def add_thinking_fields(payload, thinking, model) thinking_payload = build_thinking_payload(thinking, model) return unless thinking_payload payload[:thinking] = thinking_payload[:thinking] if thinking_payload[:thinking] return unless thinking_payload[:output_config] payload[:output_config] = payload.fetch(:output_config, {}).merge(thinking_payload[:output_config]) end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 217 def append_formatted_content(content_blocks, content) formatted_content = Media.format_content(content) if formatted_content.is_a?(Array) content_blocks.concat(formatted_content) else content_blocks << formatted_content end end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 48 def build_base_payload(chat_messages, model, stream, thinking) payload = { model: model.id, messages: chat_messages.map { |msg| format_message(msg, thinking: thinking) }, stream: stream, max_tokens: model.max_tokens || 4096 } add_thinking_fields(payload, thinking, model) payload end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 109 def build_message(data, content, thinking, thinking_signature, tool_use_blocks, response) # rubocop:disable Metrics/ParameterLists usage = data['usage'] || {} cached_tokens = usage['cache_read_input_tokens'] cache_creation_tokens = usage['cache_creation_input_tokens'] if cache_creation_tokens.nil? && usage['cache_creation'].is_a?(Hash) cache_creation_tokens = usage['cache_creation'].values.compact.sum end thinking_tokens = usage.dig('output_tokens_details', 'thinking_tokens') || usage.dig('output_tokens_details', 'reasoning_tokens') || usage['thinking_tokens'] || usage['reasoning_tokens'] Message.new( role: :assistant, content: content, thinking: Thinking.build(text: thinking, signature: thinking_signature), tool_calls: Tools.parse_tool_calls(tool_use_blocks), input_tokens: usage['input_tokens'], output_tokens: usage['output_tokens'], cached_tokens: cached_tokens, cache_creation_tokens: cache_creation_tokens, thinking_tokens: thinking_tokens, model_id: data['model'], raw: response ) end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 73 def build_output_config(schema) normalized = RubyLLM::Utils.deep_dup(schema[:schema]) normalized.delete(:strict) normalized.delete('strict') { format: { type: 'json_schema', schema: normalized } } end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 31 def build_system_content(system_messages) return [] if system_messages.empty? # Anthropic's `system` parameter accepts an array of text content blocks # (each optionally with cache_control); each :system message becomes its # own block in the resulting array. system_messages.flat_map do |msg| content = msg.content if content.is_a?(RubyLLM::Content::Raw) content.value else Media.format_content(content) end end end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 200 def build_thinking_block(thinking) return nil unless thinking if thinking.text { type: 'thinking', thinking: thinking.text, signature: thinking.signature }.compact elsif thinking.signature { type: 'redacted_thinking', data: thinking.signature } end end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 243 def build_thinking_payload(thinking, model) return nil unless thinking&.enabled? effort = resolve_effort(thinking) return nil if effort == 'none' budget = resolve_budget(thinking) if budget return enabled_thinking_payload(budget) if model.reasoning_option('budget_tokens') raise ArgumentError, "Anthropic thinking budget is not supported for #{model.id}" end raise ArgumentError, 'Anthropic adaptive thinking requires an effort' if effort.nil? return adaptive_thinking_payload(effort) if model.reasoning_option('effort') raise ArgumentError, "Anthropic thinking effort is not supported for #{model.id}" end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 10 def completion_url 'v1/messages' end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 226 def convert_role(role) case role when :tool, :user then 'user' else 'assistant' end end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 262 def enabled_thinking_payload(budget) { thinking: { type: 'enabled', budget_tokens: budget } } end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 92 def extract_text_content(blocks) text_blocks = blocks.select { |c| c['type'] == 'text' } text_blocks.map { |c| c['text'] }.join end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 97 def extract_thinking_content(blocks) thinking_blocks = blocks.select { |c| c['type'] == 'thinking' } thoughts = thinking_blocks.map { |c| c['thinking'] || c['text'] }.join thoughts.empty? ? nil : thoughts end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 103 def extract_thinking_signature(blocks) thinking_block = blocks.find { |c| c['type'] == 'thinking' } || blocks.find { |c| c['type'] == 'redacted_thinking' } thinking_block&.dig('signature') || thinking_block&.dig('data') end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 148 def format_basic_message_with_thinking(msg, thinking_enabled) content_blocks = [] if msg.role == :assistant && thinking_enabled thinking_block = build_thinking_block(msg.thinking) content_blocks << thinking_block if thinking_block end append_formatted_content(content_blocks, msg.content) { role: convert_role(msg.role), content: content_blocks } end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 136 def format_message(msg, thinking: nil) thinking_enabled = thinking&.enabled? if msg.tool_call? format_tool_call_with_thinking(msg, thinking_enabled) elsif msg.tool_result? Tools.format_tool_result(msg) else format_basic_message_with_thinking(msg, thinking_enabled) end end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 164 def format_tool_call_with_thinking(msg, thinking_enabled) if msg.content.is_a?(RubyLLM::Content::Raw) content_blocks = msg.content.value content_blocks = [content_blocks] unless content_blocks.is_a?(Array) content_blocks = prepend_thinking_block(content_blocks, msg, thinking_enabled) return { role: 'assistant', content: content_blocks } end content_blocks = prepend_thinking_block([], msg, thinking_enabled) append_formatted_content(content_blocks, msg.content) unless msg.content.nil? || msg.content.empty? msg.tool_calls.each_value do |tool_call| content_blocks << { type: 'tool_use', id: tool_call.id, name: tool_call.name, input: tool_call.arguments } end { role: 'assistant', content: content_blocks } end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 80 def parse_completion_response(response) data = response.body content_blocks = data['content'] || [] text_content = extract_text_content(content_blocks) thinking_content = extract_thinking_content(content_blocks) thinking_signature = extract_thinking_signature(content_blocks) tool_use_blocks = Tools.find_tool_uses(content_blocks) build_message(data, text_content, thinking_content, thinking_signature, tool_use_blocks, response) end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 191 def prepend_thinking_block(content_blocks, msg, thinking_enabled) return content_blocks unless thinking_enabled thinking_block = build_thinking_block(msg.thinking) content_blocks.unshift(thinking_block) if thinking_block content_blocks end
# File lib/ruby_llm/providers/anthropic/chat.rb, line 15 def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) tool_prefs ||= {} system_messages, chat_messages = separate_messages(messages) system_content = build_system_content(system_messages) build_base_payload(chat_messages, model, stream, thinking).tap do |payload| add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:, schema:) end end
rubocop:disable Metrics/ParameterLists
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 284 def resolve_budget(thinking) budget = thinking.respond_to?(:budget) ? thinking.budget : thinking budget.is_a?(Integer) ? budget : nil end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 278 def resolve_effort(thinking) effort = thinking.respond_to?(:effort) ? thinking.effort : nil effort = effort.to_s if effort effort.nil? || effort.empty? ? nil : effort end
Source
# File lib/ruby_llm/providers/anthropic/chat.rb, line 27 def separate_messages(messages) messages.partition { |msg| msg.role == :system } end
rubocop:enable Metrics/ParameterLists