ruby_llm-agents 1.2.0 → 1.2.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: a14dc618496842dd6db2fc197ab5c4f4190fb635b1bad8765a0983cc00e2550d
4
- data.tar.gz: 22132d6e59964206dccb41db60dd3a9939217d47d6925074523bc1027173f4ed
3
+ metadata.gz: b6f99247af4fcb9e76776ab2658bac4914d5b869423258bf3827583212447146
4
+ data.tar.gz: a6927cced434962ed4b923717d7fd582e983c3091ac8172554afb68671a2514d
5
5
  SHA512:
6
- metadata.gz: c4936cf3773b4864bb9d1dd8031fb23e8321435bc4c1730ac088f299cc60f5a88992496b317f3391a131a303a6c82666ca7c4d1b77e4ea7da2f8c033e01a8c43
7
- data.tar.gz: ca34cddc439b2f80652f9748dc62bd5fa1d365feac68101f7567e15f1bc5c2fef4dcec97e552b1dde7a20744782143c1ef5bffc2c7aaa97083b7d2f36bcf600c
6
+ metadata.gz: c98ae91fde73edf152ac4efc5665fe5da30e17ec14ff9f2f9128102bf4665530f55196e705dc371f0dac2354af4ab7a2102be9c04b3b7ec2e8331b7b5445ce23
7
+ data.tar.gz: af36432d85c4ac5ee6219eba846411dd4ffaa0ba8d8c9998550ea3d3e757fd125fa8a2bea3cc5c98a0387db519cc03b45d1fd3f3d63de5facc2dc84338b02692
@@ -214,7 +214,7 @@
214
214
  <%
215
215
  nav_items = [
216
216
  { path: ruby_llm_agents.root_path, label: "Dashboard", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />' },
217
- { path: ruby_llm_agents.agents_path, label: "Agents", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />' },
217
+ { path: ruby_llm_agents.agents_path, label: "Agents", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>' },
218
218
  { path: ruby_llm_agents.workflows_path, label: "Workflows", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm0 8a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zm12 0a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/>' },
219
219
  { path: ruby_llm_agents.executions_path, label: "Executions", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />' },
220
220
  { path: ruby_llm_agents.tenants_path, label: "Tenants", icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />' }
@@ -1,23 +1,26 @@
1
- <%
2
- type = local_assigns[:type] || "agents"
3
- is_workflow = type == "workflows"
4
- %>
5
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-8 text-center">
6
- <svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7
- <% if is_workflow %>
8
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm0 8a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zm12 0a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/>
9
- <% else %>
10
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
11
- <% end %>
1
+ <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-8 text-center">
2
+ <!-- Agent Icon - Blue -->
3
+ <svg class="mx-auto h-16 w-16 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
12
5
  </svg>
13
- <h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">
14
- No <%= type %> found
6
+
7
+ <h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">
8
+ No agents yet
15
9
  </h3>
16
- <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
17
- <% if is_workflow %>
18
- Create a workflow by subclassing <code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-1 rounded">Pipeline</code>, <code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-1 rounded">Parallel</code>, or <code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-1 rounded">Router</code>
19
- <% else %>
20
- Create an agent by running <code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-1 rounded">rails g ruby_llm_agents:agent YourAgentName</code>
21
- <% end %>
10
+
11
+ <p class="mt-2 text-sm text-gray-500 dark:text-gray-400 max-w-md mx-auto">
12
+ Create an agent by inheriting from <code class="bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono text-xs">ApplicationAgent</code>
22
13
  </p>
14
+
15
+ <!-- Code Example -->
16
+ <div class="mt-6 max-w-sm mx-auto">
17
+ <pre class="bg-gray-900 dark:bg-gray-950 rounded-lg p-4 text-left text-sm overflow-x-auto"><code class="text-gray-300"><span class="text-pink-400">class</span> <span class="text-yellow-300">MyAgent</span> <span class="text-pink-400">&lt;</span> <span class="text-yellow-300">ApplicationAgent</span>
18
+ <span class="text-purple-400">model</span> <span class="text-green-400">"gpt-4o-mini"</span>
19
+ <span class="text-purple-400">param</span> <span class="text-blue-400">:query</span>, <span class="text-blue-400">required:</span> <span class="text-orange-400">true</span>
20
+
21
+ <span class="text-pink-400">def</span> <span class="text-blue-300">user_prompt</span>
22
+ <span class="text-gray-300">query</span>
23
+ <span class="text-pink-400">end</span>
24
+ <span class="text-pink-400">end</span></code></pre>
25
+ </div>
23
26
  </div>
@@ -0,0 +1,22 @@
1
+ <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-8 text-center">
2
+ <!-- Workflow Icon - Emerald -->
3
+ <svg class="mx-auto h-16 w-16 text-emerald-500 dark:text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
5
+ </svg>
6
+
7
+ <h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">
8
+ No workflows yet
9
+ </h3>
10
+
11
+ <p class="mt-2 text-sm text-gray-500 dark:text-gray-400 max-w-md mx-auto">
12
+ Create a workflow by inheriting from <code class="bg-emerald-100 dark:bg-emerald-900/50 text-emerald-700 dark:text-emerald-300 px-1.5 py-0.5 rounded font-mono text-xs">ApplicationWorkflow</code> and defining steps
13
+ </p>
14
+
15
+ <!-- Code Example -->
16
+ <div class="mt-6 max-w-sm mx-auto">
17
+ <pre class="bg-gray-900 dark:bg-gray-950 rounded-lg p-4 text-left text-sm overflow-x-auto"><code class="text-gray-300"><span class="text-pink-400">class</span> <span class="text-yellow-300">MyWorkflow</span> <span class="text-pink-400">&lt;</span> <span class="text-yellow-300">ApplicationWorkflow</span>
18
+ <span class="text-purple-400">step</span> <span class="text-emerald-400">:analyze</span>, <span class="text-yellow-300">AnalyzerAgent</span>
19
+ <span class="text-purple-400">step</span> <span class="text-emerald-400">:summarize</span>, <span class="text-yellow-300">SummarizerAgent</span>
20
+ <span class="text-pink-400">end</span></code></pre>
21
+ </div>
22
+ </div>
@@ -7,7 +7,7 @@
7
7
  </div>
8
8
 
9
9
  <% if @workflows.empty? %>
10
- <%= render "ruby_llm/agents/agents/empty_state", type: "workflows" %>
10
+ <%= render "ruby_llm/agents/workflows/empty_state" %>
11
11
  <% else %>
12
12
  <div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
13
13
  <div class="overflow-x-auto">
@@ -353,21 +353,21 @@ module RubyLLM
353
353
  # config.async_max_concurrency = 20
354
354
 
355
355
  # @!attribute [rw] root_directory
356
- # The root directory name under app/ for all LLM components.
356
+ # The root directory name under app/ for all agent components.
357
357
  # This allows customization of the directory structure.
358
- # @return [String] Directory name (default: "llm")
358
+ # @return [String] Directory name (default: "agents")
359
359
  # @example
360
- # config.root_directory = "ai" # Creates app/ai/ instead of app/llm/
360
+ # config.root_directory = "ai" # Creates app/ai/ instead of app/agents/
361
361
 
362
362
  # @!attribute [rw] root_namespace
363
- # The root namespace for all LLM component classes.
364
- # This should match the root_directory in camelized form.
365
- # Set to nil or "" to use no namespace (classes at top-level).
366
- # @return [String, nil] Namespace (default: "Llm")
363
+ # The root namespace for all agent component classes.
364
+ # When set, subdirectory namespaces are prefixed with this.
365
+ # Set to nil or "" to use no root namespace.
366
+ # @return [String, nil] Namespace (default: nil)
367
367
  # @example Custom namespace
368
- # config.root_namespace = "AI" # Uses AI:: instead of Llm::
369
- # @example No namespace (top-level classes)
370
- # config.root_namespace = nil # Uses top-level classes (ApplicationAgent, etc.)
368
+ # config.root_namespace = "AI" # app/agents/embedders -> AI::Embedders
369
+ # @example No namespace (default)
370
+ # config.root_namespace = nil # app/agents/embedders -> Embedders
371
371
 
372
372
  # Attributes without validation (simple accessors)
373
373
  attr_accessor :default_model,
@@ -821,26 +821,18 @@ module RubyLLM
821
821
 
822
822
  # Returns the full namespace for a given category
823
823
  #
824
- # @param category [Symbol, nil] Category (:audio, :image, :text, or nil for root)
824
+ # @param category [Symbol, String, nil] Category (e.g., :embedders, :images, or nil for root)
825
825
  # @return [String, nil] Full namespace string, or nil if no namespace configured
826
- # @example With root_namespace = "Llm"
827
- # namespace_for(:image) #=> "Llm::Image"
828
- # namespace_for(nil) #=> "Llm"
826
+ # @example With root_namespace = "AI"
827
+ # namespace_for(:embedders) #=> "AI::Embedders"
828
+ # namespace_for(nil) #=> "AI"
829
829
  # @example With no namespace (root_namespace = nil)
830
- # namespace_for(:image) #=> "Image"
831
- # namespace_for(nil) #=> nil
830
+ # namespace_for(:embedders) #=> "Embedders"
831
+ # namespace_for(nil) #=> nil
832
832
  def namespace_for(category = nil)
833
- category_namespace = case category
834
- when :images then "Images"
835
- when :audio then "Audio"
836
- when :embedders then "Embedders"
837
- when :moderators then "Moderators"
838
- when :workflows then "Workflows"
839
- when :text then "Text"
840
- when :image then "Image"
841
- end
833
+ category_namespace = category&.to_s&.camelize
842
834
 
843
- if root_namespace
835
+ if root_namespace.present?
844
836
  category_namespace ? "#{root_namespace}::#{category_namespace}" : root_namespace
845
837
  else
846
838
  category_namespace
@@ -4,6 +4,6 @@ module RubyLLM
4
4
  module Agents
5
5
  # Current version of the RubyLLM::Agents gem
6
6
  # @return [String] Semantic version string
7
- VERSION = "1.2.0"
7
+ VERSION = "1.2.1"
8
8
  end
9
9
  end
@@ -170,14 +170,16 @@ module RubyLLM
170
170
  g.factory_bot dir: "spec/factories"
171
171
  end
172
172
 
173
- # Adds the host app's LLM directories to Rails autoload paths
173
+ # Adds the host app's agent directories to Rails autoload paths
174
174
  #
175
- # This allows agent classes and other LLM components defined in app/llm/
175
+ # This allows agent classes and other components defined in app/agents/
176
176
  # to be automatically loaded without explicit requires.
177
177
  #
178
- # Supports two structures:
179
- # 1. New grouped structure: app/llm/agents/, app/llm/tools/, etc.
180
- # 2. Legacy flat structure: app/agents/ (for backwards compatibility)
178
+ # Supports subdirectory namespacing:
179
+ # - app/agents/ (top-level, no namespace)
180
+ # - app/agents/embedders/ -> Embedders namespace
181
+ # - app/agents/images/ -> Images namespace
182
+ # - app/workflows/ (top-level, no namespace)
181
183
  #
182
184
  # @api private
183
185
  initializer "ruby_llm_agents.autoload_agents", before: :set_autoload_paths do |app|
@@ -210,42 +212,34 @@ module RubyLLM
210
212
 
211
213
  # Determines the namespace constant for a given path
212
214
  #
213
- # @param path [String] Relative path like "app/llm/agents"
215
+ # @param path [String] Relative path like "app/agents/embedders"
214
216
  # @param config [Configuration] Current configuration
215
217
  # @return [Module, nil] Namespace module or nil for top-level
216
218
  # @api private
217
219
  def self.namespace_for_path(path, config)
218
- # Parse the path to determine namespace
219
220
  parts = path.split("/")
220
- return nil unless parts.length >= 3
221
221
 
222
- category = parts[2] # e.g., "agents", "audio", "image", "text"
222
+ # app/workflows -> no namespace (top-level workflows)
223
+ return nil if parts == ["app", "workflows"]
223
224
 
224
- # Determine the namespace name based on category and root_namespace setting
225
+ # Need at least app/{root_directory}
226
+ return nil unless parts.length >= 2 && parts[0] == "app"
227
+ return nil unless parts[1] == config.root_directory
228
+
229
+ # app/agents -> no namespace (root level)
230
+ return nil if parts.length == 2
231
+
232
+ # app/agents/embedders -> Embedders namespace
233
+ subdirectory = parts[2]
225
234
  namespace_name = if config.root_namespace.blank?
226
- # No root namespace - use category namespace only for audio/image/text
227
- case category
228
- when "audio", "image", "text"
229
- category.camelize # "Audio", "Image", "Text"
230
- else
231
- nil # Top-level for agents, workflows, tools
232
- end
235
+ subdirectory.camelize
233
236
  else
234
- # With root namespace - prefix category with root namespace
235
- case category
236
- when "audio", "image", "text"
237
- "#{config.root_namespace}::#{category.camelize}"
238
- else
239
- config.root_namespace
240
- end
237
+ "#{config.root_namespace}::#{subdirectory.camelize}"
241
238
  end
242
239
 
243
- return nil if namespace_name.nil?
244
-
245
- # Return the constant, creating intermediate modules if needed
240
+ # Create the namespace module if needed
246
241
  namespace_name.constantize
247
242
  rescue NameError
248
- # Create the namespace module if it doesn't exist
249
243
  namespace_name.split("::").inject(Object) do |mod, name|
250
244
  mod.const_defined?(name, false) ? mod.const_get(name) : mod.const_set(name, Module.new)
251
245
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - adham90
@@ -159,6 +159,7 @@ files:
159
159
  - app/views/ruby_llm/agents/tenants/edit.html.erb
160
160
  - app/views/ruby_llm/agents/tenants/index.html.erb
161
161
  - app/views/ruby_llm/agents/tenants/show.html.erb
162
+ - app/views/ruby_llm/agents/workflows/_empty_state.html.erb
162
163
  - app/views/ruby_llm/agents/workflows/_step_performance.html.erb
163
164
  - app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb
164
165
  - app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb