ruby_llm 2.0.0.rc1 → 2.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +0 -2
- data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +1 -3
- data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +1 -1
- data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +1 -0
- data/lib/ruby_llm/active_record/attachment_helpers.rb +7 -1
- data/lib/ruby_llm/active_record/chat_methods.rb +5 -0
- data/lib/ruby_llm/models.json +2004 -395
- data/lib/ruby_llm/protocols/anthropic/chat.rb +18 -10
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +4 -3
- data/lib/ruby_llm/protocols/anthropic.rb +3 -2
- data/lib/ruby_llm/protocols/converse/chat.rb +14 -4
- data/lib/ruby_llm/protocols/converse/streaming.rb +8 -0
- data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
- data/lib/ruby_llm/providers/openrouter/chat.rb +1 -1
- data/lib/ruby_llm/version.rb +1 -1
- metadata +3 -2
|
@@ -18,6 +18,7 @@ module RubyLLM
|
|
|
18
18
|
COMPACTION_BETA = 'compact-2026-01-12'
|
|
19
19
|
COMPACTION_EDIT_TYPE = 'compact_20260112'
|
|
20
20
|
COUNT_TOKENS_KEYS = %i[model messages system tools tool_choice thinking].freeze
|
|
21
|
+
THINKING_BLOCK_TYPES = %w[thinking redacted_thinking].freeze
|
|
21
22
|
|
|
22
23
|
module_function
|
|
23
24
|
|
|
@@ -303,6 +304,11 @@ module RubyLLM
|
|
|
303
304
|
thinking_block&.dig('signature') || thinking_block&.dig('data')
|
|
304
305
|
end
|
|
305
306
|
|
|
307
|
+
def parse_thinking_blocks(blocks)
|
|
308
|
+
thinking = blocks.select { |block| THINKING_BLOCK_TYPES.include?(block['type']) }
|
|
309
|
+
{ 'anthropic' => thinking } unless thinking.empty?
|
|
310
|
+
end
|
|
311
|
+
|
|
306
312
|
def build_message(data, content:, citations:, thinking:, thinking_signature:, tool_use_blocks:, raw:,
|
|
307
313
|
server_tool_calls: [], raw_content: nil)
|
|
308
314
|
usage = aggregate_usage(data['usage'])
|
|
@@ -316,6 +322,7 @@ module RubyLLM
|
|
|
316
322
|
content: content,
|
|
317
323
|
citations: citations,
|
|
318
324
|
thinking: Thinking.build(text: thinking, signature: thinking_signature),
|
|
325
|
+
raw_reasoning: parse_thinking_blocks(data['content'] || []),
|
|
319
326
|
tool_calls: Tools.parse_tool_calls(tool_use_blocks),
|
|
320
327
|
server_tool_calls: server_tool_calls,
|
|
321
328
|
raw_content: raw_content,
|
|
@@ -357,12 +364,7 @@ module RubyLLM
|
|
|
357
364
|
end
|
|
358
365
|
|
|
359
366
|
def format_basic_message_with_thinking(msg, citations: false, caching: nil)
|
|
360
|
-
content_blocks = []
|
|
361
|
-
|
|
362
|
-
if msg.role == :assistant
|
|
363
|
-
thinking_block = build_thinking_block(msg.thinking)
|
|
364
|
-
content_blocks << thinking_block if thinking_block
|
|
365
|
-
end
|
|
367
|
+
content_blocks = msg.role == :assistant ? format_thinking_blocks(msg) : []
|
|
366
368
|
|
|
367
369
|
append_formatted_content(content_blocks, msg, citations: citations)
|
|
368
370
|
inject_cache_control(content_blocks, caching:) if cache_boundary?(msg, caching:)
|
|
@@ -374,7 +376,7 @@ module RubyLLM
|
|
|
374
376
|
end
|
|
375
377
|
|
|
376
378
|
def format_tool_call_with_thinking(msg, caching: nil)
|
|
377
|
-
content_blocks =
|
|
379
|
+
content_blocks = prepend_thinking_blocks([], msg)
|
|
378
380
|
append_formatted_content(content_blocks, msg) unless msg.content.nil? || msg.content.empty?
|
|
379
381
|
|
|
380
382
|
msg.tool_calls.each_value do |tool_call|
|
|
@@ -393,13 +395,19 @@ module RubyLLM
|
|
|
393
395
|
}
|
|
394
396
|
end
|
|
395
397
|
|
|
396
|
-
def
|
|
397
|
-
|
|
398
|
-
content_blocks.unshift(thinking_block) if thinking_block
|
|
398
|
+
def prepend_thinking_blocks(content_blocks, msg)
|
|
399
|
+
content_blocks.unshift(*format_thinking_blocks(msg))
|
|
399
400
|
|
|
400
401
|
content_blocks
|
|
401
402
|
end
|
|
402
403
|
|
|
404
|
+
def format_thinking_blocks(msg)
|
|
405
|
+
blocks = msg.raw_reasoning['anthropic'] if msg.raw_reasoning.is_a?(Hash)
|
|
406
|
+
return Support::Utils.deep_dup(blocks) if blocks
|
|
407
|
+
|
|
408
|
+
[build_thinking_block(msg.thinking)].compact
|
|
409
|
+
end
|
|
410
|
+
|
|
403
411
|
def build_thinking_block(thinking)
|
|
404
412
|
return nil unless thinking
|
|
405
413
|
|
|
@@ -108,12 +108,13 @@ module RubyLLM
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def stream_end_fields(data)
|
|
111
|
-
return {} unless data['type'] == 'message_stop'
|
|
111
|
+
return {} unless data['type'] == 'message_stop'
|
|
112
112
|
|
|
113
|
-
blocks = @stream_blocks.sort.map { |_index, block| block }
|
|
113
|
+
blocks = (@stream_blocks || {}).sort.map { |_index, block| block }
|
|
114
114
|
{
|
|
115
115
|
server_tool_calls: extract_server_tool_calls(blocks),
|
|
116
|
-
raw_content: blocks
|
|
116
|
+
raw_content: @saw_server_block ? blocks : nil,
|
|
117
|
+
raw_reasoning: parse_thinking_blocks(blocks)
|
|
117
118
|
}
|
|
118
119
|
end
|
|
119
120
|
|
|
@@ -87,8 +87,9 @@ module RubyLLM
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def merge_raw_content(segments)
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
return unless segments.any?(&:raw_content)
|
|
91
|
+
|
|
92
|
+
segments.flat_map { |segment| segment.raw_content || format_message(segment)[:content] }
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
def merge_tool_calls(segments)
|
|
@@ -117,6 +117,7 @@ module RubyLLM
|
|
|
117
117
|
content: text_content,
|
|
118
118
|
citations: citations,
|
|
119
119
|
thinking: Thinking.build(text: thinking_text, signature: thinking_signature),
|
|
120
|
+
raw_reasoning: parse_thinking_blocks(content_blocks),
|
|
120
121
|
tool_calls: parse_tool_calls(content_blocks),
|
|
121
122
|
server_tool_calls: extract_server_tool_calls(content_blocks),
|
|
122
123
|
input_tokens: input_tokens(usage),
|
|
@@ -195,10 +196,7 @@ module RubyLLM
|
|
|
195
196
|
end
|
|
196
197
|
|
|
197
198
|
def format_structured_message_content(msg, citations: false)
|
|
198
|
-
blocks = []
|
|
199
|
-
|
|
200
|
-
thinking_block = format_thinking_block(msg.thinking)
|
|
201
|
-
blocks << thinking_block if msg.role == :assistant && thinking_block
|
|
199
|
+
blocks = msg.role == :assistant ? format_thinking_blocks(msg) : []
|
|
202
200
|
|
|
203
201
|
blocks.concat(
|
|
204
202
|
Media.format_content(msg.content, msg.attachments, used_document_names: @used_document_names, citations:)
|
|
@@ -207,6 +205,13 @@ module RubyLLM
|
|
|
207
205
|
blocks
|
|
208
206
|
end
|
|
209
207
|
|
|
208
|
+
def format_thinking_blocks(msg)
|
|
209
|
+
blocks = msg.raw_reasoning['converse'] if msg.raw_reasoning.is_a?(Hash)
|
|
210
|
+
return Support::Utils.deep_dup(blocks) if blocks
|
|
211
|
+
|
|
212
|
+
[format_thinking_block(msg.thinking)].compact
|
|
213
|
+
end
|
|
214
|
+
|
|
210
215
|
def format_tool_result_block(msg)
|
|
211
216
|
{
|
|
212
217
|
toolResult: {
|
|
@@ -614,6 +619,11 @@ module RubyLLM
|
|
|
614
619
|
[text, signature]
|
|
615
620
|
end
|
|
616
621
|
|
|
622
|
+
def parse_thinking_blocks(content_blocks)
|
|
623
|
+
blocks = content_blocks.select { |block| block['reasoningContent'].is_a?(Hash) }
|
|
624
|
+
{ 'converse' => blocks } unless blocks.empty?
|
|
625
|
+
end
|
|
626
|
+
|
|
617
627
|
def parse_reasoning_content_block(block)
|
|
618
628
|
reasoning_content = block['reasoningContent']
|
|
619
629
|
return [nil, nil] unless reasoning_content.is_a?(Hash)
|
|
@@ -18,6 +18,7 @@ module RubyLLM
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def stream_response(payload, additional_headers = {}, &block)
|
|
21
|
+
@thinking_stream = ThinkingStream.new
|
|
21
22
|
accumulator = RubyLLM::Protocol::StreamAccumulator.new
|
|
22
23
|
decoder = event_stream_decoder
|
|
23
24
|
body = JSON.generate(payload)
|
|
@@ -195,6 +196,7 @@ module RubyLLM
|
|
|
195
196
|
text: extract_thinking_delta(event),
|
|
196
197
|
signature: extract_thinking_signature(event)
|
|
197
198
|
),
|
|
199
|
+
raw_reasoning: streamed_thinking_blocks(event),
|
|
198
200
|
tool_calls: extract_tool_calls(event),
|
|
199
201
|
server_tool_calls: extract_server_tool_call_events(event),
|
|
200
202
|
input_tokens: extract_input_tokens(metadata_usage, usage),
|
|
@@ -206,6 +208,12 @@ module RubyLLM
|
|
|
206
208
|
)
|
|
207
209
|
end
|
|
208
210
|
|
|
211
|
+
def streamed_thinking_blocks(event)
|
|
212
|
+
@thinking_stream ||= ThinkingStream.new
|
|
213
|
+
@thinking_stream.add(event)
|
|
214
|
+
@thinking_stream.raw_reasoning if event.key?('messageStop') || event.key?('stopReason')
|
|
215
|
+
end
|
|
216
|
+
|
|
209
217
|
def extract_finish_reason(event)
|
|
210
218
|
normalize_finish_reason(event.dig('messageStop', 'stopReason') || event['stopReason'])
|
|
211
219
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Protocols
|
|
7
|
+
class Converse
|
|
8
|
+
class ThinkingStream # :nodoc: all
|
|
9
|
+
def initialize
|
|
10
|
+
@blocks = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add(event)
|
|
14
|
+
@blocks.clear if event.key?('messageStart')
|
|
15
|
+
part = event['contentBlockStart'] || event['contentBlockDelta'] || event
|
|
16
|
+
index = part['contentBlockIndex']
|
|
17
|
+
return if index.nil?
|
|
18
|
+
|
|
19
|
+
start = part.dig('start', 'reasoningContent')
|
|
20
|
+
@blocks[index] = Support::Utils.deep_dup(start) if start
|
|
21
|
+
delta = part.dig('delta', 'reasoningContent')
|
|
22
|
+
append_delta(index, delta) if delta
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def raw_reasoning
|
|
26
|
+
return if @blocks.empty?
|
|
27
|
+
|
|
28
|
+
{ 'converse' => @blocks.sort.map { |_index, content| { 'reasoningContent' => content } } }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def append_delta(index, delta)
|
|
34
|
+
block = (@blocks[index] ||= {})
|
|
35
|
+
if delta.key?('redactedContent')
|
|
36
|
+
append_redacted(block, delta['redactedContent'])
|
|
37
|
+
else
|
|
38
|
+
text = (block['reasoningText'] ||= { 'text' => '' })
|
|
39
|
+
(delta['reasoningText'] || delta).slice('text', 'signature').each do |key, value|
|
|
40
|
+
text[key] = text[key].to_s + value.to_s
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def append_redacted(block, data)
|
|
46
|
+
block['redactedContent'] = if block['redactedContent']
|
|
47
|
+
Base64.strict_encode64(Base64.decode64(block['redactedContent']) +
|
|
48
|
+
Base64.decode64(data))
|
|
49
|
+
else
|
|
50
|
+
data
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -80,7 +80,7 @@ module RubyLLM
|
|
|
80
80
|
|
|
81
81
|
def format_thinking(msg)
|
|
82
82
|
return {} unless msg.role == :assistant
|
|
83
|
-
return { reasoning_details: msg.raw_reasoning } if msg.raw_reasoning
|
|
83
|
+
return { reasoning_details: msg.raw_reasoning } if msg.raw_reasoning.is_a?(Array)
|
|
84
84
|
|
|
85
85
|
thinking = msg.thinking
|
|
86
86
|
return {} unless thinking
|
data/lib/ruby_llm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.0.
|
|
4
|
+
version: 2.0.0.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
@@ -379,6 +379,7 @@ files:
|
|
|
379
379
|
- lib/ruby_llm/protocols/converse/chat.rb
|
|
380
380
|
- lib/ruby_llm/protocols/converse/media.rb
|
|
381
381
|
- lib/ruby_llm/protocols/converse/streaming.rb
|
|
382
|
+
- lib/ruby_llm/protocols/converse/thinking_stream.rb
|
|
382
383
|
- lib/ruby_llm/protocols/deepgram.rb
|
|
383
384
|
- lib/ruby_llm/protocols/deepgram/models.rb
|
|
384
385
|
- lib/ruby_llm/protocols/deepgram/speech.rb
|
|
@@ -611,7 +612,7 @@ metadata:
|
|
|
611
612
|
funding_uri: https://github.com/sponsors/crmne
|
|
612
613
|
rubygems_mfa_required: 'true'
|
|
613
614
|
post_install_message: |
|
|
614
|
-
RubyLLM 2.0
|
|
615
|
+
RubyLLM 2.0.0.rc2
|
|
615
616
|
|
|
616
617
|
2.0 renames several APIs and changes what message content returns. Coming
|
|
617
618
|
from 1.x? Read the upgrade guide before you boot:
|