action_ai 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 626b0d7eb4998d7c208dc99eb8e60932a3006988176bbd784935f1e00437b485
4
- data.tar.gz: 97512e0c8fd015af6235d61b31ed36e7030702ddaf4b436b18b462b39013bd55
3
+ metadata.gz: 3fa6d979d2013779ecfe3932a2c1bb04158dd99072ef5eb4b802f2ece910b34d
4
+ data.tar.gz: 46cb4d1ad9cca9f63c7af8d575abe3ad23fef1359d2f5d445617662e071b1ef6
5
5
  SHA512:
6
- metadata.gz: 5721c21b4234b75cc9afce08eef6e5df89b4cb3364b75df8bbc3735c9ed3735d19fe6186d1893b678e5bfb9cddcbe15511909babd0ab7851f7663fd2c4fb1011
7
- data.tar.gz: 025dec0b60c2b398450fe31174789df35689bef9a48a5c77627619c9df3c6fd2de62b153c968430a2135b0ca2cc9ee73e29049e502e5b34acbbd1217ba523fa4
6
+ metadata.gz: 0de59f1a428c9be0cd4e1926a8f1d59f1cca46c8fbdc77248fe34ab21c755412c39db3bfabcaa86855be878e707cfdb61e2d74a983c7ae2625de50eed3b96184
7
+ data.tar.gz: 4e77dc58ce51dc7a8d9027cabdab9465b82b3e9abccce7bbfb3591f8fd12cd36dbd4f28953118ec41ec3bc9f64b0569045d3e191c362ba8dbdcb9decaa9312e6
data/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
5
 
6
+ ## [0.4.0] - 2026-10-20
7
+
8
+ ### Changed
9
+
10
+ - Moved agent-level configuration to RubyLLM agents.
11
+
12
+ ### Added
13
+
14
+ #### Configuration
15
+
16
+ - RubyLLM agent configuration DSL support on `ActionAI::Agent`: class methods `model`, `instructions`, `tools`, `tool_options`, `server_tools`, `thinking`, `citations`, `caching`, `compaction`, `provider_options`, `headers`, `schema`, `fallbacks`, `context`, `chat_model`, `inputs`, `temperature`, `max_output_tokens`, and `end_user` configure a private, internally managed `RubyLLM::Agent` subclass.
17
+ `chat` is built via that agent’s `.chat` instead of plain `RubyLLM.chat`.
18
+ - Parameters set through `ActionAI::Parameterized` are forwarded as the agent’s runtime `inputs`.
19
+
20
+ #### Schemas
21
+
22
+ - Consider Active Record _stored attributes_ for schema inference.
23
+ The type is `string` because `ActiveRecord::Store` doesn’t provide type casting. The same is true for the attributes a model will get from a user-filled form, so the behavior is consistent. One should override attribute setters for type casting if needed.
24
+
25
+ ### Removed
26
+
27
+ #### Configuration
28
+
29
+ - `default`/`default_options=` class method and `default_params` class attribute.
30
+ Use the RubyLLM agent DSL (`model`, `instructions`, and so on) instead.
31
+
32
+
6
33
  ## [0.3.0] - 2026-10-13
7
34
 
8
35
  ### Changed
data/README.rdoc CHANGED
@@ -22,7 +22,7 @@ automatically at the end of the action unless you have already called it.
22
22
  This can be as simple as:
23
23
 
24
24
  class Generator < ApplicationAI
25
- default model: "gpt-4o"
25
+ model "gpt-6.1-sol"
26
26
 
27
27
  def code(task, language)
28
28
  @task = task
@@ -88,34 +88,60 @@ You can also chain multiple agent actions to compose a small workflow before rea
88
88
  WorkflowAgent.collect("release notes").refine(style: "bullet list").content
89
89
  # => collected release notes refined as bullet list
90
90
 
91
- == Setting defaults
91
+ == Configuration
92
+
93
+ Your agent class uses the full +RubyLLM::Agent+ configuration DSL
94
+ directly. You never need to define or reference a +RubyLLM::Agent+
95
+ subclass yourself; just call +model+, +instructions+, +tools+, +schema+, and
96
+ the other +RubyLLM::Agent+ class macros right on your Action AI agent:
97
+
98
+ class Generator < ApplicationAI
99
+ model "gpt-6.1-sol"
100
+ instructions "You are a concise support assistant."
101
+ tools SearchDocs, LookupAccount
102
+
103
+ def code(task, language)
104
+ @task = task
105
+ @language = language
106
+ end
107
+ end
108
+
109
+ If you declare +inputs+, use ActionAI::Parameterized to supply them at call
110
+ time; the parameters given to +with+ are forwarded as the agent's runtime
111
+ inputs:
92
112
 
93
- It is possible to set default values that will be used in every method in your
94
- Action AI Agent class. To implement this functionality, you just call the public
95
- class method +default+ which you get for free from ActionAI::Agent.
96
- This method accepts a Hash as the parameter. You can use any options
97
- supported by +RubyLLM::Chat+, such as +:provider+ and +:model+.
98
- Finally, it is also possible to pass in a
99
- Proc that will get evaluated when it is needed.
113
+ class Generator < ApplicationAI
114
+ inputs :workspace
115
+
116
+ instructions { "You are helping #{workspace.name}" }
117
+ end
100
118
 
101
- Note that every value you set with this method will get overwritten if you use the
102
- same key in your agent method.
119
+ Generator.with(workspace:).code("Parse CSV", :ruby)
103
120
 
104
- Example:
121
+ Subclasses inherit their parent's RubyLLM agent configuration and can extend
122
+ or override it, the same way +RubyLLM::Agent+ subclasses do:
123
+
124
+ class ApplicationAI < ActionAI::Agent
125
+ model "gpt-6-luna", provider: :openai
126
+ end
105
127
 
106
128
  class Generator < ApplicationAI
107
- default model: proc { Current.user.preferred_model }
129
+ model { Current.user.preferred_model }
108
130
  end
109
131
 
110
- == Configuration
132
+ The Agent class has a few settings unrelated to the RubyLLM agent DSL that are specified on the class level.
133
+ Here's an example:
134
+
135
+ ActionAI::Agent.execute_later_queue_name = :ai_agents
111
136
 
112
- The Agent class has the full list of configuration options. Here's an example:
137
+ * +logger+ — the logger is used for generating information on prompt execution if available.
138
+ Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
113
139
 
114
- ActionAI::Agent.default_options = {
115
- provider: :openai,
116
- model: "gpt-4o-mini"
117
- }
140
+ * +execution_job+ — The job class used with +later+. Agents can set this to use a
141
+ custom execution job. Defaults to +ActionAI::ExecutionJob+.
118
142
 
143
+ * +execute_later_queue_name+ — The queue name used by +later+ with the default
144
+ +execution_job+. Agents can set this to use a custom queue name.
119
145
 
120
146
  == Structured output (data schemas)
121
147
 
@@ -4,6 +4,9 @@ require "ruby_llm"
4
4
  require "active_support/core_ext/string/inflections"
5
5
  require "active_support/core_ext/hash/except"
6
6
  require "active_support/core_ext/module/anonymous"
7
+ require "active_support/core_ext/module/delegation"
8
+ require "active_support/core_ext/object/try"
9
+ require "active_support/core_ext/object/with_options"
7
10
  require "memery"
8
11
 
9
12
  require "action_ai/log_subscriber"
@@ -26,12 +29,11 @@ module ActionAI
26
29
  # the prompt views, options on the AI model used such as the <tt>:model</tt> ID, and attachments.
27
30
  #
28
31
  # class ApplicationAI < ActionAI::Agent
29
- # default model: 'gpt-4o-mini',
30
- # provider: :openai
32
+ # model "gpt-6-luna", provider: :openai
31
33
  # end
32
34
  #
33
35
  # class Generator < ApplicationAI
34
- # default model: 'gpt-4o'
36
+ # model "gpt-6.1-sol"
35
37
  #
36
38
  # def code(task, language)
37
39
  # @task = task
@@ -155,33 +157,40 @@ module ActionAI
155
157
  # end
156
158
  # end
157
159
  #
158
- # == Default \Hash
160
+ # == Configuration
159
161
  #
160
- # Action AI provides some intelligent defaults for your AI interactions, these are usually specified in a
161
- # default method inside the class definition:
162
+ # Action AI agents configure their model, instructions, tools, and other
163
+ # chat options using the +RubyLLM::Agent+ class DSL directly. You never
164
+ # need to define or reference a +RubyLLM::Agent+ subclass yourself; just
165
+ # call +model+, +instructions+, +tools+, +schema+, and the other
166
+ # +RubyLLM::Agent+ class macros right on your +ActionAI::Agent+ subclass:
162
167
  #
163
168
  # class Generator < ApplicationAI
164
- # default model: 'gpt-4o'
169
+ # model "gpt-6.1-sol"
170
+ # instructions "You are a concise support assistant."
171
+ # tools SearchDocs, LookupAccount
165
172
  # end
166
173
  #
167
- # You can pass in any config value that a +RubyLLM::Chat+ accepts.
174
+ # Under the hood, Action AI manages a private +RubyLLM::Agent+ subclass for you
175
+ # and builds +chat+ from it, so instructions, tools, schema, fallbacks, caching,
176
+ # and every other declaration are honored exactly as documented on
177
+ # <tt>RubyLLM::Agent</tt>. Any params set through ActionAI::Parameterized
178
+ # (<tt>Generator.with(...)</tt>) are forwarded as its runtime +inputs+.
168
179
  #
169
- # Finally, Action AI also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
170
- # so you can define methods that evaluate as the message is being generated:
180
+ # Configuration is inherited the same way +RubyLLM::Agent+ subclasses inherit
181
+ # it, so a shared +ApplicationAI+ base class can declare a default +model+ and
182
+ # +instructions+ that every agent subclass then extends or overrides:
171
183
  #
172
- # class Generator < ApplicationAI
173
- # default model: -> { Current.user.preferred_model },
174
- # api_key: proc { Current.user.ai_api_key }
184
+ # class ApplicationAI < ActionAI::Agent
185
+ # model "gpt-6-luna", provider: :openai
175
186
  # end
176
187
  #
177
- # Note that the proc/lambda is evaluated right at the start of the prompt generation, so if you
178
- # set something in the default hash using a proc, and then set the same thing inside of your
179
- # agent method, it will get overwritten by the agent method.
180
- #
181
- # It is also possible to set these default options that will be used in all agents through
182
- # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
188
+ # class Generator < ApplicationAI
189
+ # model { Current.user.preferred_model }
190
+ # end
183
191
  #
184
- # config.action_ai.default_options = { provider: :openai, model: "gpt-4o-mini" }
192
+ # ::rescue_from continues to handle agent-action level exceptions; use it as
193
+ # usual, there's no separate chat-level rescue mechanism to reach for.
185
194
  #
186
195
  # == \Callbacks
187
196
  #
@@ -265,9 +274,6 @@ module ActionAI
265
274
  # These options are specified on the class level, like
266
275
  # <tt>ActionAI::Agent.raise_execution_errors = true</tt>
267
276
  #
268
- # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as
269
- # per the above section.
270
- #
271
277
  # * <tt>logger</tt> - the logger is used for generating information on prompt execution if available.
272
278
  # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
273
279
  #
@@ -304,11 +310,9 @@ module ActionAI
304
310
 
305
311
  helper ActionAI::PromptHelper
306
312
 
307
- class_attribute :default_params, default: {
308
- # none
309
- }.freeze
310
-
311
313
  class << self
314
+ include Memery
315
+
312
316
  # Returns the name of the current agent. This method is also being used as a path for a view lookup.
313
317
  # If this is an anonymous agent, this method will return +anonymous+ instead.
314
318
  def agent_name
@@ -318,18 +322,18 @@ module ActionAI
318
322
  attr_writer :agent_name
319
323
  alias :controller_path :agent_name
320
324
 
321
- # Allows to set defaults through app configuration:
322
- #
323
- # config.action_ai.default_options = { provider: :openai }
324
- def default(value = nil)
325
- self.default_params = default_params.merge(value).freeze if value
326
- default_params
325
+ # The private, per-agent-class +RubyLLM::Agent+ subclass,
326
+ # lazily created and configured via its own class macros
327
+ # (+model+, +instructions+, +tools+, +schema+, and so on).
328
+ memoize def agent_class
329
+ Class.new superclass.try(:agent_class) || RubyLLM::Agent
327
330
  end
328
- alias :default_options= :default
329
331
 
330
332
  private
331
333
  def method_missing(method_name, ...)
332
- if action_methods.include?(method_name.name)
334
+ if agent_class.respond_to? method_name # configuration
335
+ agent_class.public_send(method_name, ...)
336
+ elsif action_methods.include? method_name.name # actions
333
337
  Interaction.new(self, method_name, ...)
334
338
  else
335
339
  super
@@ -337,13 +341,22 @@ module ActionAI
337
341
  end
338
342
 
339
343
  def respond_to_missing?(method, include_all = false)
340
- action_methods.include?(method.name) || super
344
+ agent_class.respond_to? method or
345
+ action_methods.include? method.name or
346
+ super
341
347
  end
342
348
  end
343
349
 
344
350
  attr_internal :message
345
351
 
346
- memoize def chat = RubyLLM.chat(**apply_defaults)
352
+ memoize def agent = agent_class.new(**params
353
+ .slice(*agent_class.inputs)
354
+ )
355
+
356
+ with_options private: true do
357
+ delegate :agent_class, to: :class
358
+ delegate :chat, to: :agent
359
+ end
347
360
 
348
361
  def process(method_name, *args) # :nodoc:
349
362
  payload = {
@@ -379,10 +392,10 @@ module ActionAI
379
392
  #
380
393
  # * +:with+ - Array of file paths or URLs to attach to the prompt.
381
394
  #
382
- # You can set default model options using the ::default class method:
395
+ # You can set default model options using the ::model class method:
383
396
  #
384
397
  # class Generator < ActionAI::Agent
385
- # default model: 'gpt-4o', provider: :openai
398
+ # model "gpt-6.1-sol", provider: :openai
386
399
  # end
387
400
  #
388
401
  # It will find a template in the view paths using by default the agent name and the
@@ -392,7 +405,7 @@ module ActionAI
392
405
  # For example:
393
406
  #
394
407
  # class Generator < ActionAI::Agent
395
- # default model: 'gpt-4o'
408
+ # model "gpt-6.1-sol"
396
409
  #
397
410
  # def code(task, language)
398
411
  # @task = task
@@ -423,24 +436,6 @@ module ActionAI
423
436
  false
424
437
  end
425
438
 
426
- def apply_defaults(config = {})
427
- default_values = self.class.default.except(*config.keys).transform_values do |value|
428
- compute_default(value)
429
- end
430
-
431
- config.reverse_merge(default_values)
432
- end
433
-
434
- def compute_default(value)
435
- return value unless value.is_a?(Proc)
436
-
437
- if value.arity == 1
438
- instance_exec(self, &value)
439
- else
440
- instance_exec(&value)
441
- end
442
- end
443
-
444
439
  def use_attachments
445
440
  attachments.presence
446
441
  .tap { @attachments = nil } # reset
@@ -35,6 +35,20 @@ module ActionAI
35
35
  locking_column,
36
36
  *cache_columns,
37
37
  ].map(&:to_sym))
38
+ .merge(stored_attributes.values.reduce(:|).to_h do |name|
39
+ [ name, {
40
+ type: :string,
41
+ description: try(:human_attribute_name, name),
42
+ required: attribute_required?(name),
43
+ default: attribute_default(name),
44
+ enum: attribute_enum(name),
45
+ const: attribute_const(name),
46
+ pattern: attribute_format(name),
47
+
48
+ **attribute_length(name),
49
+ **attribute_boundaries(name),
50
+ }.compact ]
51
+ end)
38
52
  .merge(schema_reflections)
39
53
 
40
54
  memoize def schema_reflections = nested_attributes_options
@@ -144,7 +144,7 @@ module ActionAI
144
144
  ].compact.presence
145
145
  &.map(&:to_a)
146
146
  &.inject(&:&)
147
- &.then&.select(&:many?)&.first
147
+ &.then&.detect(&:many?)
148
148
 
149
149
  def attribute_const(name) = [
150
150
  *(yield if block_given?)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAI
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -1,5 +1,9 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= class_name %> < ApplicationAI
3
+ # Uncomment to configure model/instructions/tools using the RubyLLM agent DSL:
4
+ # model "gpt-6.1-sol"
5
+ # instructions "You are a helpful assistant."
6
+
3
7
  <% actions.each_with_index do |action, index| -%>
4
8
  <% if index != 0 -%>
5
9
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
@@ -182,8 +182,8 @@ licenses:
182
182
  - MIT
183
183
  metadata:
184
184
  bug_tracker_uri: "https://github.com/Alexander-Senko/action_ai/issues"
185
- changelog_uri: "https://github.com/Alexander-Senko/action_ai/blob/v0.3.0/CHANGELOG.md"
186
- source_code_uri: "https://github.com/Alexander-Senko/action_ai/tree/v0.3.0"
185
+ changelog_uri: "https://github.com/Alexander-Senko/action_ai/blob/v0.4.0/CHANGELOG.md"
186
+ source_code_uri: "https://github.com/Alexander-Senko/action_ai/tree/v0.4.0"
187
187
  rubygems_mfa_required: "true"
188
188
  rdoc_options: []
189
189
  require_paths: