ruby_llm 1.3.0rc1 → 1.3.1
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 +13 -9
- data/lib/ruby_llm/active_record/acts_as.rb +67 -148
- data/lib/ruby_llm/aliases.json +178 -42
- data/lib/ruby_llm/attachment.rb +164 -0
- data/lib/ruby_llm/chat.rb +12 -4
- data/lib/ruby_llm/configuration.rb +6 -1
- data/lib/ruby_llm/connection.rb +28 -2
- data/lib/ruby_llm/content.rb +9 -40
- data/lib/ruby_llm/error.rb +1 -0
- data/lib/ruby_llm/image.rb +2 -3
- data/lib/ruby_llm/message.rb +2 -2
- data/lib/ruby_llm/mime_type.rb +67 -0
- data/lib/ruby_llm/model/info.rb +101 -0
- data/lib/ruby_llm/model/modalities.rb +22 -0
- data/lib/ruby_llm/model/pricing.rb +51 -0
- data/lib/ruby_llm/model/pricing_category.rb +48 -0
- data/lib/ruby_llm/model/pricing_tier.rb +34 -0
- data/lib/ruby_llm/model.rb +7 -0
- data/lib/ruby_llm/models.json +2646 -2201
- data/lib/ruby_llm/models.rb +20 -20
- data/lib/ruby_llm/provider.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/media.rb +14 -3
- data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +5 -4
- data/lib/ruby_llm/providers/bedrock/media.rb +7 -4
- data/lib/ruby_llm/providers/bedrock/models.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +3 -3
- data/lib/ruby_llm/providers/gemini/images.rb +3 -2
- data/lib/ruby_llm/providers/gemini/media.rb +12 -24
- data/lib/ruby_llm/providers/gemini/models.rb +1 -1
- data/lib/ruby_llm/providers/ollama/media.rb +8 -4
- data/lib/ruby_llm/providers/openai/capabilities.rb +5 -2
- data/lib/ruby_llm/providers/openai/chat.rb +12 -8
- data/lib/ruby_llm/providers/openai/images.rb +3 -2
- data/lib/ruby_llm/providers/openai/media.rb +18 -8
- data/lib/ruby_llm/providers/openai/models.rb +1 -1
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
- data/lib/ruby_llm/streaming.rb +46 -11
- data/lib/ruby_llm/tool.rb +8 -8
- data/lib/ruby_llm/utils.rb +14 -9
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +1 -1
- data/lib/tasks/aliases.rake +235 -0
- data/lib/tasks/models_docs.rake +13 -7
- data/lib/tasks/release.rake +32 -0
- metadata +40 -25
- data/lib/ruby_llm/attachments/audio.rb +0 -12
- data/lib/ruby_llm/attachments/image.rb +0 -9
- data/lib/ruby_llm/attachments/pdf.rb +0 -9
- data/lib/ruby_llm/attachments.rb +0 -78
- data/lib/ruby_llm/mime_types.rb +0 -713
- data/lib/ruby_llm/model_info.rb +0 -237
- data/lib/tasks/{models.rake → models_update.rake} +13 -13
data/lib/ruby_llm/models.rb
CHANGED
@@ -46,22 +46,25 @@ module RubyLLM
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def resolve(model_id, provider: nil, assume_exists: false)
|
49
|
+
def resolve(model_id, provider: nil, assume_exists: false) # rubocop:disable Metrics/PerceivedComplexity
|
50
50
|
assume_exists = true if provider && Provider.providers[provider.to_sym].local?
|
51
51
|
|
52
52
|
if assume_exists
|
53
53
|
raise ArgumentError, 'Provider must be specified if assume_exists is true' unless provider
|
54
54
|
|
55
55
|
provider = Provider.providers[provider.to_sym] || raise(Error, "Unknown provider: #{provider.to_sym}")
|
56
|
-
model =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
56
|
+
model = Model::Info.new(
|
57
|
+
id: model_id,
|
58
|
+
name: model_id.gsub('-', ' ').capitalize,
|
59
|
+
provider: provider.slug,
|
60
|
+
capabilities: %w[function_calling streaming],
|
61
|
+
modalities: { input: %w[text image], output: %w[text] },
|
62
|
+
metadata: { warning: 'Assuming model exists, capabilities may not be accurate' }
|
63
|
+
)
|
64
|
+
if RubyLLM.config.log_assume_model_exists
|
65
|
+
RubyLLM.logger.warn "Assuming model '#{model_id}' exists for provider '#{provider}'. " \
|
66
|
+
'Capabilities may not be accurately reflected.'
|
67
|
+
end
|
65
68
|
else
|
66
69
|
model = Models.find model_id, provider
|
67
70
|
provider = Provider.providers[model.provider.to_sym] || raise(Error, "Unknown provider: #{model.provider}")
|
@@ -84,15 +87,12 @@ module RubyLLM
|
|
84
87
|
def fetch_from_parsera
|
85
88
|
RubyLLM.logger.info 'Fetching models from Parsera API...'
|
86
89
|
|
87
|
-
connection =
|
90
|
+
connection = Connection.basic do |f|
|
88
91
|
f.request :json
|
89
|
-
f.response :json
|
90
|
-
f.response :raise_error
|
91
|
-
f.adapter Faraday.default_adapter
|
92
|
+
f.response :json, parser_options: { symbolize_names: true }
|
92
93
|
end
|
93
|
-
|
94
|
-
response
|
95
|
-
response.body.map { |data| ModelInfo.new(Utils.deep_symbolize_keys(data)) }
|
94
|
+
response = connection.get 'https://api.parsera.org/v1/llm-specs'
|
95
|
+
response.body.map { |data| Model::Info.new(data) }
|
96
96
|
end
|
97
97
|
|
98
98
|
def merge_models(provider_models, parsera_models)
|
@@ -130,10 +130,10 @@ module RubyLLM
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def add_provider_metadata(parsera_model, provider_model)
|
133
|
-
# Create a new
|
133
|
+
# Create a new Model::Info with parsera data but include provider metadata
|
134
134
|
data = parsera_model.to_h
|
135
135
|
data[:metadata] = provider_model.metadata.merge(data[:metadata] || {})
|
136
|
-
|
136
|
+
Model::Info.new(data)
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
@@ -145,7 +145,7 @@ module RubyLLM
|
|
145
145
|
# Load models from the JSON file
|
146
146
|
def load_models
|
147
147
|
data = File.exist?(self.class.models_file) ? File.read(self.class.models_file) : '[]'
|
148
|
-
JSON.parse(data).map { |model|
|
148
|
+
JSON.parse(data, symbolize_names: true).map { |model| Model::Info.new(model) }
|
149
149
|
rescue JSON::ParserError
|
150
150
|
[]
|
151
151
|
end
|
data/lib/ruby_llm/provider.rb
CHANGED
@@ -40,7 +40,7 @@ module RubyLLM
|
|
40
40
|
def paint(prompt, model:, size:, connection:)
|
41
41
|
payload = render_image_payload(prompt, model:, size:)
|
42
42
|
response = connection.post images_url, payload
|
43
|
-
parse_image_response
|
43
|
+
parse_image_response(response, model:)
|
44
44
|
end
|
45
45
|
|
46
46
|
def configured?(config = nil)
|
@@ -14,11 +14,15 @@ module RubyLLM
|
|
14
14
|
parts << format_text(content.text) if content.text
|
15
15
|
|
16
16
|
content.attachments.each do |attachment|
|
17
|
-
case attachment
|
18
|
-
when
|
17
|
+
case attachment.type
|
18
|
+
when :image
|
19
19
|
parts << format_image(attachment)
|
20
|
-
when
|
20
|
+
when :pdf
|
21
21
|
parts << format_pdf(attachment)
|
22
|
+
when :text
|
23
|
+
parts << format_text_file(attachment)
|
24
|
+
else
|
25
|
+
raise UnsupportedAttachmentError, attachment.mime_type
|
22
26
|
end
|
23
27
|
end
|
24
28
|
|
@@ -73,6 +77,13 @@ module RubyLLM
|
|
73
77
|
}
|
74
78
|
end
|
75
79
|
end
|
80
|
+
|
81
|
+
def format_text_file(text_file)
|
82
|
+
{
|
83
|
+
type: 'text',
|
84
|
+
text: Utils.format_text_file_for_llm(text_file)
|
85
|
+
}
|
86
|
+
end
|
76
87
|
end
|
77
88
|
end
|
78
89
|
end
|
@@ -14,12 +14,13 @@ module RubyLLM
|
|
14
14
|
def format_tool_call(msg)
|
15
15
|
tool_call = msg.tool_calls.values.first
|
16
16
|
|
17
|
+
content = []
|
18
|
+
content << Media.format_text(msg.content) unless msg.content.nil? || msg.content.empty?
|
19
|
+
content << format_tool_use_block(tool_call)
|
20
|
+
|
17
21
|
{
|
18
22
|
role: 'assistant',
|
19
|
-
content:
|
20
|
-
Media.format_text(msg.content),
|
21
|
-
format_tool_use_block(tool_call)
|
22
|
-
]
|
23
|
+
content:
|
23
24
|
}
|
24
25
|
end
|
25
26
|
|
@@ -4,6 +4,7 @@ module RubyLLM
|
|
4
4
|
module Providers
|
5
5
|
module Bedrock
|
6
6
|
# Media handling methods for the Bedrock API integration
|
7
|
+
# NOTE: Bedrock does not support url attachments
|
7
8
|
module Media
|
8
9
|
extend Anthropic::Media
|
9
10
|
|
@@ -16,13 +17,15 @@ module RubyLLM
|
|
16
17
|
parts << Anthropic::Media.format_text(content.text) if content.text
|
17
18
|
|
18
19
|
content.attachments.each do |attachment|
|
19
|
-
case attachment
|
20
|
-
when
|
20
|
+
case attachment.type
|
21
|
+
when :image
|
21
22
|
parts << format_image(attachment)
|
22
|
-
when
|
23
|
+
when :pdf
|
23
24
|
parts << format_pdf(attachment)
|
25
|
+
when :text
|
26
|
+
parts << Anthropic::Media.format_text_file(attachment)
|
24
27
|
else
|
25
|
-
raise
|
28
|
+
raise UnsupportedAttachmentError, attachment.type
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
@@ -30,7 +30,7 @@ module RubyLLM
|
|
30
30
|
models.select { |m| m['modelId'].include?('claude') }.map do |model_data|
|
31
31
|
model_id = model_data['modelId']
|
32
32
|
|
33
|
-
|
33
|
+
Model::Info.new(
|
34
34
|
id: model_id_with_region(model_id, model_data),
|
35
35
|
name: model_data['modelName'] || capabilities.format_display_name(model_id),
|
36
36
|
provider: slug,
|
@@ -56,7 +56,7 @@ module RubyLLM
|
|
56
56
|
def create_model_info(model_data, slug, _capabilities)
|
57
57
|
model_id = model_data['modelId']
|
58
58
|
|
59
|
-
|
59
|
+
Model::Info.new(
|
60
60
|
id: model_id_with_region(model_id, model_data),
|
61
61
|
name: model_data['modelName'] || model_id,
|
62
62
|
provider: slug,
|
@@ -30,7 +30,7 @@ module RubyLLM
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def valid_lengths?(total_length, headers_length)
|
33
|
-
|
33
|
+
valid_length_constraints?(total_length, headers_length)
|
34
34
|
end
|
35
35
|
|
36
36
|
def calculate_positions(offset, total_length, headers_length)
|
@@ -67,7 +67,7 @@ module RubyLLM
|
|
67
67
|
|
68
68
|
def valid_prelude_at_position?(chunk, pos)
|
69
69
|
lengths = extract_potential_lengths(chunk, pos)
|
70
|
-
|
70
|
+
valid_length_constraints?(*lengths)
|
71
71
|
end
|
72
72
|
|
73
73
|
def extract_potential_lengths(chunk, pos)
|
@@ -77,7 +77,7 @@ module RubyLLM
|
|
77
77
|
]
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
80
|
+
def valid_length_constraints?(total_length, headers_length)
|
81
81
|
return false if total_length.nil? || headers_length.nil?
|
82
82
|
return false if total_length <= 0 || total_length > 1_000_000
|
83
83
|
return false if headers_length <= 0 || headers_length >= total_length
|
@@ -24,7 +24,7 @@ module RubyLLM
|
|
24
24
|
}
|
25
25
|
end
|
26
26
|
|
27
|
-
def parse_image_response(response)
|
27
|
+
def parse_image_response(response, model:)
|
28
28
|
data = response.body
|
29
29
|
image_data = data['predictions']&.first
|
30
30
|
|
@@ -38,7 +38,8 @@ module RubyLLM
|
|
38
38
|
|
39
39
|
Image.new(
|
40
40
|
data: base64_data,
|
41
|
-
mime_type: mime_type
|
41
|
+
mime_type: mime_type,
|
42
|
+
model_id: model
|
42
43
|
)
|
43
44
|
end
|
44
45
|
end
|
@@ -14,43 +14,31 @@ module RubyLLM
|
|
14
14
|
parts << format_text(content.text) if content.text
|
15
15
|
|
16
16
|
content.attachments.each do |attachment|
|
17
|
-
case attachment
|
18
|
-
when
|
19
|
-
parts <<
|
20
|
-
when
|
21
|
-
|
22
|
-
|
23
|
-
parts <<
|
17
|
+
case attachment.type
|
18
|
+
when :text
|
19
|
+
parts << format_text_file(attachment)
|
20
|
+
when :unknown
|
21
|
+
raise UnsupportedAttachmentError, attachment.mime_type
|
22
|
+
else
|
23
|
+
parts << format_attachment(attachment)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
parts
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
30
|
+
def format_attachment(attachment)
|
31
31
|
{
|
32
32
|
inline_data: {
|
33
|
-
mime_type:
|
34
|
-
data:
|
33
|
+
mime_type: attachment.mime_type,
|
34
|
+
data: attachment.encoded
|
35
35
|
}
|
36
36
|
}
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
39
|
+
def format_text_file(text_file)
|
40
40
|
{
|
41
|
-
|
42
|
-
mime_type: pdf.mime_type,
|
43
|
-
data: pdf.encoded
|
44
|
-
}
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
def format_audio(audio)
|
49
|
-
{
|
50
|
-
inline_data: {
|
51
|
-
mime_type: audio.mime_type,
|
52
|
-
data: audio.encoded
|
53
|
-
}
|
41
|
+
text: Utils.format_text_file_for_llm(text_file)
|
54
42
|
}
|
55
43
|
end
|
56
44
|
|
@@ -16,13 +16,17 @@ module RubyLLM
|
|
16
16
|
parts << format_text(content.text) if content.text
|
17
17
|
|
18
18
|
content.attachments.each do |attachment|
|
19
|
-
case attachment
|
20
|
-
when
|
19
|
+
case attachment.type
|
20
|
+
when :image
|
21
21
|
parts << Ollama::Media.format_image(attachment)
|
22
|
-
when
|
22
|
+
when :pdf
|
23
23
|
parts << format_pdf(attachment)
|
24
|
-
when
|
24
|
+
when :audio
|
25
25
|
parts << format_audio(attachment)
|
26
|
+
when :text
|
27
|
+
parts << format_text_file(attachment)
|
28
|
+
else
|
29
|
+
raise UnsupportedAttachmentError, attachment.mime_type
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -215,10 +215,13 @@ module RubyLLM
|
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
218
|
-
def normalize_temperature(temperature, model_id)
|
218
|
+
def self.normalize_temperature(temperature, model_id)
|
219
219
|
if model_id.match?(/^o\d/)
|
220
220
|
RubyLLM.logger.debug "Model #{model_id} requires temperature=1.0, ignoring provided value"
|
221
221
|
1.0
|
222
|
+
elsif model_id.match?(/-search/)
|
223
|
+
RubyLLM.logger.debug "Model #{model_id} does not accept temperature parameter, removing"
|
224
|
+
nil
|
222
225
|
else
|
223
226
|
temperature
|
224
227
|
end
|
@@ -263,7 +266,7 @@ module RubyLLM
|
|
263
266
|
# Advanced capabilities
|
264
267
|
capabilities << 'reasoning' if model_id.match?(/o1/)
|
265
268
|
|
266
|
-
if model_id.match?(/gpt-4-turbo|gpt-4o
|
269
|
+
if model_id.match?(/gpt-4-turbo|gpt-4o/)
|
267
270
|
capabilities << 'image_generation' if model_id.match?(/vision/)
|
268
271
|
capabilities << 'speech_generation' if model_id.match?(/audio/)
|
269
272
|
capabilities << 'transcription' if model_id.match?(/audio/)
|
@@ -12,18 +12,22 @@ module RubyLLM
|
|
12
12
|
module_function
|
13
13
|
|
14
14
|
def render_payload(messages, tools:, temperature:, model:, stream: false)
|
15
|
-
{
|
15
|
+
payload = {
|
16
16
|
model: model,
|
17
17
|
messages: format_messages(messages),
|
18
|
-
temperature: temperature,
|
19
18
|
stream: stream
|
20
|
-
}
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
}
|
20
|
+
|
21
|
+
# Only include temperature if it's not nil (some models don't accept it)
|
22
|
+
payload[:temperature] = temperature unless temperature.nil?
|
23
|
+
|
24
|
+
if tools.any?
|
25
|
+
payload[:tools] = tools.map { |_, tool| tool_for(tool) }
|
26
|
+
payload[:tool_choice] = 'auto'
|
26
27
|
end
|
28
|
+
|
29
|
+
payload[:stream_options] = { include_usage: true } if stream
|
30
|
+
payload
|
27
31
|
end
|
28
32
|
|
29
33
|
def parse_completion_response(response)
|
@@ -20,7 +20,7 @@ module RubyLLM
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
def parse_image_response(response)
|
23
|
+
def parse_image_response(response, model:)
|
24
24
|
data = response.body
|
25
25
|
image_data = data['data'].first
|
26
26
|
|
@@ -28,7 +28,8 @@ module RubyLLM
|
|
28
28
|
url: image_data['url'],
|
29
29
|
mime_type: 'image/png', # DALL-E typically returns PNGs
|
30
30
|
revised_prompt: image_data['revised_prompt'],
|
31
|
-
model_id:
|
31
|
+
model_id: model,
|
32
|
+
data: image_data['b64_json']
|
32
33
|
)
|
33
34
|
end
|
34
35
|
end
|
@@ -14,13 +14,17 @@ module RubyLLM
|
|
14
14
|
parts << format_text(content.text) if content.text
|
15
15
|
|
16
16
|
content.attachments.each do |attachment|
|
17
|
-
case attachment
|
18
|
-
when
|
17
|
+
case attachment.type
|
18
|
+
when :image
|
19
19
|
parts << format_image(attachment)
|
20
|
-
when
|
20
|
+
when :pdf
|
21
21
|
parts << format_pdf(attachment)
|
22
|
-
when
|
22
|
+
when :audio
|
23
23
|
parts << format_audio(attachment)
|
24
|
+
when :text
|
25
|
+
parts << format_text_file(attachment)
|
26
|
+
else
|
27
|
+
raise UnsupportedAttachmentError, attachment.type
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
@@ -31,8 +35,7 @@ module RubyLLM
|
|
31
35
|
{
|
32
36
|
type: 'image_url',
|
33
37
|
image_url: {
|
34
|
-
url: image.url? ? image.source : "data:#{image.mime_type};base64,#{image.encoded}"
|
35
|
-
detail: 'auto'
|
38
|
+
url: image.url? ? image.source : "data:#{image.mime_type};base64,#{image.encoded}"
|
36
39
|
}
|
37
40
|
}
|
38
41
|
end
|
@@ -41,18 +44,25 @@ module RubyLLM
|
|
41
44
|
{
|
42
45
|
type: 'file',
|
43
46
|
file: {
|
44
|
-
filename:
|
47
|
+
filename: pdf.filename,
|
45
48
|
file_data: "data:#{pdf.mime_type};base64,#{pdf.encoded}"
|
46
49
|
}
|
47
50
|
}
|
48
51
|
end
|
49
52
|
|
53
|
+
def format_text_file(text_file)
|
54
|
+
{
|
55
|
+
type: 'text',
|
56
|
+
text: Utils.format_text_file_for_llm(text_file)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
50
60
|
def format_audio(audio)
|
51
61
|
{
|
52
62
|
type: 'input_audio',
|
53
63
|
input_audio: {
|
54
64
|
data: audio.encoded,
|
55
|
-
format: audio.
|
65
|
+
format: audio.mime_type.split('/').last
|
56
66
|
}
|
57
67
|
}
|
58
68
|
end
|
@@ -37,7 +37,7 @@ module RubyLLM
|
|
37
37
|
# Convert OpenRouter's supported parameters to our capability format
|
38
38
|
capabilities = supported_parameters_to_capabilities(model_data['supported_parameters'])
|
39
39
|
|
40
|
-
|
40
|
+
Model::Info.new(
|
41
41
|
id: model_data['id'],
|
42
42
|
name: model_data['name'],
|
43
43
|
provider: slug,
|
data/lib/ruby_llm/streaming.rb
CHANGED
@@ -12,9 +12,18 @@ module RubyLLM
|
|
12
12
|
accumulator = StreamAccumulator.new
|
13
13
|
|
14
14
|
connection.post stream_url, payload do |req|
|
15
|
-
req.options.on_data
|
16
|
-
|
17
|
-
|
15
|
+
if req.options.respond_to?(:on_data)
|
16
|
+
# Handle Faraday 2.x streaming with on_data method
|
17
|
+
req.options.on_data = handle_stream do |chunk|
|
18
|
+
accumulator.add chunk
|
19
|
+
block.call chunk
|
20
|
+
end
|
21
|
+
else
|
22
|
+
# Handle Faraday 1.x streaming with :on_data key
|
23
|
+
req.options[:on_data] = handle_stream do |chunk|
|
24
|
+
accumulator.add chunk
|
25
|
+
block.call chunk
|
26
|
+
end
|
18
27
|
end
|
19
28
|
end
|
20
29
|
|
@@ -29,19 +38,45 @@ module RubyLLM
|
|
29
38
|
|
30
39
|
private
|
31
40
|
|
32
|
-
def to_json_stream(&
|
41
|
+
def to_json_stream(&)
|
33
42
|
buffer = String.new
|
34
43
|
parser = EventStreamParser::Parser.new
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
create_stream_processor(parser, buffer, &)
|
46
|
+
end
|
38
47
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
def create_stream_processor(parser, buffer, &)
|
49
|
+
if Faraday::VERSION.start_with?('1')
|
50
|
+
# Faraday 1.x: on_data receives (chunk, size)
|
51
|
+
legacy_stream_processor(parser, &)
|
52
|
+
else
|
53
|
+
# Faraday 2.x: on_data receives (chunk, bytes, env)
|
54
|
+
stream_processor(parser, buffer, &)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_stream_chunk(chunk, parser, _env, &)
|
59
|
+
RubyLLM.logger.debug "Received chunk: #{chunk}"
|
60
|
+
|
61
|
+
if error_chunk?(chunk)
|
62
|
+
handle_error_chunk(chunk, nil)
|
63
|
+
else
|
64
|
+
yield handle_sse(chunk, parser, nil, &)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def legacy_stream_processor(parser, &block)
|
69
|
+
proc do |chunk, _size|
|
70
|
+
process_stream_chunk(chunk, parser, nil, &block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def stream_processor(parser, buffer, &block)
|
75
|
+
proc do |chunk, _bytes, env|
|
76
|
+
if env&.status == 200
|
77
|
+
process_stream_chunk(chunk, parser, env, &block)
|
43
78
|
else
|
44
|
-
|
79
|
+
handle_failed_response(chunk, buffer, env)
|
45
80
|
end
|
46
81
|
end
|
47
82
|
end
|
data/lib/ruby_llm/tool.rb
CHANGED
@@ -49,14 +49,14 @@ module RubyLLM
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def name
|
52
|
-
self.class.name
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
klass_name = self.class.name
|
53
|
+
normalized = klass_name.to_s.dup.force_encoding('UTF-8').unicode_normalize(:nfkd)
|
54
|
+
normalized.encode('ASCII', replace: '')
|
55
|
+
.gsub(/[^a-zA-Z0-9_-]/, '-')
|
56
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
57
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
58
|
+
.downcase
|
59
|
+
.delete_suffix('_tool')
|
60
60
|
end
|
61
61
|
|
62
62
|
def description
|
data/lib/ruby_llm/utils.rb
CHANGED
@@ -5,17 +5,22 @@ module RubyLLM
|
|
5
5
|
module Utils
|
6
6
|
module_function
|
7
7
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
def format_text_file_for_llm(text_file)
|
9
|
+
"<file name='#{text_file.filename}' mime_type='#{text_file.mime_type}'>#{text_file.content}</file>"
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_get(hash, key)
|
13
|
+
hash[key.to_sym] || hash[key.to_s]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_safe_array(item)
|
17
|
+
case item
|
15
18
|
when Array
|
16
|
-
|
19
|
+
item
|
20
|
+
when Hash
|
21
|
+
[item]
|
17
22
|
else
|
18
|
-
|
23
|
+
Array(item)
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
data/lib/ruby_llm/version.rb
CHANGED