llm.rb 15.0.0 → 15.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f32731cd1933be73ca1297c6733b25ccc3873cb2d617bfbf5ae4d556714aaca
4
- data.tar.gz: cbe78b1776b8c4df9f60341b901436fbd69783fca251b3656cf4cd9851815b49
3
+ metadata.gz: 3ce996689c82e7a0d62a7f09ab2545d10b36462fc75cc2feeb57ce26f6523585
4
+ data.tar.gz: 74cf140b0fd11b077aea57bde027f0b00cf67f4e099ad35778bc5042b958469b
5
5
  SHA512:
6
- metadata.gz: 01e6e7d10f5359ae9ba5f7643c4c0679ef22c99deed608e45b44d2afd52f42f2069a317b4c56abd76bc569d40f070d8dc74a5d981c727fc23871a5d4df69aea6
7
- data.tar.gz: d2c61e22df6a2f5b4c206f4f13f3b8ec3b0e4014f1d0608ed78adadf598e00b576fd486c01040f655adec0af3efb8c3e1685fcb22ef31038a0917d13481072a2
6
+ metadata.gz: 4274138e7054d9db54e121dcad674f307b0e1bdbfc568ac16bb3b13181e873a77a57d19f4d2eef2479216555ba0ed9213fa7372cf051d4fa73f6fe73dae594cd
7
+ data.tar.gz: 12219c398f482c7f949bc9964038a2597e74a592b78023b101e96a337605c2e2fb8963f5526fbd4ca8f1ffd8b14b843e1a10334369d2f62a1e393eb409f4821d
data/CHANGELOG.md CHANGED
@@ -15,6 +15,35 @@
15
15
 
16
16
  ## What's next
17
17
 
18
+ ## v15.0.1
19
+
20
+ Changes since `v15.0.0`.
21
+
22
+ This release fixes agent `set` handling of single `Symbol`/`Proc`
23
+ values and resolves ORM options as `Symbol`s through the bound record
24
+ instead of the `LLM::Agent` instance.
25
+
26
+ ### Agent
27
+
28
+ * **agent: fix `set (skills|tools): Symbol|Proc`** <br>
29
+ The `skills`, `tools`, `confirm`, and `schema` class accessors now
30
+ consistently resolve a single `Symbol` or `Proc` lazily at agent
31
+ initialization, so `LLM::Agent.set(skills: proc { [...] })` and
32
+ `LLM::Agent.set(tools: :tools)` no longer raise. The `set` family
33
+ shares one `single_callable?` helper rather than each accessor
34
+ duplicating its own logic.
35
+
36
+ ### Fix
37
+
38
+ * **orm: resolve a `Symbol` through the bound record** <br>
39
+ When an ORM model using `acts_as_agent` (ActiveRecord), `plugin :agent`
40
+ (Sequel), or `acts_as_llm` / `plugin :llm` configures an option as a
41
+ `Symbol`, that symbol is now resolved on the model instance (or its
42
+ bound record) instead of the `LLM::Agent` instance. The contexts and
43
+ agents built by the wrappers are now bound to the record, so a model like
44
+ `agent.set :tools` with a `tools` method on the record works as
45
+ expected.
46
+
18
47
  ## v15.0.0
19
48
 
20
49
  Changes since `v14.0.0`.
@@ -126,7 +126,7 @@ module LLM::ActiveRecord
126
126
  @ctx ||= begin
127
127
  options = self.class.llm_plugin_options
128
128
  params = Utils.resolve_options(self, options[:context], EMPTY_HASH).dup
129
- ctx = self.class.agent.new(llm, params.compact)
129
+ ctx = self.class.agent.new(llm, params.compact.merge(record: self))
130
130
  columns = Utils.columns(options)
131
131
  data = self[columns[:data_column]]
132
132
  if data.nil? || data == ""
@@ -243,7 +243,7 @@ module LLM::ActiveRecord
243
243
  options = self.class.llm_plugin_options
244
244
  columns = Utils.columns(options)
245
245
  params = Utils.resolve_options(self, options[:context], EMPTY_HASH).dup
246
- ctx = LLM::Context.new(llm, params.compact)
246
+ ctx = LLM::Context.new(llm, params.compact.merge(record: self))
247
247
  data = self[columns[:data_column]]
248
248
  if data.nil? || data == ""
249
249
  ctx
data/lib/llm/agent.rb CHANGED
@@ -57,14 +57,16 @@ module LLM
57
57
  ##
58
58
  # Ugh :)
59
59
  # @api private
60
- FIELDS = %i[name description
60
+ FIELDS = %i[record
61
+ name description
61
62
  path tool_budget
62
63
  retry_budget model
63
64
  skills schema
64
65
  tracer stream
65
66
  tools concurrency
66
67
  instructions confirm]
67
- IVARS = %i[name description
68
+ IVARS = %i[record
69
+ name description
68
70
  path tool_budget
69
71
  tracer concurrency
70
72
  instructions confirm]
@@ -169,10 +171,21 @@ module LLM
169
171
  # @return [String, nil]
170
172
  # Returns the current model when no argument is provided
171
173
  def self.model(model = nil, &block)
172
- return @model if model.nil? && !block
174
+ return @model if model.nil? and !block
173
175
  @model = block || model
174
176
  end
175
177
 
178
+ ##
179
+ # Set or get the default schema
180
+ # @param [#to_json, nil] schema
181
+ # The schema
182
+ # @return [#to_json, nil]
183
+ # Returns the current schema when no argument is provided
184
+ def self.schema(schema = nil, &block)
185
+ return @schema if schema.nil? and !block
186
+ @schema = block || schema
187
+ end
188
+
176
189
  ##
177
190
  # Set or get the default tools
178
191
  # @param [Array<LLM::Function>, nil] tools
@@ -180,11 +193,13 @@ module LLM
180
193
  # @return [Array<LLM::Function>]
181
194
  # Returns the current tools when no argument is provided
182
195
  def self.tools(*tools, &block)
183
- return @tools || [] if tools.empty? && !block
184
- if tools.size == 1 and tools.grep(Symbol).any?
196
+ return @tools || [] if tools.empty? and !block
197
+ if block
198
+ @tools = block
199
+ elsif single_callable?(tools)
185
200
  @tools = tools.first
186
201
  else
187
- @tools = block || tools.flatten
202
+ @tools = tools.flatten
188
203
  end
189
204
  end
190
205
 
@@ -195,23 +210,46 @@ module LLM
195
210
  # @return [Array<String>, nil]
196
211
  # Returns the current skills when no argument is provided
197
212
  def self.skills(*skills, &block)
198
- return @skills if skills.empty? && !block
199
- if skills.size == 1 and skills.grep(Symbol).any?
213
+ return @skills if skills.empty? and !block
214
+ if block
215
+ @skills = block
216
+ elsif single_callable?(skills)
200
217
  @skills = skills.first
201
218
  else
202
- @skills = block || skills.flatten
219
+ @skills = skills.flatten
203
220
  end
204
221
  end
205
222
 
206
223
  ##
207
- # Set or get the default schema
208
- # @param [#to_json, nil] schema
209
- # The schema
210
- # @return [#to_json, nil]
211
- # Returns the current schema when no argument is provided
212
- def self.schema(schema = nil, &block)
213
- return @schema if schema.nil? && !block
214
- @schema = block || schema
224
+ # Set or get the tool names that require confirmation before they can run.
225
+ #
226
+ # When a single Symbol is given, it is stored as-is and resolved at
227
+ # initialization time by calling the method with that name on the agent
228
+ # instance. This allows dynamic tool confirmation lists.
229
+ #
230
+ # @example
231
+ # class MyAgent < LLM::Agent
232
+ # confirm :tools_that_need_confirmation
233
+ #
234
+ # def tools_that_need_confirmation
235
+ # some_condition ? %w[delete destroy] : %w[delete]
236
+ # end
237
+ # end
238
+ #
239
+ # @param [String, Symbol, Array<String, Symbol>, Proc] tool_names
240
+ # One or more tool names.
241
+ # @param [Proc] block
242
+ # An optional, lazy-evaluated Proc
243
+ # @return [Array<String>, Proc, Symbol, nil]
244
+ def self.confirm(*tool_names, &block)
245
+ return @confirm if tool_names.empty? and !block
246
+ if block
247
+ @confirm = block
248
+ elsif single_callable?(tool_names)
249
+ @confirm = tool_names.first
250
+ else
251
+ @confirm = tool_names.flatten.map(&:to_s)
252
+ end
215
253
  end
216
254
 
217
255
  ##
@@ -285,36 +323,6 @@ module LLM
285
323
  @stream = block || stream
286
324
  end
287
325
 
288
- ##
289
- # Set or get the tool names that require confirmation before they can run.
290
- #
291
- # When a single Symbol is given, it is stored as-is and resolved at
292
- # initialization time by calling the method with that name on the agent
293
- # instance. This allows dynamic tool confirmation lists.
294
- #
295
- # @example
296
- # class MyAgent < LLM::Agent
297
- # confirm :tools_that_need_confirmation
298
- #
299
- # def tools_that_need_confirmation
300
- # some_condition ? %w[delete destroy] : %w[delete]
301
- # end
302
- # end
303
- #
304
- # @param [String, Symbol, Array<String, Symbol>, Proc] tool_names
305
- # One or more tool names.
306
- # @param [Proc] block
307
- # An optional, lazy-evaluated Proc
308
- # @return [Array<String>, Proc, Symbol, nil]
309
- def self.confirm(*tool_names, &block)
310
- return @confirm if tool_names.empty? && !block
311
- if tool_names.size == 1 && tool_names.grep(Symbol).any?
312
- @confirm = tool_names.first
313
- else
314
- @confirm = block || tool_names.flatten.map(&:to_s)
315
- end
316
- end
317
-
318
326
  ##
319
327
  # Set the file path where an agent's memory
320
328
  # can be restored from, and written to.
@@ -370,6 +378,13 @@ module LLM
370
378
  end
371
379
  end
372
380
 
381
+ ##
382
+ # @api private
383
+ def self.single_callable?(callable)
384
+ callable.size == 1 and (Proc === callable[0] or Symbol === callable[0])
385
+ end
386
+ private_class_method :single_callable?
387
+
373
388
  ##
374
389
  # @param [LLM::Provider] llm
375
390
  # A provider
@@ -389,7 +404,7 @@ module LLM
389
404
  @llm = llm
390
405
  fields, fields_ivar = FIELDS, IVARS
391
406
  fields.each do |field|
392
- resolvable = params.key?(field) ? params.delete(field) : self.class.public_send(field)
407
+ resolvable = params.key?(field) ? params.delete(field) : (self.class.respond_to?(field) ? self.class.public_send(field) : nil)
393
408
  resolve_symbol = !%i[concurrency].include?(field)
394
409
  resolved = resolvable != nil ? resolve_option(self, resolvable, resolve_symbol:) : resolvable
395
410
  resolved = [*resolved].map(&:to_s) if field == :confirm && resolved
@@ -420,6 +435,13 @@ module LLM
420
435
  @path
421
436
  end
422
437
 
438
+ ##
439
+ # Returns the ORM record this agent is bound to, or nil.
440
+ # @return [Object, nil]
441
+ def record
442
+ @record
443
+ end
444
+
423
445
  ##
424
446
  # Returns the agent's description
425
447
  # @return [String, nil]
data/lib/llm/context.rb CHANGED
@@ -46,7 +46,7 @@ module LLM
46
46
  # @api private
47
47
  # @return [Array<Symbol>]
48
48
  def self.params
49
- %w[guard retry_budget concurrency transformer compactor]
49
+ %w[guard retry_budget concurrency transformer compactor record]
50
50
  end
51
51
 
52
52
  ##
@@ -64,6 +64,11 @@ module LLM
64
64
  # @return [Symbol]
65
65
  attr_reader :mode
66
66
 
67
+ ##
68
+ # Returns the ORM record this context is bound to, or nil.
69
+ # @return [Object, nil]
70
+ attr_reader :record
71
+
67
72
  ##
68
73
  # @param [LLM::Provider] llm
69
74
  # A provider
@@ -95,6 +100,7 @@ module LLM
95
100
  def initialize(llm, params = {})
96
101
  params = {}.merge!(params)
97
102
  @llm = llm
103
+ @record = params.delete(:record)
98
104
  @mode = params.delete(:mode) || (llm.name == :openai ? :responses : :completions)
99
105
  tools = [*params.delete(:tools), *load_skills(params.delete(:skills))]
100
106
  @params = {model: llm.default_model, schema: nil}.compact.merge!(params)
@@ -88,7 +88,7 @@ module LLM::Sequel
88
88
  options = self.class.llm_plugin_options
89
89
  columns = Agent::Utils.columns(options)
90
90
  params = Agent::Utils.resolve_options(self, options[:context], Agent::EMPTY_HASH).dup
91
- ctx = self.class.agent.new(llm, params.compact)
91
+ ctx = self.class.agent.new(llm, params.compact.merge(record: self))
92
92
  data = self[columns[:data_column]]
93
93
  if data.nil? || data == ""
94
94
  ctx
@@ -343,7 +343,7 @@ module LLM::Sequel
343
343
  options = self.class.llm_plugin_options
344
344
  columns = Utils.columns(options)
345
345
  params = Utils.resolve_options(self, options[:context], Plugin::EMPTY_HASH).dup
346
- ctx = LLM::Context.new(llm, params.compact)
346
+ ctx = LLM::Context.new(llm, params.compact.merge(record: self))
347
347
  data = self[columns[:data_column]]
348
348
  if data.nil? || data == ""
349
349
  ctx
data/lib/llm/utils.rb CHANGED
@@ -20,12 +20,34 @@ module LLM
20
20
  def resolve_option(obj, option, resolve_symbol: true)
21
21
  case option
22
22
  when Proc then obj.instance_exec(&option)
23
- when Symbol then resolve_symbol ? obj.send(option) : option
23
+ when Symbol
24
+ if resolve_symbol
25
+ if obj.respond_to?(option, true) and record?(obj)
26
+ obj.send(option)
27
+ elsif obj.respond_to?(:record) and obj.record.respond_to?(option, true)
28
+ obj.record.send(option)
29
+ elsif obj.respond_to?(option, true)
30
+ obj.send(option)
31
+ else
32
+ raise ArgumentError, "unable to resolve #{option}"
33
+ end
34
+ else
35
+ option
36
+ end
24
37
  when Hash then option.dup
25
38
  else option
26
39
  end
27
40
  end
28
41
 
42
+ ##
43
+ # Returns true when `obj` is an ActiveRecord or Sequel model.
44
+ # @param [Object] obj
45
+ # @api private
46
+ def record?(obj)
47
+ (defined?(::ActiveRecord::Base) and ::ActiveRecord::Base === obj) or
48
+ (defined?(::Sequel::Model) and ::Sequel::Model === obj)
49
+ end
50
+
29
51
  ##
30
52
  # Normalizes an HTTP API base path.
31
53
  #
data/lib/llm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "15.0.0"
4
+ VERSION = "15.0.1"
5
5
  end
data/llm.gemspec CHANGED
@@ -21,7 +21,7 @@ DESCRIPTION
21
21
  spec.license = "MIT"
22
22
  spec.required_ruby_version = ">= 3.3.0"
23
23
 
24
- spec.homepage = "https://r.uby.dev/llm/"
24
+ spec.homepage = "https://r.uby.dev"
25
25
  spec.metadata["homepage_uri"] = spec.homepage
26
26
  spec.metadata["source_code_uri"] = "https://github.com/r-uby-dev/llm"
27
27
  spec.metadata["documentation_uri"] = spec.homepage
@@ -38,9 +38,15 @@ DESCRIPTION
38
38
  spec.executables = ["llm.rb"]
39
39
  spec.require_paths = ["lib"]
40
40
  spec.post_install_message = "\n" \
41
- "Learn more about llm.rb on GitHub:" \
41
+ "Got a question about llm.rb? " \
42
42
  "\n" \
43
- "https://github.com/r-uby-dev/llm#readme" \
43
+ "Ask our chatbot." \
44
+ "\n" \
45
+ "It is connected to the official GitHub repository." \
46
+ "\n" \
47
+ "100% free to use." \
48
+ "\n" \
49
+ "https://r.uby.dev" \
44
50
  "\n\n"
45
51
 
46
52
  spec.add_development_dependency "webmock", "~> 3.24.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 15.0.0
4
+ version: 15.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gleeson
@@ -644,19 +644,16 @@ files:
644
644
  - lib/sequel/plugins/agent.rb
645
645
  - lib/sequel/plugins/llm.rb
646
646
  - llm.gemspec
647
- homepage: https://r.uby.dev/llm/
647
+ homepage: https://r.uby.dev
648
648
  licenses:
649
649
  - MIT
650
650
  metadata:
651
- homepage_uri: https://r.uby.dev/llm/
651
+ homepage_uri: https://r.uby.dev
652
652
  source_code_uri: https://github.com/r-uby-dev/llm
653
- documentation_uri: https://r.uby.dev/llm/
653
+ documentation_uri: https://r.uby.dev
654
654
  changelog_uri: https://github.com/r-uby-dev/llm/blob/main/CHANGELOG.md
655
- post_install_message: |2+
656
-
657
- Learn more about llm.rb on GitHub:
658
- https://github.com/r-uby-dev/llm#readme
659
-
655
+ post_install_message: "\nGot a question about llm.rb? \nAsk our chatbot.\nIt is connected
656
+ to the official GitHub repository.\n100% free to use.\nhttps://r.uby.dev\n\n"
660
657
  rdoc_options: []
661
658
  require_paths:
662
659
  - lib
@@ -675,4 +672,3 @@ rubygems_version: 4.0.16
675
672
  specification_version: 4
676
673
  summary: Ruby's capable AI runtime
677
674
  test_files: []
678
- ...