ruby_llm-agents 3.8.0 → 3.10.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 +30 -10
- data/app/controllers/ruby_llm/agents/requests_controller.rb +117 -0
- data/app/models/ruby_llm/agents/execution.rb +4 -0
- data/app/models/ruby_llm/agents/tool_execution.rb +25 -0
- data/app/views/layouts/ruby_llm/agents/application.html.erb +4 -2
- data/app/views/ruby_llm/agents/requests/index.html.erb +153 -0
- data/app/views/ruby_llm/agents/requests/show.html.erb +136 -0
- data/config/routes.rb +2 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/demo_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/doctor_generator.rb +196 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +7 -19
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +27 -80
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +18 -51
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +19 -17
- data/lib/ruby_llm/agents/base_agent.rb +70 -7
- data/lib/ruby_llm/agents/core/base.rb +4 -0
- data/lib/ruby_llm/agents/core/configuration.rb +12 -0
- data/lib/ruby_llm/agents/core/errors.rb +3 -0
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/pipeline/context.rb +26 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +58 -4
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +17 -17
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +34 -22
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +105 -50
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +7 -5
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +6 -4
- data/lib/ruby_llm/agents/rails/engine.rb +11 -0
- data/lib/ruby_llm/agents/results/background_removal_result.rb +7 -1
- data/lib/ruby_llm/agents/results/base.rb +39 -2
- data/lib/ruby_llm/agents/results/embedding_result.rb +4 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +6 -0
- data/lib/ruby_llm/agents/results/trackable.rb +25 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +6 -0
- data/lib/ruby_llm/agents/text/embedder.rb +7 -4
- data/lib/ruby_llm/agents/tool.rb +169 -0
- data/lib/ruby_llm/agents/tool_context.rb +71 -0
- data/lib/ruby_llm/agents/track_report.rb +127 -0
- data/lib/ruby_llm/agents/tracker.rb +32 -0
- data/lib/ruby_llm/agents.rb +212 -0
- data/lib/tasks/ruby_llm_agents.rake +6 -0
- metadata +13 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RubyLlmAgents
|
|
6
|
+
# Demo generator — scaffolds a working HelloAgent with a smoke-test script.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# rails generate ruby_llm_agents:demo
|
|
10
|
+
#
|
|
11
|
+
# Creates:
|
|
12
|
+
# - app/agents/hello_agent.rb — minimal working agent
|
|
13
|
+
# - bin/smoke_test_agent — one-command verification script
|
|
14
|
+
#
|
|
15
|
+
class DemoGenerator < ::Rails::Generators::Base
|
|
16
|
+
source_root File.expand_path("templates", __dir__)
|
|
17
|
+
|
|
18
|
+
def ensure_base_class
|
|
19
|
+
agents_dir = "app/agents"
|
|
20
|
+
empty_directory agents_dir
|
|
21
|
+
|
|
22
|
+
base_class_path = "#{agents_dir}/application_agent.rb"
|
|
23
|
+
unless File.exist?(File.join(destination_root, base_class_path))
|
|
24
|
+
template "application_agent.rb.tt", base_class_path
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_hello_agent
|
|
29
|
+
create_file "app/agents/hello_agent.rb", <<~RUBY
|
|
30
|
+
# frozen_string_literal: true
|
|
31
|
+
|
|
32
|
+
class HelloAgent < ApplicationAgent
|
|
33
|
+
system "You are a friendly assistant. Keep responses under 2 sentences."
|
|
34
|
+
|
|
35
|
+
prompt "Say hello to {name} and tell them one fun fact."
|
|
36
|
+
end
|
|
37
|
+
RUBY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_smoke_test
|
|
41
|
+
create_file "bin/smoke_test_agent", <<~RUBY
|
|
42
|
+
#!/usr/bin/env ruby
|
|
43
|
+
# frozen_string_literal: true
|
|
44
|
+
|
|
45
|
+
# Smoke test — verifies your RubyLLM::Agents setup end-to-end.
|
|
46
|
+
#
|
|
47
|
+
# Usage:
|
|
48
|
+
# bin/rails runner bin/smoke_test_agent
|
|
49
|
+
#
|
|
50
|
+
puts "Running RubyLLM::Agents smoke test..."
|
|
51
|
+
puts ""
|
|
52
|
+
|
|
53
|
+
# 1. Check configuration
|
|
54
|
+
config = RubyLLM::Agents.configuration
|
|
55
|
+
model = config.default_model
|
|
56
|
+
puts "Default model: \#{model}"
|
|
57
|
+
|
|
58
|
+
# 2. Dry-run (no API call)
|
|
59
|
+
puts ""
|
|
60
|
+
puts "Dry run:"
|
|
61
|
+
dry = HelloAgent.call(name: "World", dry_run: true)
|
|
62
|
+
puts " System prompt: \#{dry.system_prompt[0..80]}..."
|
|
63
|
+
puts " User prompt: \#{dry.user_prompt}"
|
|
64
|
+
puts " Model: \#{dry.model}"
|
|
65
|
+
puts " Dry run OK!"
|
|
66
|
+
|
|
67
|
+
# 3. Live call
|
|
68
|
+
puts ""
|
|
69
|
+
puts "Live call (calling \#{model})..."
|
|
70
|
+
begin
|
|
71
|
+
result = HelloAgent.call(name: "World")
|
|
72
|
+
puts " Response: \#{result.content}"
|
|
73
|
+
puts ""
|
|
74
|
+
puts "Success! Your setup is working."
|
|
75
|
+
rescue => e
|
|
76
|
+
puts " Error: \#{e.class}: \#{e.message}"
|
|
77
|
+
puts ""
|
|
78
|
+
puts "The dry run worked but the live call failed."
|
|
79
|
+
puts "This usually means your API key is missing or invalid."
|
|
80
|
+
puts ""
|
|
81
|
+
puts "Run 'rails ruby_llm_agents:doctor' for detailed diagnostics."
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
RUBY
|
|
85
|
+
|
|
86
|
+
chmod "bin/smoke_test_agent", 0o755
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def show_next_steps
|
|
90
|
+
say ""
|
|
91
|
+
say "Demo agent created!", :green
|
|
92
|
+
say ""
|
|
93
|
+
say "Try it:"
|
|
94
|
+
say " bin/rails runner bin/smoke_test_agent"
|
|
95
|
+
say ""
|
|
96
|
+
say "Or in the Rails console:"
|
|
97
|
+
say " HelloAgent.call(name: \"World\")"
|
|
98
|
+
say " HelloAgent.call(name: \"World\", dry_run: true)"
|
|
99
|
+
say ""
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RubyLlmAgents
|
|
6
|
+
# Doctor generator — validates that setup is complete and working.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# rails generate ruby_llm_agents:doctor
|
|
10
|
+
# rails ruby_llm_agents:doctor (rake task alias)
|
|
11
|
+
#
|
|
12
|
+
# Checks:
|
|
13
|
+
# 1. API keys — at least one provider key is configured
|
|
14
|
+
# 2. Migrations — required tables exist
|
|
15
|
+
# 3. Routes — engine is mounted
|
|
16
|
+
# 4. Background jobs — ActiveJob adapter is configured (not :async/:inline in prod)
|
|
17
|
+
# 5. Agents — at least one agent file exists
|
|
18
|
+
#
|
|
19
|
+
class DoctorGenerator < ::Rails::Generators::Base
|
|
20
|
+
desc "Validate your RubyLLM::Agents setup and print actionable fixes"
|
|
21
|
+
|
|
22
|
+
def run_checks
|
|
23
|
+
@pass = 0
|
|
24
|
+
@fail = 0
|
|
25
|
+
@warn = 0
|
|
26
|
+
|
|
27
|
+
say ""
|
|
28
|
+
say "RubyLLM::Agents Doctor", :bold
|
|
29
|
+
say "=" * 40
|
|
30
|
+
|
|
31
|
+
check_api_keys
|
|
32
|
+
check_migrations
|
|
33
|
+
check_routes
|
|
34
|
+
check_background_jobs
|
|
35
|
+
check_agents
|
|
36
|
+
|
|
37
|
+
say ""
|
|
38
|
+
say "=" * 40
|
|
39
|
+
summary = "#{@pass} passed, #{@fail} failed, #{@warn} warnings"
|
|
40
|
+
if @fail > 0
|
|
41
|
+
say "Result: #{summary}", :red
|
|
42
|
+
elsif @warn > 0
|
|
43
|
+
say "Result: #{summary}", :yellow
|
|
44
|
+
else
|
|
45
|
+
say "Result: #{summary} — you're all set!", :green
|
|
46
|
+
end
|
|
47
|
+
say ""
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def check_api_keys
|
|
53
|
+
say ""
|
|
54
|
+
say "API Keys", :bold
|
|
55
|
+
|
|
56
|
+
config = RubyLLM::Agents.configuration
|
|
57
|
+
providers = {
|
|
58
|
+
"OpenAI" => -> { config.openai_api_key },
|
|
59
|
+
"Anthropic" => -> { config.anthropic_api_key },
|
|
60
|
+
"Gemini" => -> { config.gemini_api_key },
|
|
61
|
+
"DeepSeek" => -> { config.deepseek_api_key },
|
|
62
|
+
"OpenRouter" => -> { config.openrouter_api_key },
|
|
63
|
+
"Mistral" => -> { config.mistral_api_key }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
configured = providers.select { |_, v| v.call.present? }.keys
|
|
67
|
+
|
|
68
|
+
if configured.any?
|
|
69
|
+
configured.each { |name| pass "#{name} API key configured" }
|
|
70
|
+
else
|
|
71
|
+
fail_check "No API keys configured"
|
|
72
|
+
fix "Add to config/initializers/ruby_llm_agents.rb:"
|
|
73
|
+
fix " config.openai_api_key = ENV[\"OPENAI_API_KEY\"]"
|
|
74
|
+
fix "Then set the environment variable in .env or credentials."
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_migrations
|
|
79
|
+
say ""
|
|
80
|
+
say "Database", :bold
|
|
81
|
+
|
|
82
|
+
tables = {
|
|
83
|
+
"ruby_llm_agents_executions" => "rails generate ruby_llm_agents:install && rails db:migrate",
|
|
84
|
+
"ruby_llm_agents_execution_details" => "rails generate ruby_llm_agents:upgrade && rails db:migrate"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
tables.each do |table, fix_cmd|
|
|
88
|
+
if table_exists?(table)
|
|
89
|
+
pass "Table #{table} exists"
|
|
90
|
+
else
|
|
91
|
+
fail_check "Table #{table} missing"
|
|
92
|
+
fix fix_cmd
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_routes
|
|
98
|
+
say ""
|
|
99
|
+
say "Routes", :bold
|
|
100
|
+
|
|
101
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
|
102
|
+
if File.exist?(routes_file)
|
|
103
|
+
content = File.read(routes_file)
|
|
104
|
+
if content.include?("RubyLLM::Agents::Engine")
|
|
105
|
+
pass "Dashboard engine mounted"
|
|
106
|
+
else
|
|
107
|
+
warn_check "Dashboard engine not mounted in routes"
|
|
108
|
+
fix "Add to config/routes.rb:"
|
|
109
|
+
fix " mount RubyLLM::Agents::Engine => \"/agents\""
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
warn_check "Could not find config/routes.rb"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def check_background_jobs
|
|
117
|
+
say ""
|
|
118
|
+
say "Background Jobs", :bold
|
|
119
|
+
|
|
120
|
+
adapter = ActiveJob::Base.queue_adapter.class.name
|
|
121
|
+
async_logging = RubyLLM::Agents.configuration.async_logging
|
|
122
|
+
|
|
123
|
+
if !async_logging
|
|
124
|
+
pass "Async logging disabled (synchronous mode)"
|
|
125
|
+
elsif adapter.include?("Async") || adapter.include?("Inline")
|
|
126
|
+
if Rails.env.production?
|
|
127
|
+
warn_check "ActiveJob adapter is #{adapter} — execution logging may be lost in production"
|
|
128
|
+
fix "Configure a persistent adapter (Sidekiq, GoodJob, SolidQueue, etc.)"
|
|
129
|
+
fix "Or set config.async_logging = false for synchronous logging."
|
|
130
|
+
else
|
|
131
|
+
pass "ActiveJob adapter: #{adapter} (OK for development)"
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
pass "ActiveJob adapter: #{adapter}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def check_agents
|
|
139
|
+
say ""
|
|
140
|
+
say "Agents", :bold
|
|
141
|
+
|
|
142
|
+
agents_dir = File.join(destination_root, "app/agents")
|
|
143
|
+
if Dir.exist?(agents_dir)
|
|
144
|
+
agent_files = Dir.glob(File.join(agents_dir, "**/*_agent.rb"))
|
|
145
|
+
.reject { |f| f.end_with?("application_agent.rb") }
|
|
146
|
+
|
|
147
|
+
if agent_files.any?
|
|
148
|
+
pass "Found #{agent_files.size} agent(s)"
|
|
149
|
+
else
|
|
150
|
+
warn_check "No agents found (only application_agent.rb)"
|
|
151
|
+
fix "rails generate ruby_llm_agents:agent HelloWorld query:required"
|
|
152
|
+
fix "Or: rails generate ruby_llm_agents:demo"
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
fail_check "app/agents/ directory missing"
|
|
156
|
+
fix "rails generate ruby_llm_agents:install"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Helpers
|
|
161
|
+
|
|
162
|
+
def table_exists?(name)
|
|
163
|
+
ActiveRecord::Base.connection.table_exists?(name)
|
|
164
|
+
rescue => e
|
|
165
|
+
say " (Could not check database: #{e.message})", :yellow
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def pass(msg)
|
|
170
|
+
@pass += 1
|
|
171
|
+
say " #{status_icon(:pass)} #{msg}", :green
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def fail_check(msg)
|
|
175
|
+
@fail += 1
|
|
176
|
+
say " #{status_icon(:fail)} #{msg}", :red
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def warn_check(msg)
|
|
180
|
+
@warn += 1
|
|
181
|
+
say " #{status_icon(:warn)} #{msg}", :yellow
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def fix(msg)
|
|
185
|
+
say " Fix: #{msg}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def status_icon(type)
|
|
189
|
+
case type
|
|
190
|
+
when :pass then "OK"
|
|
191
|
+
when :fail then "FAIL"
|
|
192
|
+
when :warn then "WARN"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -96,28 +96,16 @@ module RubyLlmAgents
|
|
|
96
96
|
say ""
|
|
97
97
|
say "RubyLLM::Agents has been installed!", :green
|
|
98
98
|
say ""
|
|
99
|
-
say "Directory structure created:"
|
|
100
|
-
say " app/"
|
|
101
|
-
say " ├── agents/"
|
|
102
|
-
say " │ ├── application_agent.rb"
|
|
103
|
-
say " │ ├── concerns/"
|
|
104
|
-
say " │ └── AGENTS.md"
|
|
105
|
-
say " └── tools/"
|
|
106
|
-
say " └── TOOLS.md"
|
|
107
|
-
say ""
|
|
108
|
-
say "Skill files (*.md) help AI coding assistants understand how to use this gem."
|
|
109
|
-
say ""
|
|
110
99
|
say "Next steps:"
|
|
111
|
-
say " 1. Set your API
|
|
100
|
+
say " 1. Set your API key in config/initializers/ruby_llm_agents.rb"
|
|
112
101
|
say " 2. Run migrations: rails db:migrate"
|
|
113
|
-
say " 3.
|
|
114
|
-
say " 4.
|
|
102
|
+
say " 3. Verify setup: rails ruby_llm_agents:doctor"
|
|
103
|
+
say " 4. Try it out: rails generate ruby_llm_agents:demo"
|
|
104
|
+
say ""
|
|
105
|
+
say "Or generate a custom agent:"
|
|
106
|
+
say " rails generate ruby_llm_agents:agent MyAgent query:required"
|
|
115
107
|
say ""
|
|
116
|
-
say "
|
|
117
|
-
say " rails generate ruby_llm_agents:agent CustomerSupport query:required"
|
|
118
|
-
say " rails generate ruby_llm_agents:image_generator Product"
|
|
119
|
-
say " rails generate ruby_llm_agents:transcriber Meeting"
|
|
120
|
-
say " rails generate ruby_llm_agents:embedder Semantic"
|
|
108
|
+
say "Dashboard: /agents"
|
|
121
109
|
say ""
|
|
122
110
|
end
|
|
123
111
|
|
|
@@ -8,108 +8,55 @@
|
|
|
8
8
|
<%- else -%>
|
|
9
9
|
class <%= class_name %>Agent < ApplicationAgent
|
|
10
10
|
<%- end -%>
|
|
11
|
-
|
|
12
|
-
# Model Configuration
|
|
13
|
-
# ============================================
|
|
14
|
-
|
|
11
|
+
<% if options[:model] != "default" -%>
|
|
15
12
|
model "<%= options[:model] %>"
|
|
13
|
+
<% end -%>
|
|
14
|
+
<% if options[:temperature] != 0.0 -%>
|
|
16
15
|
temperature <%= options[:temperature] %>
|
|
17
|
-
# timeout 30 # Per-request timeout in seconds (default: 60)
|
|
18
|
-
|
|
19
|
-
# ============================================
|
|
20
|
-
# Caching
|
|
21
|
-
# ============================================
|
|
22
|
-
|
|
23
|
-
<% if options[:cache] -%>
|
|
24
|
-
cache <%= options[:cache] %>
|
|
25
|
-
<% else -%>
|
|
26
|
-
# cache 1.hour # Enable response caching with TTL
|
|
27
16
|
<% end -%>
|
|
28
17
|
|
|
29
18
|
# ============================================
|
|
30
|
-
#
|
|
19
|
+
# Prompts
|
|
31
20
|
# ============================================
|
|
21
|
+
# Use {placeholder} syntax — placeholders become required params automatically.
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
# - max: Number of retry attempts
|
|
35
|
-
# - backoff: :constant or :exponential
|
|
36
|
-
# - base: Base delay in seconds
|
|
37
|
-
# - max_delay: Maximum delay between retries
|
|
38
|
-
# - on: Additional error classes to retry on
|
|
39
|
-
# retries max: 2, backoff: :exponential, base: 0.4, max_delay: 3.0
|
|
40
|
-
|
|
41
|
-
# Fallback models (tried in order when primary model fails)
|
|
42
|
-
# fallback_models ["gpt-4o-mini", "claude-3-haiku"]
|
|
43
|
-
|
|
44
|
-
# Total timeout across all retry/fallback attempts
|
|
45
|
-
# total_timeout 30
|
|
46
|
-
|
|
47
|
-
# Circuit breaker (prevents repeated calls to failing models)
|
|
48
|
-
# - errors: Number of errors to trigger open state
|
|
49
|
-
# - within: Rolling window in seconds
|
|
50
|
-
# - cooldown: Time to wait before allowing requests again
|
|
51
|
-
# circuit_breaker errors: 5, within: 60, cooldown: 300
|
|
52
|
-
|
|
53
|
-
# ============================================
|
|
54
|
-
# Parameters
|
|
55
|
-
# ============================================
|
|
23
|
+
system "You are a helpful assistant."
|
|
56
24
|
|
|
57
|
-
<% parsed_params.
|
|
58
|
-
|
|
25
|
+
<% if parsed_params.any? -%>
|
|
26
|
+
prompt "<%= parsed_params.map { |p| "{#{p.name}}" }.join(" ") %>"
|
|
27
|
+
<% else -%>
|
|
28
|
+
prompt "Your prompt here"
|
|
59
29
|
<% end -%>
|
|
60
30
|
|
|
61
|
-
|
|
31
|
+
<% parsed_params.select { |p| !p.required? && p.default }.each do |param| -%>
|
|
32
|
+
param :<%= param.name %>, default: <%= param.default.inspect %>
|
|
33
|
+
<% end -%>
|
|
34
|
+
<% if options[:cache] -%>
|
|
62
35
|
|
|
63
36
|
# ============================================
|
|
64
|
-
#
|
|
37
|
+
# Caching
|
|
65
38
|
# ============================================
|
|
66
39
|
|
|
67
|
-
|
|
68
|
-
<<~PROMPT
|
|
69
|
-
You are a helpful assistant.
|
|
70
|
-
# Define your system instructions here
|
|
71
|
-
PROMPT
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def user_prompt
|
|
75
|
-
# Build the prompt from parameters
|
|
76
|
-
<% if parsed_params.any? -%>
|
|
77
|
-
<%= parsed_params.first.name %>
|
|
78
|
-
<% else -%>
|
|
79
|
-
"Your prompt here"
|
|
40
|
+
cache for: <%= options[:cache] %>
|
|
80
41
|
<% end -%>
|
|
81
|
-
end
|
|
82
42
|
|
|
83
43
|
# ============================================
|
|
84
|
-
#
|
|
44
|
+
# Error Handling (uncomment to enable)
|
|
85
45
|
# ============================================
|
|
86
46
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# integer :confidence, description: "Confidence score 1-100"
|
|
92
|
-
# array :tags, description: "Relevant tags" do
|
|
93
|
-
# string
|
|
94
|
-
# end
|
|
95
|
-
# end
|
|
96
|
-
# end
|
|
97
|
-
|
|
98
|
-
# Custom response processing (default: symbolize hash keys)
|
|
99
|
-
# def process_response(response)
|
|
100
|
-
# content = response.content
|
|
101
|
-
# # Transform or validate the response
|
|
102
|
-
# content
|
|
47
|
+
# on_failure do
|
|
48
|
+
# retries times: 2, backoff: :exponential
|
|
49
|
+
# fallback to: ["gpt-4o-mini"]
|
|
50
|
+
# timeout 30
|
|
103
51
|
# end
|
|
104
52
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
# end
|
|
53
|
+
# ============================================
|
|
54
|
+
# Structured Output (uncomment to enable)
|
|
55
|
+
# ============================================
|
|
109
56
|
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
57
|
+
# returns do
|
|
58
|
+
# string :result, description: "The result"
|
|
59
|
+
# integer :confidence, description: "Confidence score 1-100"
|
|
113
60
|
# end
|
|
114
61
|
<%- if class_name.include?("::") -%>
|
|
115
62
|
<%- (class_name.split("::").length - 1).times do |i| -%>
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
# ApplicationAgent - Base class for all agents in this application
|
|
4
4
|
#
|
|
5
|
-
# All agents inherit from this class. Configure shared settings here
|
|
6
|
-
# that apply to all agents, or override them per-agent as needed.
|
|
5
|
+
# All agents inherit from this class. Configure shared settings here.
|
|
7
6
|
#
|
|
8
|
-
#
|
|
7
|
+
# Quick reference:
|
|
9
8
|
# class MyAgent < ApplicationAgent
|
|
10
|
-
#
|
|
9
|
+
# system "You are a helpful assistant."
|
|
10
|
+
# prompt "Answer this: {query}" # {query} becomes a required param
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# returns do # Optional: structured output
|
|
13
|
+
# string :answer, description: "The answer"
|
|
14
14
|
# end
|
|
15
15
|
# end
|
|
16
16
|
#
|
|
@@ -20,50 +20,17 @@
|
|
|
20
20
|
# MyAgent.call(query: "hello", skip_cache: true) # Bypass cache
|
|
21
21
|
#
|
|
22
22
|
class ApplicationAgent < RubyLLM::Agents::Base
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
# cache 1.hour # Enable caching for all agents (override per-agent if needed)
|
|
37
|
-
|
|
38
|
-
# ============================================
|
|
39
|
-
# Shared Reliability Settings
|
|
40
|
-
# ============================================
|
|
41
|
-
# Configure once here, all agents inherit these settings
|
|
42
|
-
|
|
43
|
-
# Automatic retries for all agents
|
|
44
|
-
# retries max: 2, backoff: :exponential, base: 0.4, max_delay: 3.0
|
|
45
|
-
|
|
46
|
-
# Shared fallback models
|
|
47
|
-
# fallback_models ["gpt-4o-mini", "claude-3-haiku"]
|
|
48
|
-
|
|
49
|
-
# Total timeout across retries/fallbacks
|
|
50
|
-
# total_timeout 30
|
|
51
|
-
|
|
52
|
-
# Circuit breaker (per agent-model pair)
|
|
53
|
-
# circuit_breaker errors: 5, within: 60, cooldown: 300
|
|
54
|
-
|
|
55
|
-
# ============================================
|
|
56
|
-
# Shared Helper Methods
|
|
57
|
-
# ============================================
|
|
58
|
-
# Define methods here that can be used by all agents
|
|
59
|
-
|
|
60
|
-
# Example: Common system prompt prefix
|
|
61
|
-
# def system_prompt_prefix
|
|
62
|
-
# "You are an AI assistant for #{Rails.application.class.module_parent_name}."
|
|
63
|
-
# end
|
|
64
|
-
|
|
65
|
-
# Example: Common metadata
|
|
66
|
-
# def metadata
|
|
67
|
-
# { app_version: Rails.application.config.version }
|
|
23
|
+
# Shared settings inherited by all agents.
|
|
24
|
+
# Override per-agent as needed.
|
|
25
|
+
|
|
26
|
+
# model "gpt-4o" # Override the configured default model
|
|
27
|
+
# temperature 0.0 # 0.0 = deterministic, 2.0 = creative
|
|
28
|
+
# cache for: 1.hour # Enable caching for all agents
|
|
29
|
+
|
|
30
|
+
# Shared error handling (uncomment to apply to all agents)
|
|
31
|
+
# on_failure do
|
|
32
|
+
# retries times: 2, backoff: :exponential
|
|
33
|
+
# fallback to: ["gpt-4o-mini"]
|
|
34
|
+
# timeout 30
|
|
68
35
|
# end
|
|
69
36
|
end
|
|
@@ -6,29 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
RubyLLM::Agents.configure do |config|
|
|
8
8
|
# ============================================
|
|
9
|
-
#
|
|
9
|
+
# Quick Start — set ONE API key to get going
|
|
10
10
|
# ============================================
|
|
11
|
-
#
|
|
12
|
-
# or replace ENV[] calls with your keys directly.
|
|
11
|
+
# Uncomment one line below, then run: rails ruby_llm_agents:doctor
|
|
13
12
|
|
|
14
13
|
# config.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
15
14
|
# config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
|
16
15
|
# config.gemini_api_key = ENV["GOOGLE_API_KEY"]
|
|
17
16
|
|
|
18
|
-
# Additional providers:
|
|
19
|
-
# config.deepseek_api_key = ENV["DEEPSEEK_API_KEY"]
|
|
20
|
-
# config.openrouter_api_key = ENV["OPENROUTER_API_KEY"]
|
|
21
|
-
# config.mistral_api_key = ENV["MISTRAL_API_KEY"]
|
|
22
|
-
# config.xai_api_key = ENV["XAI_API_KEY"]
|
|
23
|
-
|
|
24
|
-
# Custom endpoints (e.g., Azure OpenAI, local Ollama):
|
|
25
|
-
# config.openai_api_base = "https://your-resource.openai.azure.com"
|
|
26
|
-
# config.ollama_api_base = "http://localhost:11434"
|
|
27
|
-
|
|
28
|
-
# Connection settings:
|
|
29
|
-
# config.request_timeout = 120
|
|
30
|
-
# config.max_retries = 3
|
|
31
|
-
|
|
32
17
|
# ============================================
|
|
33
18
|
# Model Defaults
|
|
34
19
|
# ============================================
|
|
@@ -46,6 +31,23 @@ RubyLLM::Agents.configure do |config|
|
|
|
46
31
|
# When enabled, agents stream responses and track time-to-first-token
|
|
47
32
|
# config.default_streaming = false
|
|
48
33
|
|
|
34
|
+
# ============================================
|
|
35
|
+
# Additional Providers (uncomment as needed)
|
|
36
|
+
# ============================================
|
|
37
|
+
|
|
38
|
+
# config.deepseek_api_key = ENV["DEEPSEEK_API_KEY"]
|
|
39
|
+
# config.openrouter_api_key = ENV["OPENROUTER_API_KEY"]
|
|
40
|
+
# config.mistral_api_key = ENV["MISTRAL_API_KEY"]
|
|
41
|
+
# config.xai_api_key = ENV["XAI_API_KEY"]
|
|
42
|
+
|
|
43
|
+
# Custom endpoints (e.g., Azure OpenAI, local Ollama):
|
|
44
|
+
# config.openai_api_base = "https://your-resource.openai.azure.com"
|
|
45
|
+
# config.ollama_api_base = "http://localhost:11434"
|
|
46
|
+
|
|
47
|
+
# Connection settings:
|
|
48
|
+
# config.request_timeout = 120
|
|
49
|
+
# config.max_retries = 3
|
|
50
|
+
|
|
49
51
|
# ============================================
|
|
50
52
|
# Caching
|
|
51
53
|
# ============================================
|