self_agency 0.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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +40 -0
- data/.irbrc +22 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +177 -0
- data/Rakefile +8 -0
- data/docs/api/configuration.md +85 -0
- data/docs/api/errors.md +166 -0
- data/docs/api/index.md +37 -0
- data/docs/api/self-agency-module.md +198 -0
- data/docs/architecture/overview.md +181 -0
- data/docs/architecture/security.md +101 -0
- data/docs/assets/images/self_agency.gif +0 -0
- data/docs/assets/images/self_agency.mp4 +0 -0
- data/docs/development/contributing.md +45 -0
- data/docs/development/setup.md +81 -0
- data/docs/development/testing.md +70 -0
- data/docs/examples/autonomous-robots.md +109 -0
- data/docs/examples/basic-examples.md +237 -0
- data/docs/examples/collaborative-robots.md +98 -0
- data/docs/examples/full-workflow.md +100 -0
- data/docs/examples/index.md +36 -0
- data/docs/getting-started/installation.md +71 -0
- data/docs/getting-started/quick-start.md +94 -0
- data/docs/guide/configuration.md +113 -0
- data/docs/guide/generating-methods.md +146 -0
- data/docs/guide/how-to-use.md +144 -0
- data/docs/guide/lifecycle-hooks.md +86 -0
- data/docs/guide/prompt-templates.md +189 -0
- data/docs/guide/saving-methods.md +84 -0
- data/docs/guide/scopes.md +74 -0
- data/docs/guide/source-inspection.md +96 -0
- data/docs/index.md +77 -0
- data/examples/01_basic_usage.rb +27 -0
- data/examples/02_multiple_methods.rb +43 -0
- data/examples/03_scopes.rb +40 -0
- data/examples/04_source_inspection.rb +46 -0
- data/examples/05_lifecycle_hook.rb +55 -0
- data/examples/06_configuration.rb +97 -0
- data/examples/07_error_handling.rb +103 -0
- data/examples/08_class_context.rb +64 -0
- data/examples/09_method_override.rb +52 -0
- data/examples/10_full_workflow.rb +118 -0
- data/examples/11_collaborative_robots/atlas.rb +31 -0
- data/examples/11_collaborative_robots/echo.rb +30 -0
- data/examples/11_collaborative_robots/main.rb +190 -0
- data/examples/11_collaborative_robots/nova.rb +71 -0
- data/examples/11_collaborative_robots/robot.rb +119 -0
- data/examples/12_autonomous_robots/analyst.rb +193 -0
- data/examples/12_autonomous_robots/collector.rb +78 -0
- data/examples/12_autonomous_robots/main.rb +166 -0
- data/examples/12_autonomous_robots/planner.rb +125 -0
- data/examples/12_autonomous_robots/robot.rb +284 -0
- data/examples/generated/from_range_class.rb +3 -0
- data/examples/generated/mean_instance.rb +4 -0
- data/examples/generated/median_instance.rb +15 -0
- data/examples/generated/report_singleton.rb +3 -0
- data/examples/generated/standard_deviation_instance.rb +8 -0
- data/examples/lib/message_bus.rb +57 -0
- data/examples/lib/setup.rb +8 -0
- data/lib/self_agency/configuration.rb +76 -0
- data/lib/self_agency/errors.rb +35 -0
- data/lib/self_agency/generator.rb +47 -0
- data/lib/self_agency/prompts/generate/system.txt.erb +15 -0
- data/lib/self_agency/prompts/generate/user.txt.erb +13 -0
- data/lib/self_agency/prompts/shape/system.txt.erb +26 -0
- data/lib/self_agency/prompts/shape/user.txt.erb +10 -0
- data/lib/self_agency/sandbox.rb +17 -0
- data/lib/self_agency/saver.rb +62 -0
- data/lib/self_agency/validator.rb +64 -0
- data/lib/self_agency/version.rb +5 -0
- data/lib/self_agency.rb +315 -0
- data/mkdocs.yml +156 -0
- data/sig/self_agency.rbs +4 -0
- metadata +163 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 11_collaborative_robots/main.rb — Collaborative Robots Demo
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Two-layer LLM approach: analyze task -> generate methods via _()
|
|
8
|
+
# - Three robots collaborating through a shared message bus
|
|
9
|
+
# - Pipeline execution: data generation -> analysis -> reporting
|
|
10
|
+
#
|
|
11
|
+
# Requires a running Ollama instance with the configured model.
|
|
12
|
+
|
|
13
|
+
require_relative "robot"
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Create message bus and robots
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
puts "=== Collaborative Robots — Weather Data Pipeline ==="
|
|
20
|
+
puts ""
|
|
21
|
+
|
|
22
|
+
bus = MessageBus.new
|
|
23
|
+
|
|
24
|
+
atlas = Robot.new(
|
|
25
|
+
name: "Atlas",
|
|
26
|
+
task: <<~TASK,
|
|
27
|
+
You are a data generator robot. Create exactly two instance methods:
|
|
28
|
+
|
|
29
|
+
1. Method named 'generate_weather_data' that takes no parameters.
|
|
30
|
+
It must return an Array of 24 Hashes (one per hour, index 0..23).
|
|
31
|
+
Each Hash has these keys (all Symbols):
|
|
32
|
+
:hour => the integer hour (0..23)
|
|
33
|
+
:temperature => a Float computed as 20.0 + 8.0 * Math.sin((hour - 6) * Math::PI / 12.0)
|
|
34
|
+
:humidity => a Float computed as 60.0 + 20.0 * Math.cos((hour - 14) * Math::PI / 12.0)
|
|
35
|
+
:wind_speed => a Float computed as 10.0 + 5.0 * Math.sin((hour * 7) * Math::PI / 24.0)
|
|
36
|
+
Do NOT use random numbers. Use only the deterministic formulas above.
|
|
37
|
+
|
|
38
|
+
2. Method named 'summarize_raw_data' that takes one parameter (data),
|
|
39
|
+
an Array of Hashes as described above. It returns a Hash with:
|
|
40
|
+
:readings_count => data.size
|
|
41
|
+
:raw_data => data
|
|
42
|
+
:source => "Atlas"
|
|
43
|
+
TASK
|
|
44
|
+
bus: bus
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
puts ""
|
|
48
|
+
|
|
49
|
+
nova = Robot.new(
|
|
50
|
+
name: "Nova",
|
|
51
|
+
task: <<~TASK,
|
|
52
|
+
You are an analyzer robot. Create exactly two instance methods:
|
|
53
|
+
|
|
54
|
+
1. Method named 'compute_basic_statistics' that takes one parameter (data),
|
|
55
|
+
a Hash with key :raw_data containing an Array of Hashes. Each inner Hash
|
|
56
|
+
has keys :temperature, :humidity, :wind_speed (all Floats).
|
|
57
|
+
Compute and return a Hash with:
|
|
58
|
+
:avg_temp => average of all :temperature values, rounded to 1 decimal
|
|
59
|
+
:min_temp => minimum :temperature value, rounded to 1 decimal
|
|
60
|
+
:max_temp => maximum :temperature value, rounded to 1 decimal
|
|
61
|
+
:avg_humidity => average of all :humidity values, rounded to 1 decimal
|
|
62
|
+
:avg_wind => average of all :wind_speed values, rounded to 1 decimal
|
|
63
|
+
:readings => data[:readings_count]
|
|
64
|
+
:source => data[:source]
|
|
65
|
+
Use .round(1) for all Float results.
|
|
66
|
+
|
|
67
|
+
2. Method named 'classify_conditions' that takes one parameter (stats),
|
|
68
|
+
a Hash with keys :avg_temp, :avg_humidity, :avg_wind (all Floats).
|
|
69
|
+
Determine classifications:
|
|
70
|
+
- temperature_class: "cold" if avg_temp < 15, "mild" if < 25, else "hot"
|
|
71
|
+
- humidity_class: "dry" if avg_humidity < 40, "comfortable" if < 70, else "humid"
|
|
72
|
+
- wind_class: "calm" if avg_wind < 8, "breezy" if < 15, else "windy"
|
|
73
|
+
Return stats.merge with the three new keys (:temperature_class, :humidity_class,
|
|
74
|
+
:wind_class) added, preserving all existing keys.
|
|
75
|
+
TASK
|
|
76
|
+
bus: bus
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
puts ""
|
|
80
|
+
|
|
81
|
+
echo = Robot.new(
|
|
82
|
+
name: "Echo",
|
|
83
|
+
task: <<~TASK,
|
|
84
|
+
You are a reporter robot. Create exactly one instance method:
|
|
85
|
+
|
|
86
|
+
1. Method named 'format_weather_report' that takes one parameter (data),
|
|
87
|
+
a Hash with these keys:
|
|
88
|
+
:avg_temp, :min_temp, :max_temp (Floats)
|
|
89
|
+
:avg_humidity, :avg_wind (Floats)
|
|
90
|
+
:temperature_class, :humidity_class, :wind_class (Strings)
|
|
91
|
+
:readings (Integer), :source (String)
|
|
92
|
+
Return a formatted multi-line String report like:
|
|
93
|
+
|
|
94
|
+
"=== Weather Report ===\\n" +
|
|
95
|
+
"Source: \#{data[:source]} | Readings: \#{data[:readings]}\\n" +
|
|
96
|
+
"Temperature: \#{data[:avg_temp]}° (min: \#{data[:min_temp]}°, max: \#{data[:max_temp]}°) [\#{data[:temperature_class]}]\\n" +
|
|
97
|
+
"Humidity: \#{data[:avg_humidity]}% [\#{data[:humidity_class]}]\\n" +
|
|
98
|
+
"Wind: \#{data[:avg_wind]} km/h [\#{data[:wind_class]}]\\n" +
|
|
99
|
+
"======================"
|
|
100
|
+
|
|
101
|
+
Use string interpolation. Return the String, do not print it.
|
|
102
|
+
TASK
|
|
103
|
+
bus: bus
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Display capabilities summary
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
puts ""
|
|
111
|
+
puts "=== Capabilities Summary ==="
|
|
112
|
+
[atlas, nova, echo].each do |robot|
|
|
113
|
+
puts "#{robot.name}: #{robot.capabilities.inspect}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
puts ""
|
|
117
|
+
puts "=== Generated Source Code ==="
|
|
118
|
+
[atlas, nova, echo].each do |robot|
|
|
119
|
+
robot.generation_log.each do |entry|
|
|
120
|
+
puts "--- #{robot.name}##{entry[:method_name]} ---"
|
|
121
|
+
puts entry[:code]
|
|
122
|
+
puts ""
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Execute the pipeline
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
puts "=== Executing Pipeline ==="
|
|
131
|
+
puts ""
|
|
132
|
+
|
|
133
|
+
# Step 1: Atlas generates and summarizes weather data
|
|
134
|
+
puts "Step 1: Atlas generates weather data..."
|
|
135
|
+
atlas_result = atlas.execute
|
|
136
|
+
atlas.send_message(to: "Nova", content: atlas_result)
|
|
137
|
+
puts "Atlas produced #{atlas_result[:raw_data]&.size || 0} readings"
|
|
138
|
+
puts ""
|
|
139
|
+
|
|
140
|
+
# Step 2: Nova analyzes the data
|
|
141
|
+
puts "Step 2: Nova analyzes the data..."
|
|
142
|
+
nova_input = nova.inbox.last&.dig(:content)
|
|
143
|
+
nova_result = nova.execute(nova_input)
|
|
144
|
+
nova.send_message(to: "Echo", content: nova_result)
|
|
145
|
+
puts "Nova computed statistics and classifications"
|
|
146
|
+
puts ""
|
|
147
|
+
|
|
148
|
+
# Step 3: Echo formats the report
|
|
149
|
+
puts "Step 3: Echo formats the final report..."
|
|
150
|
+
echo_input = echo.inbox.last&.dig(:content)
|
|
151
|
+
final_report = echo.execute(echo_input)
|
|
152
|
+
puts ""
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# Display results
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
puts "=== Final Weather Report ==="
|
|
159
|
+
puts final_report.to_s
|
|
160
|
+
puts ""
|
|
161
|
+
|
|
162
|
+
bus.print_log
|
|
163
|
+
puts ""
|
|
164
|
+
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
# Generation statistics
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
puts "=== Generation Statistics ==="
|
|
170
|
+
total_methods = 0
|
|
171
|
+
total_lines = 0
|
|
172
|
+
[atlas, nova, echo].each do |robot|
|
|
173
|
+
methods = robot.generation_log.size
|
|
174
|
+
lines = robot.generation_log.sum { |e| e[:code].lines.size }
|
|
175
|
+
total_methods += methods
|
|
176
|
+
total_lines += lines
|
|
177
|
+
puts "#{robot.name}: #{methods} method(s), #{lines} lines of generated code"
|
|
178
|
+
end
|
|
179
|
+
puts "Total: #{total_methods} methods, #{total_lines} lines of generated code"
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Save generated robots as subclasses
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
puts ""
|
|
186
|
+
puts "=== Saving Generated Robots ==="
|
|
187
|
+
[atlas, nova, echo].each do |robot|
|
|
188
|
+
path = robot._save!(as: robot.name)
|
|
189
|
+
puts "#{robot.name}: saved to #{path}"
|
|
190
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "robot"
|
|
4
|
+
|
|
5
|
+
class Nova < Robot
|
|
6
|
+
# Compute basic statistics from raw data. Takes one parameter (data), a Hash with key :raw_data containing an Array of Hashes. Each inner Hash has keys :temperature, :humidity, :wind_speed (all Floats). Compute and return a Hash with: :avg_temp => average of all :temperature values, rounded to 1 decimal, :min_temp => minimum :temperature value, rounded to 1 decimal, :max_temp => maximum :temperature value, rounded to 1 decimal, :avg_humidity => average of all :humidity values, rounded to 1 decimal, :avg_wind => average of all :wind_speed values, rounded to 1 decimal, :readings => data[:readings_count], :source => data[:source]. Use .round(1) for all Float results.
|
|
7
|
+
def compute_statistics(data)
|
|
8
|
+
raw_data = data[:raw_data]
|
|
9
|
+
readings_count = data[:readings_count]
|
|
10
|
+
source = data[:source]
|
|
11
|
+
|
|
12
|
+
temperatures = []
|
|
13
|
+
humidities = []
|
|
14
|
+
wind_speeds = []
|
|
15
|
+
|
|
16
|
+
raw_data.each do |reading|
|
|
17
|
+
temperatures << reading[:temperature]
|
|
18
|
+
humidities << reading[:humidity]
|
|
19
|
+
wind_speeds << reading[:wind_speed]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
avg_temp = (temperatures.sum / temperatures.length.to_f).round(1)
|
|
23
|
+
min_temp = temperatures.min.round(1)
|
|
24
|
+
max_temp = temperatures.max.round(1)
|
|
25
|
+
avg_humidity = (humidities.sum / humidities.length.to_f).round(1)
|
|
26
|
+
avg_wind = (wind_speeds.sum / wind_speeds.length.to_f).round(1)
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
:avg_temp => avg_temp,
|
|
30
|
+
:min_temp => min_temp,
|
|
31
|
+
:max_temp => max_temp,
|
|
32
|
+
:avg_humidity => avg_humidity,
|
|
33
|
+
:avg_wind => avg_wind,
|
|
34
|
+
:readings => readings_count,
|
|
35
|
+
:source => source
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Classify weather conditions based on statistics. Takes one parameter (stats), a Hash with keys :avg_temp, :avg_humidity, :avg_wind (all Floats). Determine classifications: - temperature_class: "cold" if avg_temp < 15, "mild" if < 25, else "hot" - humidity_class: "dry" if avg_humidity < 40, "comfortable" if < 70, else "humid" - wind_class: "calm" if avg_wind < 8, "breezy" if < 15, else "windy". Return stats.merge with the three new keys (:temperature_class, :humidity_class, :wind_class) added, preserving all existing keys.
|
|
40
|
+
def classify_weather_conditions(stats)
|
|
41
|
+
temperature_class = if stats[:avg_temp] < 15.0
|
|
42
|
+
"cold"
|
|
43
|
+
elsif stats[:avg_temp] < 25.0
|
|
44
|
+
"mild"
|
|
45
|
+
else
|
|
46
|
+
"hot"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
humidity_class = if stats[:avg_humidity] < 40.0
|
|
50
|
+
"dry"
|
|
51
|
+
elsif stats[:avg_humidity] < 70.0
|
|
52
|
+
"comfortable"
|
|
53
|
+
else
|
|
54
|
+
"humid"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
wind_class = if stats[:avg_wind] < 8.0
|
|
58
|
+
"calm"
|
|
59
|
+
elsif stats[:avg_wind] < 15.0
|
|
60
|
+
"breezy"
|
|
61
|
+
else
|
|
62
|
+
"windy"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
stats.merge(
|
|
66
|
+
temperature_class: temperature_class,
|
|
67
|
+
humidity_class: humidity_class,
|
|
68
|
+
wind_class: wind_class
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# robot.rb — Robot class with two-layer LLM self-generation
|
|
4
|
+
#
|
|
5
|
+
# Layer 1 (Analyze): Direct RubyLLM.chat().ask() decomposes a task into
|
|
6
|
+
# a JSON array of method specifications.
|
|
7
|
+
# Layer 2 (Generate): Loops through specs, calls _() for each.
|
|
8
|
+
# SelfAgency handles shape -> generate -> validate -> sandbox eval.
|
|
9
|
+
|
|
10
|
+
require "json"
|
|
11
|
+
require_relative "../lib/message_bus"
|
|
12
|
+
require_relative "../lib/setup"
|
|
13
|
+
|
|
14
|
+
class Robot
|
|
15
|
+
include SelfAgency
|
|
16
|
+
|
|
17
|
+
attr_reader :name, :task, :bus, :inbox, :capabilities, :generation_log
|
|
18
|
+
|
|
19
|
+
def initialize(name:, task:, bus:)
|
|
20
|
+
@name = name
|
|
21
|
+
@task = task
|
|
22
|
+
@bus = bus
|
|
23
|
+
@inbox = []
|
|
24
|
+
@capabilities = []
|
|
25
|
+
@generation_log = []
|
|
26
|
+
|
|
27
|
+
bus.register(self)
|
|
28
|
+
|
|
29
|
+
puts "#{@name}: Analyzing task..."
|
|
30
|
+
specs = analyze_task(task)
|
|
31
|
+
puts "#{@name}: Found #{specs.size} method(s) to generate"
|
|
32
|
+
|
|
33
|
+
specs.each do |spec|
|
|
34
|
+
method_name = spec["name"]
|
|
35
|
+
description = spec["description"]
|
|
36
|
+
puts "#{@name}: Generating '#{method_name}' — #{description}"
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
defined_methods = _(description)
|
|
40
|
+
@capabilities.concat(defined_methods)
|
|
41
|
+
puts "#{@name}: Successfully generated #{defined_methods.inspect}"
|
|
42
|
+
rescue SelfAgency::Error => e
|
|
43
|
+
puts "#{@name}: Failed to generate '#{method_name}': #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts "#{@name}: Ready with capabilities: #{@capabilities.inspect}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def execute(input = nil)
|
|
51
|
+
result = nil
|
|
52
|
+
|
|
53
|
+
@capabilities.each do |cap|
|
|
54
|
+
arity = method(cap).arity
|
|
55
|
+
|
|
56
|
+
if arity == 0 && result.nil?
|
|
57
|
+
result = public_send(cap)
|
|
58
|
+
elsif arity != 0 && !result.nil?
|
|
59
|
+
result = public_send(cap, result)
|
|
60
|
+
elsif arity != 0 && result.nil? && !input.nil?
|
|
61
|
+
result = public_send(cap, input)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def receive_message(from:, content:)
|
|
69
|
+
@inbox << { from: from, content: content }
|
|
70
|
+
puts "#{@name}: Received message from #{from}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def send_message(to:, content:)
|
|
74
|
+
@bus.deliver(from: @name, to: to, content: content)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def broadcast(content:)
|
|
78
|
+
@bus.broadcast(from: @name, content: content)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def on_method_generated(method_name, scope, code)
|
|
82
|
+
@generation_log << { method_name: method_name, scope: scope, code: code }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def analyze_task(description)
|
|
88
|
+
cfg = SelfAgency.configuration
|
|
89
|
+
chat = RubyLLM.chat(model: cfg.model, provider: cfg.provider)
|
|
90
|
+
|
|
91
|
+
prompt = <<~PROMPT
|
|
92
|
+
You are a task decomposition engine. Given a task description, return a JSON
|
|
93
|
+
array of method specifications. Each element must have:
|
|
94
|
+
- "name": the Ruby method name (snake_case), exactly as specified in the task
|
|
95
|
+
- "description": a precise description for a Ruby code generator that
|
|
96
|
+
preserves EVERY identifier verbatim from the task — method names, parameter
|
|
97
|
+
names, Hash key names (as Ruby Symbols), return types, thresholds, and
|
|
98
|
+
formulas. Do NOT paraphrase, rename, or abbreviate any identifier.
|
|
99
|
+
- "takes_input": boolean, true if the method accepts a parameter
|
|
100
|
+
|
|
101
|
+
Respond with ONLY the JSON array. No markdown fences, no explanation.
|
|
102
|
+
|
|
103
|
+
Task: #{description}
|
|
104
|
+
PROMPT
|
|
105
|
+
|
|
106
|
+
response = chat.ask(prompt)
|
|
107
|
+
raw = response.content.to_s.strip
|
|
108
|
+
|
|
109
|
+
# Sanitize the same way SelfAgency does — strip <think> blocks and markdown fences
|
|
110
|
+
raw = raw.gsub(/<think>.*?<\/think>/m, "")
|
|
111
|
+
raw = raw.sub(/\A```\w*\n?/, "").sub(/\n?```\s*\z/, "")
|
|
112
|
+
raw.strip!
|
|
113
|
+
|
|
114
|
+
JSON.parse(raw)
|
|
115
|
+
rescue JSON::ParserError => e
|
|
116
|
+
puts "#{@name}: Failed to parse task analysis: #{e.message}"
|
|
117
|
+
[]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "robot"
|
|
4
|
+
|
|
5
|
+
class Analyst < Robot
|
|
6
|
+
# Fix the Ruby singleton method 'calculate_statistics' on this Robot instance.
|
|
7
|
+
#
|
|
8
|
+
# Robot's overall goal: Analyze landmark data. The input is an Array of Hashes, each with keys :name (String), :type (String), :duration (Integer), :rating (Float). Return a Hash with three keys: :statistics (a Hash with :avg_rating, :avg_duration, :total_duration, :count), :ranked (the landmarks Array sorted by :rating descending), :by_type (a Hash grouping landmarks by :type)
|
|
9
|
+
# Generated capabilities on this object: [:calculate_statistics, :sort_by_rating, :group_by_type, :execute_task]
|
|
10
|
+
#
|
|
11
|
+
# Current source code of 'calculate_statistics':
|
|
12
|
+
# def calculate_statistics(landmarks)
|
|
13
|
+
# total_duration = 0
|
|
14
|
+
# rating_sum = 0.0
|
|
15
|
+
#
|
|
16
|
+
# landmarks.each do |landmark|
|
|
17
|
+
# total_duration += landmark[:duration]
|
|
18
|
+
# rating_sum += landmark[:rating]
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# avg_rating = landmarks.length > 0 ? rating_sum / landmarks.length : 0.0
|
|
22
|
+
# avg_duration = landmarks.length > 0 ? total_duration.to_f / landmarks.length : 0.0
|
|
23
|
+
#
|
|
24
|
+
# {
|
|
25
|
+
# avg_rating: avg_rating,
|
|
26
|
+
# avg_duration: avg_duration,
|
|
27
|
+
# total_duration: total_duration,
|
|
28
|
+
# count: landmarks.length
|
|
29
|
+
# }
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# Runtime error:
|
|
33
|
+
# NoMethodError: undefined method 'each' for nil
|
|
34
|
+
#
|
|
35
|
+
# Backtrace (top 5):
|
|
36
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):5:in 'calculate_statistics'
|
|
37
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):2:in 'execute_task'
|
|
38
|
+
# /Users/dewayne/sandbox/git_repos/madbomber/self_agency/examples/12_autonomous_robots/robot.rb:72:in 'Robot#perform_task'
|
|
39
|
+
# ./main.rb:102:in '<main>'
|
|
40
|
+
#
|
|
41
|
+
# This method takes no external input.
|
|
42
|
+
#
|
|
43
|
+
# Produce a corrected version of this method that avoids the error.
|
|
44
|
+
# Keep the same method name and signature. Only define this one method.
|
|
45
|
+
# Fix the bug while preserving the method's intent.
|
|
46
|
+
def calculate_statistics(landmarks)
|
|
47
|
+
landmarks = landmarks || []
|
|
48
|
+
|
|
49
|
+
total_duration = 0
|
|
50
|
+
rating_sum = 0.0
|
|
51
|
+
count = 0
|
|
52
|
+
|
|
53
|
+
landmarks.each do |landmark|
|
|
54
|
+
next unless landmark.is_a?(Hash)
|
|
55
|
+
next unless landmark[:duration].is_a?(Integer)
|
|
56
|
+
next unless landmark[:rating].is_a?(Float)
|
|
57
|
+
|
|
58
|
+
total_duration += landmark[:duration]
|
|
59
|
+
rating_sum += landmark[:rating]
|
|
60
|
+
count += 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
avg_rating = count > 0 ? rating_sum / count : 0.0
|
|
64
|
+
avg_duration = count > 0 ? total_duration.to_f / count : 0.0
|
|
65
|
+
|
|
66
|
+
statistics = {
|
|
67
|
+
:avg_rating => avg_rating,
|
|
68
|
+
:avg_duration => avg_duration,
|
|
69
|
+
:total_duration => total_duration,
|
|
70
|
+
:count => count
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ranked = landmarks.sort_by { |landmark| -landmark[:rating] }
|
|
74
|
+
|
|
75
|
+
by_type = landmarks.group_by { |landmark| landmark[:type] }
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
:statistics => statistics,
|
|
79
|
+
:ranked => ranked,
|
|
80
|
+
:by_type => by_type
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Fix the Ruby singleton method 'sort_by_rating' on this Robot instance.
|
|
85
|
+
#
|
|
86
|
+
# Robot's overall goal: Analyze landmark data. The input is an Array of Hashes, each with keys :name (String), :type (String), :duration (Integer), :rating (Float). Return a Hash with three keys: :statistics (a Hash with :avg_rating, :avg_duration, :total_duration, :count), :ranked (the landmarks Array sorted by :rating descending), :by_type (a Hash grouping landmarks by :type)
|
|
87
|
+
# Generated capabilities on this object: [:calculate_statistics, :sort_by_rating, :group_by_type, :execute_task]
|
|
88
|
+
#
|
|
89
|
+
# Current source code of 'sort_by_rating':
|
|
90
|
+
# def sort_by_rating(landmarks)
|
|
91
|
+
# landmarks.sort_by { |landmark| -landmark[:rating] || 0 }
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# Runtime error:
|
|
95
|
+
# NoMethodError: undefined method 'sort_by' for nil
|
|
96
|
+
#
|
|
97
|
+
# Backtrace (top 5):
|
|
98
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):2:in 'sort_by_rating'
|
|
99
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):3:in 'execute_task'
|
|
100
|
+
# /Users/dewayne/sandbox/git_repos/madbomber/self_agency/examples/12_autonomous_robots/robot.rb:72:in 'Robot#perform_task'
|
|
101
|
+
# ./main.rb:102:in '<main>'
|
|
102
|
+
#
|
|
103
|
+
# This method takes no external input.
|
|
104
|
+
#
|
|
105
|
+
# Produce a corrected version of this method that avoids the error.
|
|
106
|
+
# Keep the same method name and signature. Only define this one method.
|
|
107
|
+
# Fix the bug while preserving the method's intent.
|
|
108
|
+
def sort_by_rating(landmarks)
|
|
109
|
+
return {
|
|
110
|
+
:statistics => {
|
|
111
|
+
:average_rating => 0.0,
|
|
112
|
+
:average_duration => 0.0,
|
|
113
|
+
:total_duration => 0,
|
|
114
|
+
:count => 0
|
|
115
|
+
},
|
|
116
|
+
:ranked => [],
|
|
117
|
+
:by_type => {}
|
|
118
|
+
} if landmarks.nil? || landmarks.empty?
|
|
119
|
+
|
|
120
|
+
total_rating = 0.0
|
|
121
|
+
total_duration = 0
|
|
122
|
+
count = landmarks.length
|
|
123
|
+
|
|
124
|
+
landmarks.each do |landmark|
|
|
125
|
+
total_rating += landmark[:rating] || 0.0
|
|
126
|
+
total_duration += landmark[:duration] || 0
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
average_rating = count > 0 ? total_rating / count : 0.0
|
|
130
|
+
average_duration = count > 0 ? total_duration.to_f / count : 0.0
|
|
131
|
+
|
|
132
|
+
ranked = landmarks.sort_by { |landmark| -(landmark[:rating] || 0.0) }
|
|
133
|
+
|
|
134
|
+
by_type = ranked.group_by { |landmark| landmark[:type] }
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
:statistics => {
|
|
138
|
+
:average_rating => average_rating,
|
|
139
|
+
:average_duration => average_duration,
|
|
140
|
+
:total_duration => total_duration,
|
|
141
|
+
:count => count
|
|
142
|
+
},
|
|
143
|
+
:ranked => ranked,
|
|
144
|
+
:by_type => by_type
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# def group_by_type(landmarks) -> Hash
|
|
149
|
+
# Groups an array of landmark hashes by their type.
|
|
150
|
+
# Parameters: landmarks (Array[Hash]) - array of landmark data hashes
|
|
151
|
+
# Returns: Hash with type names as keys and Arrays of landmark hashes as values
|
|
152
|
+
# Algorithm:
|
|
153
|
+
# 1. Initialize an empty Hash result
|
|
154
|
+
# 2. Iterate through each landmark in landmarks array
|
|
155
|
+
# 3. For each landmark, get the :type key value
|
|
156
|
+
# 4. If result has a key for this type, append landmark to that array
|
|
157
|
+
# 5. Otherwise, create new array with landmark as first element
|
|
158
|
+
# 6. Return the grouped result hash
|
|
159
|
+
def group_by_type(landmarks)
|
|
160
|
+
result = {}
|
|
161
|
+
landmarks.each do |landmark|
|
|
162
|
+
type = landmark[:type]
|
|
163
|
+
if result.key?(type)
|
|
164
|
+
result[type] << landmark
|
|
165
|
+
else
|
|
166
|
+
result[type] = [landmark]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
result
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# An instance method named 'execute_task' that orchestrates the following
|
|
173
|
+
# goal: Analyze landmark data. The input is an Array of Hashes, each with keys :name (String), :type (String), :duration (Integer), :rating (Float). Return a Hash with three keys: :statistics (a Hash with :avg_rating, :avg_duration, :total_duration, :count), :ranked (the landmarks Array sorted by :rating descending), :by_type (a Hash grouping landmarks by :type)
|
|
174
|
+
#
|
|
175
|
+
# It takes one parameter (input) which is the data passed in from a previous stage.
|
|
176
|
+
#
|
|
177
|
+
# Available helper methods on this object: calculate_statistics, sort_by_rating, group_by_type
|
|
178
|
+
#
|
|
179
|
+
# Call the helper methods in whatever order makes sense to accomplish the goal.
|
|
180
|
+
# Return the final result. Do NOT define the helper methods — they already exist.
|
|
181
|
+
# Only define the execute_task method itself.
|
|
182
|
+
def execute_task(input)
|
|
183
|
+
statistics = calculate_statistics(input)
|
|
184
|
+
ranked = sort_by_rating(input)
|
|
185
|
+
by_type = group_by_type(input)
|
|
186
|
+
|
|
187
|
+
{
|
|
188
|
+
:statistics => statistics,
|
|
189
|
+
:ranked => ranked,
|
|
190
|
+
:by_type => by_type
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "robot"
|
|
4
|
+
|
|
5
|
+
class Collector < Robot
|
|
6
|
+
# def generate_landmark_data
|
|
7
|
+
# landmarks = []
|
|
8
|
+
# 8.times do |i|
|
|
9
|
+
# name = "Landmark #{i + 1}"
|
|
10
|
+
# type = ['museum', 'park', 'bridge', 'tower', 'theater', 'garden', 'statue', 'monument'].sample
|
|
11
|
+
# duration = rand(30..180)
|
|
12
|
+
# rating = (rand(100..500).to_f / 100.0).round(1)
|
|
13
|
+
# landmarks << { name: name, type: type, duration: duration, rating: rating }
|
|
14
|
+
# end
|
|
15
|
+
# landmarks
|
|
16
|
+
# end
|
|
17
|
+
def generate_landmark_data
|
|
18
|
+
landmarks = []
|
|
19
|
+
8.times do |i|
|
|
20
|
+
name = "Landmark #{i + 1}"
|
|
21
|
+
type = ['museum', 'park', 'bridge', 'tower', 'theater', 'garden', 'statue', 'monument'].sample
|
|
22
|
+
duration = rand(30..180)
|
|
23
|
+
rating = (rand(100..500).to_f / 100.0).round(1)
|
|
24
|
+
landmark = { name: name, type: type, duration: duration, rating: rating }
|
|
25
|
+
landmarks << landmark
|
|
26
|
+
end
|
|
27
|
+
landmarks
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Fix the Ruby singleton method 'execute_task' on this Robot instance.
|
|
31
|
+
#
|
|
32
|
+
# Robot's overall goal: Return an Array of 8 Hashes representing fictional city landmarks. Each Hash has Symbol keys :name (String), :type (String, e.g. 'museum'), :duration (Integer, 30..180 minutes), :rating (Float, 1.0..5.0). Do NOT wrap the Array in an outer Hash.
|
|
33
|
+
# Generated capabilities on this object: [:generate_landmark_data, :execute_task]
|
|
34
|
+
#
|
|
35
|
+
# Current source code of 'execute_task':
|
|
36
|
+
# def execute_task
|
|
37
|
+
# landmarks = []
|
|
38
|
+
# 8.times do
|
|
39
|
+
# data = generate_landmark_data
|
|
40
|
+
# landmarks << {
|
|
41
|
+
# name: data[:name],
|
|
42
|
+
# type: data[:type],
|
|
43
|
+
# duration: rand(30..180),
|
|
44
|
+
# rating: rand(1.0..5.0)
|
|
45
|
+
# }
|
|
46
|
+
# end
|
|
47
|
+
# landmarks
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# Runtime error:
|
|
51
|
+
# TypeError: no implicit conversion of Symbol into Integer
|
|
52
|
+
#
|
|
53
|
+
# Backtrace (top 5):
|
|
54
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):6:in 'block in execute_task'
|
|
55
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):3:in 'Integer#times'
|
|
56
|
+
# (eval at /Users/dewayne/sandbox/git_repos/madbomber/self_agency/lib/self_agency.rb:265):3:in 'execute_task'
|
|
57
|
+
# /Users/dewayne/sandbox/git_repos/madbomber/self_agency/examples/12_autonomous_robots/robot.rb:70:in 'Robot#perform_task'
|
|
58
|
+
# ./main.rb:94:in '<main>'
|
|
59
|
+
#
|
|
60
|
+
# This method takes no external input.
|
|
61
|
+
#
|
|
62
|
+
# Produce a corrected version of this method that avoids the error.
|
|
63
|
+
# Keep the same method name and signature. Only define this one method.
|
|
64
|
+
# Fix the bug while preserving the method's intent.
|
|
65
|
+
def execute_task
|
|
66
|
+
landmarks = []
|
|
67
|
+
8.times do
|
|
68
|
+
data = generate_landmark_data
|
|
69
|
+
landmarks << {
|
|
70
|
+
name: data[:name],
|
|
71
|
+
type: data[:type],
|
|
72
|
+
duration: rand(30..180),
|
|
73
|
+
rating: rand(1.0..5.0)
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
landmarks
|
|
77
|
+
end
|
|
78
|
+
end
|