Module: RubyLLM::Providers::Anthropic::Chat
- Included in:
- RubyLLM::Providers::Anthropic
- Defined in:
- lib/ruby_llm/providers/anthropic/chat.rb
Overview
Chat methods for the Anthropic API implementation
Class Method Summary collapse
- .add_optional_fields(payload, system_content:, tools:, temperature:) ⇒ Object
- .append_formatted_content(content_blocks, content) ⇒ Object
- .build_base_payload(chat_messages, model, stream, thinking) ⇒ Object
-
.build_message(data, content, thinking, thinking_signature, tool_use_blocks, response) ⇒ Object
rubocop:disable Metrics/ParameterLists.
- .build_system_content(system_messages) ⇒ Object
- .build_thinking_block(thinking) ⇒ Object
- .build_thinking_payload(thinking) ⇒ Object
- .completion_url ⇒ Object
- .convert_role(role) ⇒ Object
- .extract_text_content(blocks) ⇒ Object
- .extract_thinking_content(blocks) ⇒ Object
- .extract_thinking_signature(blocks) ⇒ Object
- .format_basic_message_with_thinking(msg, thinking_enabled) ⇒ Object
- .format_message(msg, thinking: nil) ⇒ Object
- .format_tool_call_with_thinking(msg, thinking_enabled) ⇒ Object
- .parse_completion_response(response) ⇒ Object
- .prepend_thinking_block(content_blocks, msg, thinking_enabled) ⇒ Object
-
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument.
- .resolve_budget(thinking) ⇒ Object
- .separate_messages(messages) ⇒ Object
Class Method Details
.add_optional_fields(payload, system_content:, tools:, temperature:) ⇒ Object
62 63 64 65 66 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 62 def add_optional_fields(payload, system_content:, tools:, temperature:) payload[:tools] = tools.values.map { |t| Tools.function_for(t) } if tools.any? payload[:system] = system_content unless system_content.empty? payload[:temperature] = temperature unless temperature.nil? end |
.append_formatted_content(content_blocks, content) ⇒ Object
205 206 207 208 209 210 211 212 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 205 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 |
.build_base_payload(chat_messages, model, stream, thinking) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 48 def build_base_payload(, model, stream, thinking) payload = { model: model.id, messages: .map { |msg| (msg, thinking: thinking) }, stream: stream, max_tokens: model.max_tokens || 4096 } thinking_payload = build_thinking_payload(thinking) payload[:thinking] = thinking_payload if thinking_payload payload end |
.build_message(data, content, thinking, thinking_signature, tool_use_blocks, response) ⇒ Object
rubocop:disable Metrics/ParameterLists
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 97 def (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 |
.build_system_content(system_messages) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 27 def build_system_content() return [] if .empty? if .length > 1 RubyLLM.logger.warn( "Anthropic's Claude implementation only supports a single system message. " \ 'Multiple system messages will be combined into one.' ) end .flat_map do |msg| content = msg.content if content.is_a?(RubyLLM::Content::Raw) content.value else Media.format_content(content) end end end |
.build_thinking_block(thinking) ⇒ Object
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 188 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 |
.build_thinking_payload(thinking) ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 221 def build_thinking_payload(thinking) return nil unless thinking&.enabled? budget = resolve_budget(thinking) raise ArgumentError, 'Anthropic thinking requires a budget' if budget.nil? { type: 'enabled', budget_tokens: budget } end |
.completion_url ⇒ Object
10 11 12 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 10 def completion_url '/v1/messages' end |
.convert_role(role) ⇒ Object
214 215 216 217 218 219 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 214 def convert_role(role) case role when :tool, :user then 'user' else 'assistant' end end |
.extract_text_content(blocks) ⇒ Object
80 81 82 83 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 80 def extract_text_content(blocks) text_blocks = blocks.select { |c| c['type'] == 'text' } text_blocks.map { |c| c['text'] }.join end |
.extract_thinking_content(blocks) ⇒ Object
85 86 87 88 89 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 85 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 |
.extract_thinking_signature(blocks) ⇒ Object
91 92 93 94 95 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 91 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 |
.format_basic_message_with_thinking(msg, thinking_enabled) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 136 def (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 |
.format_message(msg, thinking: nil) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 124 def (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 (msg, thinking_enabled) end end |
.format_tool_call_with_thinking(msg, thinking_enabled) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 152 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) content_blocks << Media.format_text(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 |
.parse_completion_response(response) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 68 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) (data, text_content, thinking_content, thinking_signature, tool_use_blocks, response) end |
.prepend_thinking_block(content_blocks, msg, thinking_enabled) ⇒ Object
179 180 181 182 183 184 185 186 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 179 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 |
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
14 15 16 17 18 19 20 21 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 14 def render_payload(, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil) # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument , = () system_content = build_system_content() build_base_payload(, model, stream, thinking).tap do |payload| add_optional_fields(payload, system_content:, tools:, temperature:) end end |
.resolve_budget(thinking) ⇒ Object
233 234 235 236 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 233 def resolve_budget(thinking) budget = thinking.respond_to?(:budget) ? thinking.budget : thinking budget.is_a?(Integer) ? budget : nil end |
.separate_messages(messages) ⇒ Object
23 24 25 |
# File 'lib/ruby_llm/providers/anthropic/chat.rb', line 23 def () .partition { |msg| msg.role == :system } end |