ruby_llm 1.11.0 → 1.12.0
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 +12 -0
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +41 -7
- data/lib/ruby_llm/active_record/chat_methods.rb +41 -7
- data/lib/ruby_llm/agent.rb +323 -0
- data/lib/ruby_llm/aliases.json +47 -29
- data/lib/ruby_llm/chat.rb +27 -3
- data/lib/ruby_llm/configuration.rb +3 -0
- data/lib/ruby_llm/models.json +19090 -5190
- data/lib/ruby_llm/models.rb +35 -6
- data/lib/ruby_llm/provider.rb +8 -0
- data/lib/ruby_llm/providers/azure/chat.rb +29 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +24 -0
- data/lib/ruby_llm/providers/azure/media.rb +45 -0
- data/lib/ruby_llm/providers/azure/models.rb +14 -0
- data/lib/ruby_llm/providers/azure.rb +56 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +122 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +296 -64
- data/lib/ruby_llm/providers/bedrock/media.rb +62 -33
- data/lib/ruby_llm/providers/bedrock/models.rb +88 -65
- data/lib/ruby_llm/providers/bedrock/streaming.rb +305 -8
- data/lib/ruby_llm/providers/bedrock.rb +61 -52
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +4 -0
- data/lib/tasks/models.rake +10 -5
- data/lib/tasks/vcr.rake +32 -0
- metadata +13 -13
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +0 -167
- data/lib/ruby_llm/providers/bedrock/signing.rb +0 -831
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -51
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -128
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -67
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -85
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -78
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8ff2f4da0c39e4909925217affa2b76908207e2c936a6dc05b56cffb2781863
|
|
4
|
+
data.tar.gz: 0eebb76434b049d4332a247e7581bb721d4dd86b0496be8655d15a4adcb42f65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95a3eb5a1c6a50c69dd8166044d99d77848fc8dcdd5d31b748e8303a3e8c5756cc86a8ce0220dd3c643bc0b32080f6c2125c79f5e437bb654725a119b4b4b601
|
|
7
|
+
data.tar.gz: 5efbd317193ca7f5e28df0a2d9bcc7ffd8a1740f40d29e7b872bed8a2ee910db376dc2dc890723b0c253718404993e55deab87356d414afdfd741d9392bd5e0f
|
data/README.md
CHANGED
|
@@ -95,6 +95,17 @@ end
|
|
|
95
95
|
chat.with_tool(Weather).ask "What's the weather in Berlin?"
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
```ruby
|
|
99
|
+
# Define an agent with instructions + tools
|
|
100
|
+
class WeatherAssistant < RubyLLM::Agent
|
|
101
|
+
model "gpt-4.1-nano"
|
|
102
|
+
instructions "Be concise and always use tools for weather."
|
|
103
|
+
tools Weather
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
WeatherAssistant.new.ask "What's the weather in Berlin?"
|
|
107
|
+
```
|
|
108
|
+
|
|
98
109
|
```ruby
|
|
99
110
|
# Get structured output
|
|
100
111
|
class ProductSchema < RubyLLM::Schema
|
|
@@ -118,6 +129,7 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
|
|
|
118
129
|
* **Embeddings:** Generate embeddings with `RubyLLM.embed`
|
|
119
130
|
* **Moderation:** Content safety with `RubyLLM.moderate`
|
|
120
131
|
* **Tools:** Let AI call your Ruby methods
|
|
132
|
+
* **Agents:** Reusable assistants with `RubyLLM::Agent`
|
|
121
133
|
* **Structured output:** JSON schemas that just work
|
|
122
134
|
* **Streaming:** Real-time responses with blocks
|
|
123
135
|
* **Rails:** ActiveRecord integration with `acts_as_chat`
|
|
@@ -95,19 +95,19 @@ module RubyLLM
|
|
|
95
95
|
end
|
|
96
96
|
@chat.reset_messages!
|
|
97
97
|
|
|
98
|
-
messages.
|
|
98
|
+
ordered_messages = order_messages_for_llm(messages.to_a)
|
|
99
|
+
ordered_messages.each do |msg|
|
|
99
100
|
@chat.add_message(msg.to_llm)
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
setup_persistence_callbacks
|
|
103
104
|
end
|
|
104
105
|
|
|
105
|
-
def with_instructions(instructions, replace:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
to_llm.with_instructions(instructions)
|
|
106
|
+
def with_instructions(instructions, append: false, replace: nil)
|
|
107
|
+
append = append_instructions?(append:, replace:)
|
|
108
|
+
persist_system_instruction(instructions, append:)
|
|
109
|
+
|
|
110
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
111
111
|
self
|
|
112
112
|
end
|
|
113
113
|
|
|
@@ -233,6 +233,40 @@ module RubyLLM
|
|
|
233
233
|
end
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
+
def replace_persisted_system_instructions(instructions)
|
|
237
|
+
system_messages = messages.where(role: :system).order(:id).to_a
|
|
238
|
+
|
|
239
|
+
if system_messages.empty?
|
|
240
|
+
messages.create!(role: :system, content: instructions)
|
|
241
|
+
return
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
primary_message = system_messages.shift
|
|
245
|
+
primary_message.update!(content: instructions) if primary_message.content != instructions
|
|
246
|
+
system_messages.each(&:destroy!)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def append_instructions?(append:, replace:)
|
|
250
|
+
return append if replace.nil?
|
|
251
|
+
|
|
252
|
+
append || (replace == false)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def persist_system_instruction(instructions, append:)
|
|
256
|
+
transaction do
|
|
257
|
+
if append
|
|
258
|
+
messages.create!(role: :system, content: instructions)
|
|
259
|
+
else
|
|
260
|
+
replace_persisted_system_instructions(instructions)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def order_messages_for_llm(messages)
|
|
266
|
+
system_messages, non_system_messages = messages.partition { |msg| msg.role.to_s == 'system' }
|
|
267
|
+
system_messages + non_system_messages
|
|
268
|
+
end
|
|
269
|
+
|
|
236
270
|
def setup_persistence_callbacks
|
|
237
271
|
return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
|
|
238
272
|
|
|
@@ -83,19 +83,19 @@ module RubyLLM
|
|
|
83
83
|
)
|
|
84
84
|
@chat.reset_messages!
|
|
85
85
|
|
|
86
|
-
messages_association.
|
|
86
|
+
ordered_messages = order_messages_for_llm(messages_association.to_a)
|
|
87
|
+
ordered_messages.each do |msg|
|
|
87
88
|
@chat.add_message(msg.to_llm)
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
setup_persistence_callbacks
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
def with_instructions(instructions, replace:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
to_llm.with_instructions(instructions)
|
|
94
|
+
def with_instructions(instructions, append: false, replace: nil)
|
|
95
|
+
append = append_instructions?(append:, replace:)
|
|
96
|
+
persist_system_instruction(instructions, append:)
|
|
97
|
+
|
|
98
|
+
to_llm.with_instructions(instructions, append:, replace:)
|
|
99
99
|
self
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -244,6 +244,40 @@ module RubyLLM
|
|
|
244
244
|
@chat
|
|
245
245
|
end
|
|
246
246
|
|
|
247
|
+
def replace_persisted_system_instructions(instructions)
|
|
248
|
+
system_messages = messages_association.where(role: :system).order(:id).to_a
|
|
249
|
+
|
|
250
|
+
if system_messages.empty?
|
|
251
|
+
messages_association.create!(role: :system, content: instructions)
|
|
252
|
+
return
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
primary_message = system_messages.shift
|
|
256
|
+
primary_message.update!(content: instructions) if primary_message.content != instructions
|
|
257
|
+
system_messages.each(&:destroy!)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def append_instructions?(append:, replace:)
|
|
261
|
+
return append if replace.nil?
|
|
262
|
+
|
|
263
|
+
append || (replace == false)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def persist_system_instruction(instructions, append:)
|
|
267
|
+
transaction do
|
|
268
|
+
if append
|
|
269
|
+
messages_association.create!(role: :system, content: instructions)
|
|
270
|
+
else
|
|
271
|
+
replace_persisted_system_instructions(instructions)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def order_messages_for_llm(messages)
|
|
277
|
+
system_messages, non_system_messages = messages.partition { |msg| msg.role.to_s == 'system' }
|
|
278
|
+
system_messages + non_system_messages
|
|
279
|
+
end
|
|
280
|
+
|
|
247
281
|
def persist_new_message
|
|
248
282
|
@message = messages_association.create!(role: :assistant, content: '')
|
|
249
283
|
end
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
# Base class for simple, class-configured agents.
|
|
8
|
+
class Agent
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def inherited(subclass)
|
|
13
|
+
super
|
|
14
|
+
subclass.instance_variable_set(:@chat_kwargs, (@chat_kwargs || {}).dup)
|
|
15
|
+
subclass.instance_variable_set(:@tools, (@tools || []).dup)
|
|
16
|
+
subclass.instance_variable_set(:@instructions, @instructions)
|
|
17
|
+
subclass.instance_variable_set(:@temperature, @temperature)
|
|
18
|
+
subclass.instance_variable_set(:@thinking, @thinking)
|
|
19
|
+
subclass.instance_variable_set(:@params, (@params || {}).dup)
|
|
20
|
+
subclass.instance_variable_set(:@headers, (@headers || {}).dup)
|
|
21
|
+
subclass.instance_variable_set(:@schema, @schema)
|
|
22
|
+
subclass.instance_variable_set(:@context, @context)
|
|
23
|
+
subclass.instance_variable_set(:@chat_model, @chat_model)
|
|
24
|
+
subclass.instance_variable_set(:@input_names, (@input_names || []).dup)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def model(model_id = nil, **options)
|
|
28
|
+
options[:model] = model_id unless model_id.nil?
|
|
29
|
+
@chat_kwargs = options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tools(*tools, &block)
|
|
33
|
+
return @tools || [] if tools.empty? && !block_given?
|
|
34
|
+
|
|
35
|
+
@tools = block_given? ? block : tools.flatten
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def instructions(text = nil, **prompt_locals, &block)
|
|
39
|
+
if text.nil? && prompt_locals.empty? && !block_given?
|
|
40
|
+
@instructions ||= { prompt: 'instructions', locals: {} }
|
|
41
|
+
return @instructions
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@instructions = block || text || { prompt: 'instructions', locals: prompt_locals }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def temperature(value = nil)
|
|
48
|
+
return @temperature if value.nil?
|
|
49
|
+
|
|
50
|
+
@temperature = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def thinking(effort: nil, budget: nil)
|
|
54
|
+
return @thinking if effort.nil? && budget.nil?
|
|
55
|
+
|
|
56
|
+
@thinking = { effort: effort, budget: budget }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def params(**params, &block)
|
|
60
|
+
return @params || {} if params.empty? && !block_given?
|
|
61
|
+
|
|
62
|
+
@params = block_given? ? block : params
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def headers(**headers, &block)
|
|
66
|
+
return @headers || {} if headers.empty? && !block_given?
|
|
67
|
+
|
|
68
|
+
@headers = block_given? ? block : headers
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def schema(value = nil, &block)
|
|
72
|
+
return @schema if value.nil? && !block_given?
|
|
73
|
+
|
|
74
|
+
@schema = block_given? ? block : value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def context(value = nil)
|
|
78
|
+
return @context if value.nil?
|
|
79
|
+
|
|
80
|
+
@context = value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def chat_model(value = nil)
|
|
84
|
+
return @chat_model if value.nil?
|
|
85
|
+
|
|
86
|
+
@chat_model = value
|
|
87
|
+
remove_instance_variable(:@resolved_chat_model) if instance_variable_defined?(:@resolved_chat_model)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def inputs(*names)
|
|
91
|
+
return @input_names || [] if names.empty?
|
|
92
|
+
|
|
93
|
+
@input_names = names.flatten.map(&:to_sym)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def chat_kwargs
|
|
97
|
+
@chat_kwargs || {}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def chat(**kwargs)
|
|
101
|
+
input_values, chat_options = partition_inputs(kwargs)
|
|
102
|
+
chat = RubyLLM.chat(**chat_kwargs, **chat_options)
|
|
103
|
+
apply_configuration(chat, input_values:, persist_instructions: true)
|
|
104
|
+
chat
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create(**kwargs)
|
|
108
|
+
with_rails_chat_record(:create, **kwargs)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def create!(**kwargs)
|
|
112
|
+
with_rails_chat_record(:create!, **kwargs)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def find(id, **kwargs)
|
|
116
|
+
raise ArgumentError, 'chat_model must be configured to use find' unless resolved_chat_model
|
|
117
|
+
|
|
118
|
+
input_values, = partition_inputs(kwargs)
|
|
119
|
+
record = resolved_chat_model.find(id)
|
|
120
|
+
apply_configuration(record, input_values:, persist_instructions: false)
|
|
121
|
+
record
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def sync_instructions!(chat_or_id, **kwargs)
|
|
125
|
+
raise ArgumentError, 'chat_model must be configured to use sync_instructions!' unless resolved_chat_model
|
|
126
|
+
|
|
127
|
+
input_values, = partition_inputs(kwargs)
|
|
128
|
+
record = chat_or_id.is_a?(resolved_chat_model) ? chat_or_id : resolved_chat_model.find(chat_or_id)
|
|
129
|
+
runtime = runtime_context(chat: record, inputs: input_values)
|
|
130
|
+
instructions_value = resolved_instructions_value(record, runtime, inputs: input_values)
|
|
131
|
+
return record if instructions_value.nil?
|
|
132
|
+
|
|
133
|
+
record.with_instructions(instructions_value)
|
|
134
|
+
record
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_prompt(name, chat:, inputs:, locals:)
|
|
138
|
+
path = prompt_path_for(name)
|
|
139
|
+
return nil unless File.exist?(path)
|
|
140
|
+
|
|
141
|
+
resolved_locals = resolve_prompt_locals(locals, runtime: runtime_context(chat:, inputs:), chat:, inputs:)
|
|
142
|
+
ERB.new(File.read(path)).result_with_hash(resolved_locals)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def with_rails_chat_record(method_name, **kwargs)
|
|
148
|
+
raise ArgumentError, 'chat_model must be configured to use create/create!' unless resolved_chat_model
|
|
149
|
+
|
|
150
|
+
input_values, chat_options = partition_inputs(kwargs)
|
|
151
|
+
record = resolved_chat_model.public_send(method_name, **chat_kwargs, **chat_options)
|
|
152
|
+
apply_configuration(record, input_values:, persist_instructions: true) if record
|
|
153
|
+
record
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def apply_configuration(chat_object, input_values:, persist_instructions:)
|
|
157
|
+
runtime = runtime_context(chat: chat_object, inputs: input_values)
|
|
158
|
+
llm_chat = llm_chat_for(chat_object)
|
|
159
|
+
|
|
160
|
+
apply_context(llm_chat)
|
|
161
|
+
apply_instructions(chat_object, runtime, inputs: input_values, persist: persist_instructions)
|
|
162
|
+
apply_tools(llm_chat, runtime)
|
|
163
|
+
apply_temperature(llm_chat)
|
|
164
|
+
apply_thinking(llm_chat)
|
|
165
|
+
apply_params(llm_chat, runtime)
|
|
166
|
+
apply_headers(llm_chat, runtime)
|
|
167
|
+
apply_schema(llm_chat, runtime)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def apply_context(llm_chat)
|
|
171
|
+
llm_chat.with_context(context) if context
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def apply_instructions(chat_object, runtime, inputs:, persist:)
|
|
175
|
+
value = resolved_instructions_value(chat_object, runtime, inputs:)
|
|
176
|
+
return if value.nil?
|
|
177
|
+
|
|
178
|
+
instruction_target(chat_object, persist:).with_instructions(value)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def apply_tools(llm_chat, runtime)
|
|
182
|
+
tools_to_apply = Array(evaluate(tools, runtime))
|
|
183
|
+
llm_chat.with_tools(*tools_to_apply) unless tools_to_apply.empty?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def apply_temperature(llm_chat)
|
|
187
|
+
llm_chat.with_temperature(temperature) unless temperature.nil?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def apply_thinking(llm_chat)
|
|
191
|
+
llm_chat.with_thinking(**thinking) if thinking
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def apply_params(llm_chat, runtime)
|
|
195
|
+
value = evaluate(params, runtime)
|
|
196
|
+
llm_chat.with_params(**value) if value && !value.empty?
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def apply_headers(llm_chat, runtime)
|
|
200
|
+
value = evaluate(headers, runtime)
|
|
201
|
+
llm_chat.with_headers(**value) if value && !value.empty?
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def apply_schema(llm_chat, runtime)
|
|
205
|
+
value = evaluate(schema, runtime)
|
|
206
|
+
llm_chat.with_schema(value) if value
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def llm_chat_for(chat_object)
|
|
210
|
+
chat_object.respond_to?(:to_llm) ? chat_object.to_llm : chat_object
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def evaluate(value, runtime)
|
|
214
|
+
value.is_a?(Proc) ? runtime.instance_exec(&value) : value
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def resolved_instructions_value(chat_object, runtime, inputs:)
|
|
218
|
+
value = evaluate(instructions, runtime)
|
|
219
|
+
return value unless prompt_instruction?(value)
|
|
220
|
+
|
|
221
|
+
runtime.prompt(
|
|
222
|
+
value[:prompt],
|
|
223
|
+
**resolve_prompt_locals(value[:locals] || {}, runtime:, chat: chat_object, inputs:)
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def prompt_instruction?(value)
|
|
228
|
+
value.is_a?(Hash) && value[:prompt]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def instruction_target(chat_object, persist:)
|
|
232
|
+
if persist || !chat_object.respond_to?(:to_llm)
|
|
233
|
+
chat_object
|
|
234
|
+
else
|
|
235
|
+
chat_object.to_llm
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def resolve_prompt_locals(locals, runtime:, chat:, inputs:)
|
|
240
|
+
base = { chat: chat }.merge(inputs)
|
|
241
|
+
evaluated = locals.each_with_object({}) do |(key, value), acc|
|
|
242
|
+
acc[key.to_sym] = value.is_a?(Proc) ? runtime.instance_exec(&value) : value
|
|
243
|
+
end
|
|
244
|
+
base.merge(evaluated)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def partition_inputs(kwargs)
|
|
248
|
+
input_values = {}
|
|
249
|
+
chat_options = {}
|
|
250
|
+
|
|
251
|
+
kwargs.each do |key, value|
|
|
252
|
+
symbolized_key = key.to_sym
|
|
253
|
+
if inputs.include?(symbolized_key)
|
|
254
|
+
input_values[symbolized_key] = value
|
|
255
|
+
else
|
|
256
|
+
chat_options[symbolized_key] = value
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
[input_values, chat_options]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def runtime_context(chat:, inputs:)
|
|
264
|
+
agent_class = self
|
|
265
|
+
Object.new.tap do |runtime|
|
|
266
|
+
runtime.define_singleton_method(:chat) { chat }
|
|
267
|
+
runtime.define_singleton_method(:prompt) do |name, **locals|
|
|
268
|
+
agent_class.render_prompt(name, chat:, inputs:, locals:)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
inputs.each do |name, value|
|
|
272
|
+
runtime.define_singleton_method(name) { value }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def prompt_path_for(name)
|
|
278
|
+
filename = name.to_s
|
|
279
|
+
filename += '.txt.erb' unless filename.end_with?('.txt.erb')
|
|
280
|
+
prompt_root.join(prompt_agent_path, filename)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def prompt_agent_path
|
|
284
|
+
class_name = name || 'agent'
|
|
285
|
+
class_name.gsub('::', '/')
|
|
286
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
287
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
288
|
+
.tr('-', '_')
|
|
289
|
+
.downcase
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def prompt_root
|
|
293
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
294
|
+
Rails.root.join('app/prompts')
|
|
295
|
+
else
|
|
296
|
+
Pathname.new(Dir.pwd).join('app/prompts')
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def resolved_chat_model
|
|
301
|
+
return @resolved_chat_model if defined?(@resolved_chat_model)
|
|
302
|
+
|
|
303
|
+
@resolved_chat_model = case @chat_model
|
|
304
|
+
when String then Object.const_get(@chat_model)
|
|
305
|
+
else @chat_model
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def initialize(chat: nil, inputs: nil, persist_instructions: true, **kwargs)
|
|
311
|
+
input_values, chat_options = self.class.send(:partition_inputs, kwargs)
|
|
312
|
+
@chat = chat || RubyLLM.chat(**self.class.chat_kwargs, **chat_options)
|
|
313
|
+
self.class.send(:apply_configuration, @chat, input_values: input_values.merge(inputs || {}),
|
|
314
|
+
persist_instructions:)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
attr_reader :chat
|
|
318
|
+
|
|
319
|
+
delegate :ask, :say, :complete, :add_message, :messages,
|
|
320
|
+
:on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each,
|
|
321
|
+
to: :chat
|
|
322
|
+
end
|
|
323
|
+
end
|
data/lib/ruby_llm/aliases.json
CHANGED
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"claude-haiku-4-5": {
|
|
41
41
|
"anthropic": "claude-haiku-4-5-20251001",
|
|
42
42
|
"openrouter": "anthropic/claude-haiku-4.5",
|
|
43
|
-
"bedrock": "anthropic.claude-haiku-4-5-20251001-v1:0"
|
|
43
|
+
"bedrock": "anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
44
|
+
"azure": "claude-haiku-4-5-20251001"
|
|
44
45
|
},
|
|
45
46
|
"claude-opus-4": {
|
|
46
47
|
"anthropic": "claude-opus-4-20250514",
|
|
@@ -53,12 +54,19 @@
|
|
|
53
54
|
"claude-opus-4-1": {
|
|
54
55
|
"anthropic": "claude-opus-4-1-20250805",
|
|
55
56
|
"openrouter": "anthropic/claude-opus-4.1",
|
|
56
|
-
"bedrock": "anthropic.claude-opus-4-1-20250805-v1:0"
|
|
57
|
+
"bedrock": "anthropic.claude-opus-4-1-20250805-v1:0",
|
|
58
|
+
"azure": "claude-opus-4-1-20250805"
|
|
57
59
|
},
|
|
58
60
|
"claude-opus-4-5": {
|
|
59
61
|
"anthropic": "claude-opus-4-5-20251101",
|
|
60
62
|
"openrouter": "anthropic/claude-opus-4.5",
|
|
61
|
-
"bedrock": "anthropic.claude-opus-4-5-20251101-v1:0"
|
|
63
|
+
"bedrock": "anthropic.claude-opus-4-5-20251101-v1:0",
|
|
64
|
+
"azure": "claude-opus-4-5-20251101"
|
|
65
|
+
},
|
|
66
|
+
"claude-opus-4-6": {
|
|
67
|
+
"anthropic": "claude-opus-4-6",
|
|
68
|
+
"openrouter": "anthropic/claude-opus-4.6",
|
|
69
|
+
"bedrock": "anthropic.claude-opus-4-6-v1"
|
|
62
70
|
},
|
|
63
71
|
"claude-sonnet-4": {
|
|
64
72
|
"anthropic": "claude-sonnet-4-20250514",
|
|
@@ -71,7 +79,8 @@
|
|
|
71
79
|
"claude-sonnet-4-5": {
|
|
72
80
|
"anthropic": "claude-sonnet-4-5-20250929",
|
|
73
81
|
"openrouter": "anthropic/claude-sonnet-4.5",
|
|
74
|
-
"bedrock": "anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
82
|
+
"bedrock": "anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
83
|
+
"azure": "claude-sonnet-4-5-20250929"
|
|
75
84
|
},
|
|
76
85
|
"deepseek-chat": {
|
|
77
86
|
"deepseek": "deepseek-chat",
|
|
@@ -98,10 +107,6 @@
|
|
|
98
107
|
"openrouter": "google/gemini-2.0-flash-001",
|
|
99
108
|
"vertexai": "gemini-2.0-flash-001"
|
|
100
109
|
},
|
|
101
|
-
"gemini-2.0-flash-exp": {
|
|
102
|
-
"gemini": "gemini-2.0-flash-exp",
|
|
103
|
-
"vertexai": "gemini-2.0-flash-exp"
|
|
104
|
-
},
|
|
105
110
|
"gemini-2.0-flash-lite": {
|
|
106
111
|
"gemini": "gemini-2.0-flash-lite",
|
|
107
112
|
"vertexai": "gemini-2.0-flash-lite"
|
|
@@ -222,7 +227,8 @@
|
|
|
222
227
|
},
|
|
223
228
|
"gpt-4": {
|
|
224
229
|
"openai": "gpt-4",
|
|
225
|
-
"openrouter": "openai/gpt-4"
|
|
230
|
+
"openrouter": "openai/gpt-4",
|
|
231
|
+
"azure": "gpt-4"
|
|
226
232
|
},
|
|
227
233
|
"gpt-4-1106-preview": {
|
|
228
234
|
"openai": "gpt-4-1106-preview",
|
|
@@ -238,31 +244,38 @@
|
|
|
238
244
|
},
|
|
239
245
|
"gpt-4.1": {
|
|
240
246
|
"openai": "gpt-4.1",
|
|
241
|
-
"openrouter": "openai/gpt-4.1"
|
|
247
|
+
"openrouter": "openai/gpt-4.1",
|
|
248
|
+
"azure": "gpt-4.1"
|
|
242
249
|
},
|
|
243
250
|
"gpt-4.1-mini": {
|
|
244
251
|
"openai": "gpt-4.1-mini",
|
|
245
|
-
"openrouter": "openai/gpt-4.1-mini"
|
|
252
|
+
"openrouter": "openai/gpt-4.1-mini",
|
|
253
|
+
"azure": "gpt-4.1-mini"
|
|
246
254
|
},
|
|
247
255
|
"gpt-4.1-nano": {
|
|
248
256
|
"openai": "gpt-4.1-nano",
|
|
249
|
-
"openrouter": "openai/gpt-4.1-nano"
|
|
257
|
+
"openrouter": "openai/gpt-4.1-nano",
|
|
258
|
+
"azure": "gpt-4.1-nano"
|
|
250
259
|
},
|
|
251
260
|
"gpt-4o": {
|
|
252
261
|
"openai": "gpt-4o",
|
|
253
|
-
"openrouter": "openai/gpt-4o"
|
|
262
|
+
"openrouter": "openai/gpt-4o",
|
|
263
|
+
"azure": "gpt-4o"
|
|
254
264
|
},
|
|
255
265
|
"gpt-4o-2024-05-13": {
|
|
256
266
|
"openai": "gpt-4o-2024-05-13",
|
|
257
|
-
"openrouter": "openai/gpt-4o-2024-05-13"
|
|
267
|
+
"openrouter": "openai/gpt-4o-2024-05-13",
|
|
268
|
+
"azure": "gpt-4o-2024-05-13"
|
|
258
269
|
},
|
|
259
270
|
"gpt-4o-2024-08-06": {
|
|
260
271
|
"openai": "gpt-4o-2024-08-06",
|
|
261
|
-
"openrouter": "openai/gpt-4o-2024-08-06"
|
|
272
|
+
"openrouter": "openai/gpt-4o-2024-08-06",
|
|
273
|
+
"azure": "gpt-4o-2024-08-06"
|
|
262
274
|
},
|
|
263
275
|
"gpt-4o-2024-11-20": {
|
|
264
276
|
"openai": "gpt-4o-2024-11-20",
|
|
265
|
-
"openrouter": "openai/gpt-4o-2024-11-20"
|
|
277
|
+
"openrouter": "openai/gpt-4o-2024-11-20",
|
|
278
|
+
"azure": "gpt-4o-2024-11-20"
|
|
266
279
|
},
|
|
267
280
|
"gpt-4o-audio-preview": {
|
|
268
281
|
"openai": "gpt-4o-audio-preview",
|
|
@@ -270,11 +283,13 @@
|
|
|
270
283
|
},
|
|
271
284
|
"gpt-4o-mini": {
|
|
272
285
|
"openai": "gpt-4o-mini",
|
|
273
|
-
"openrouter": "openai/gpt-4o-mini"
|
|
286
|
+
"openrouter": "openai/gpt-4o-mini",
|
|
287
|
+
"azure": "gpt-4o-mini"
|
|
274
288
|
},
|
|
275
289
|
"gpt-4o-mini-2024-07-18": {
|
|
276
290
|
"openai": "gpt-4o-mini-2024-07-18",
|
|
277
|
-
"openrouter": "openai/gpt-4o-mini-2024-07-18"
|
|
291
|
+
"openrouter": "openai/gpt-4o-mini-2024-07-18",
|
|
292
|
+
"azure": "gpt-4o-mini-2024-07-18"
|
|
278
293
|
},
|
|
279
294
|
"gpt-4o-mini-search-preview": {
|
|
280
295
|
"openai": "gpt-4o-mini-search-preview",
|
|
@@ -324,10 +339,6 @@
|
|
|
324
339
|
"openai": "gpt-5.2",
|
|
325
340
|
"openrouter": "openai/gpt-5.2"
|
|
326
341
|
},
|
|
327
|
-
"gpt-5.2-chat": {
|
|
328
|
-
"openai": "gpt-5.2-chat-latest",
|
|
329
|
-
"openrouter": "openai/gpt-5.2-chat-latest"
|
|
330
|
-
},
|
|
331
342
|
"gpt-5.2-codex": {
|
|
332
343
|
"openai": "gpt-5.2-codex",
|
|
333
344
|
"openrouter": "openai/gpt-5.2-codex"
|
|
@@ -336,13 +347,22 @@
|
|
|
336
347
|
"openai": "gpt-5.2-pro",
|
|
337
348
|
"openrouter": "openai/gpt-5.2-pro"
|
|
338
349
|
},
|
|
350
|
+
"gpt-audio": {
|
|
351
|
+
"openai": "gpt-audio",
|
|
352
|
+
"openrouter": "openai/gpt-audio"
|
|
353
|
+
},
|
|
354
|
+
"gpt-audio-mini": {
|
|
355
|
+
"openai": "gpt-audio-mini",
|
|
356
|
+
"openrouter": "openai/gpt-audio-mini"
|
|
357
|
+
},
|
|
339
358
|
"o1": {
|
|
340
359
|
"openai": "o1",
|
|
341
360
|
"openrouter": "openai/o1"
|
|
342
361
|
},
|
|
343
362
|
"o1-pro": {
|
|
344
363
|
"openai": "o1-pro",
|
|
345
|
-
"openrouter": "openai/o1-pro"
|
|
364
|
+
"openrouter": "openai/o1-pro",
|
|
365
|
+
"azure": "o1-pro"
|
|
346
366
|
},
|
|
347
367
|
"o3": {
|
|
348
368
|
"openai": "o3",
|
|
@@ -354,7 +374,8 @@
|
|
|
354
374
|
},
|
|
355
375
|
"o3-mini": {
|
|
356
376
|
"openai": "o3-mini",
|
|
357
|
-
"openrouter": "openai/o3-mini"
|
|
377
|
+
"openrouter": "openai/o3-mini",
|
|
378
|
+
"azure": "o3-mini"
|
|
358
379
|
},
|
|
359
380
|
"o3-pro": {
|
|
360
381
|
"openai": "o3-pro",
|
|
@@ -362,14 +383,11 @@
|
|
|
362
383
|
},
|
|
363
384
|
"o4-mini": {
|
|
364
385
|
"openai": "o4-mini",
|
|
365
|
-
"openrouter": "openai/o4-mini"
|
|
386
|
+
"openrouter": "openai/o4-mini",
|
|
387
|
+
"azure": "o4-mini"
|
|
366
388
|
},
|
|
367
389
|
"o4-mini-deep-research": {
|
|
368
390
|
"openai": "o4-mini-deep-research",
|
|
369
391
|
"openrouter": "openai/o4-mini-deep-research"
|
|
370
|
-
},
|
|
371
|
-
"text-embedding-004": {
|
|
372
|
-
"gemini": "text-embedding-004",
|
|
373
|
-
"vertexai": "text-embedding-004"
|
|
374
392
|
}
|
|
375
393
|
}
|