foobara-agent 0.0.2 → 0.0.3
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/CHANGELOG.md +13 -0
- data/src/foobara/agent/accomplish_goal.rb +39 -16
- data/src/foobara/agent/determine_inputs_for_next_command.rb +1 -1
- data/src/foobara/agent/determine_next_command.rb +10 -1
- data/src/foobara/agent/determine_next_command_name_and_inputs.rb +7 -3
- data/src/foobara/agent.rb +81 -19
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88666f0c9353fd88d386752d774b3a5abac7bf035b9349769049a1e633fb6a4e
|
4
|
+
data.tar.gz: 60a09c5cd82d3c4b580ab3224875bc78b3086f04d91e3c28da6708483d4b3806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5590b86e0dbb6baf8feac06ed1dce7a95a066acf3d684203dff92b86adb1fc329a217ec702affa332d9b6b6c2d6d187b5e3c960ee4b22a7263c0412760fd30c2
|
7
|
+
data.tar.gz: 13e01dd7118c05188ffd6a58ba51923eec81b14b227506a6fdeaf0c731dfef4bc41a14a052f2c17c2e46e3d32438a181ff906a082bf7f123ee7210fca1efc959
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [0.0.3] - 2025-05-30
|
2
|
+
|
3
|
+
- Increase the retries, attempt to improve command descriptions,
|
4
|
+
and attempt to improve the information stored in the context/command log
|
5
|
+
- Add some safeguards around changing the result type too late in the process
|
6
|
+
- Add an agent state machine and a kill! method
|
7
|
+
|
8
|
+
## [0.0.2] - 2025-05-27
|
9
|
+
|
10
|
+
- Add maximum_call_count option
|
11
|
+
- Try getting the command and inputs together to reduce calls
|
12
|
+
- Tweaks to algorithms and improvements to what is stored in context/command_log
|
13
|
+
|
1
14
|
## [0.0.1] - 2025-05-21
|
2
15
|
|
3
16
|
- Initial release
|
@@ -62,7 +62,8 @@ module Foobara
|
|
62
62
|
connect_agent_commands
|
63
63
|
end
|
64
64
|
|
65
|
-
until mission_accomplished or given_up
|
65
|
+
until mission_accomplished or given_up
|
66
|
+
check_if_too_many_calls
|
66
67
|
if choose_next_command_and_next_inputs_separately?
|
67
68
|
determine_next_command_then_inputs_separately
|
68
69
|
else
|
@@ -71,7 +72,6 @@ module Foobara
|
|
71
72
|
|
72
73
|
run_next_command
|
73
74
|
log_last_command_outcome
|
74
|
-
check_if_too_many_calls
|
75
75
|
end
|
76
76
|
|
77
77
|
if given_up
|
@@ -140,7 +140,7 @@ module Foobara
|
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
143
|
-
def determine_next_command_and_inputs(retries =
|
143
|
+
def determine_next_command_and_inputs(retries = 2)
|
144
144
|
if context.command_log.empty?
|
145
145
|
self.next_command_name = ListCommands.full_command_name
|
146
146
|
self.next_command_inputs = nil
|
@@ -157,7 +157,11 @@ module Foobara
|
|
157
157
|
|
158
158
|
determine_command = DetermineNextCommandNameAndInputs.new(inputs_for_determine)
|
159
159
|
|
160
|
-
outcome =
|
160
|
+
outcome = begin
|
161
|
+
determine_command.run
|
162
|
+
rescue CommandPatternImplementation::Concerns::Result::CouldNotProcessResult => e
|
163
|
+
Outcome.errors(e.errors)
|
164
|
+
end
|
161
165
|
|
162
166
|
if outcome.success?
|
163
167
|
self.next_command_name = outcome.result[:command_name]
|
@@ -182,7 +186,8 @@ module Foobara
|
|
182
186
|
log_command_outcome(
|
183
187
|
command: determine_command,
|
184
188
|
inputs: determine_command.inputs.except(:context),
|
185
|
-
outcome
|
189
|
+
outcome:,
|
190
|
+
result: outcome.result || determine_command.raw_result
|
186
191
|
)
|
187
192
|
|
188
193
|
determine_next_command_inputs
|
@@ -194,7 +199,8 @@ module Foobara
|
|
194
199
|
log_command_outcome(
|
195
200
|
command: determine_command,
|
196
201
|
inputs: determine_command.inputs&.except(:context),
|
197
|
-
outcome
|
202
|
+
outcome:,
|
203
|
+
result: outcome.result || determine_command.raw_result
|
198
204
|
)
|
199
205
|
|
200
206
|
if retries > 0
|
@@ -207,7 +213,8 @@ module Foobara
|
|
207
213
|
log_command_outcome(
|
208
214
|
command_name: determine_command.class.full_command_name,
|
209
215
|
inputs: determine_command.inputs&.except(:context),
|
210
|
-
outcome
|
216
|
+
outcome:,
|
217
|
+
result: outcome.result || determine_command.raw_result
|
211
218
|
)
|
212
219
|
|
213
220
|
if retries > 0
|
@@ -258,7 +265,7 @@ module Foobara
|
|
258
265
|
@command_name_type ||= Agent.foobara_type_from_declaration(:string, one_of: all_command_classes)
|
259
266
|
end
|
260
267
|
|
261
|
-
def determine_next_command_name(retries =
|
268
|
+
def determine_next_command_name(retries = 2)
|
262
269
|
self.next_command_name = if context.command_log.empty?
|
263
270
|
ListCommands.full_command_name
|
264
271
|
elsif delayed_command_name
|
@@ -276,13 +283,18 @@ module Foobara
|
|
276
283
|
end
|
277
284
|
|
278
285
|
command = command_class.new(inputs)
|
279
|
-
outcome =
|
286
|
+
outcome = begin
|
287
|
+
command.run
|
288
|
+
rescue CommandPatternImplementation::Concerns::Result::CouldNotProcessResult => e
|
289
|
+
Outcome.errors(e.errors)
|
290
|
+
end
|
280
291
|
|
281
292
|
if outcome.success?
|
282
293
|
if log_successful_determine_command_and_inputs_outcomes?
|
283
294
|
log_command_outcome(
|
284
295
|
command:,
|
285
|
-
inputs: command.inputs.except(:context)
|
296
|
+
inputs: command.inputs.except(:context),
|
297
|
+
outcome:
|
286
298
|
)
|
287
299
|
end
|
288
300
|
else
|
@@ -290,7 +302,9 @@ module Foobara
|
|
290
302
|
# :nocov:
|
291
303
|
log_command_outcome(
|
292
304
|
command:,
|
293
|
-
inputs: command.inputs.except(:context)
|
305
|
+
inputs: command.inputs.except(:context),
|
306
|
+
outcome:,
|
307
|
+
result: outcome.result || command.raw_result
|
294
308
|
)
|
295
309
|
|
296
310
|
if retries > 0
|
@@ -318,7 +332,7 @@ module Foobara
|
|
318
332
|
self.next_command_class = command_connector.transformed_command_from_name(next_command_name)
|
319
333
|
end
|
320
334
|
|
321
|
-
def determine_next_command_inputs(retries =
|
335
|
+
def determine_next_command_inputs(retries = 2)
|
322
336
|
self.next_command_inputs = if next_command_has_inputs?
|
323
337
|
command_class = command_class_for_determine_inputs_for_next_command
|
324
338
|
|
@@ -328,13 +342,18 @@ module Foobara
|
|
328
342
|
end
|
329
343
|
|
330
344
|
command = command_class.new(inputs)
|
331
|
-
outcome =
|
345
|
+
outcome = begin
|
346
|
+
command.run
|
347
|
+
rescue CommandPatternImplementation::Concerns::Result::CouldNotProcessResult => e
|
348
|
+
Outcome.errors(e.errors)
|
349
|
+
end
|
332
350
|
|
333
351
|
if outcome.success?
|
334
352
|
if log_successful_determine_command_and_inputs_outcomes?
|
335
353
|
log_command_outcome(
|
336
354
|
command:,
|
337
|
-
inputs: command.inputs.except(:context)
|
355
|
+
inputs: command.inputs.except(:context),
|
356
|
+
outcome:
|
338
357
|
)
|
339
358
|
end
|
340
359
|
else
|
@@ -342,7 +361,9 @@ module Foobara
|
|
342
361
|
# :nocov:
|
343
362
|
log_command_outcome(
|
344
363
|
command:,
|
345
|
-
inputs: command.inputs.except(:context)
|
364
|
+
inputs: command.inputs.except(:context),
|
365
|
+
outcome:,
|
366
|
+
result: outcome.result || command.raw_result
|
346
367
|
)
|
347
368
|
if retries > 0
|
348
369
|
return determine_next_command_inputs(retries - 1)
|
@@ -409,11 +430,13 @@ module Foobara
|
|
409
430
|
outcome_hash[:errors_hash] = outcome.errors_hash
|
410
431
|
end
|
411
432
|
|
412
|
-
|
433
|
+
log_entry = CommandLogEntry.new(
|
413
434
|
command_name:,
|
414
435
|
inputs:,
|
415
436
|
outcome: outcome_hash
|
416
437
|
)
|
438
|
+
|
439
|
+
context.command_log << log_entry
|
417
440
|
end
|
418
441
|
|
419
442
|
# TODO: these are awkwardly called from outside. Come up with a better solution.
|
@@ -17,7 +17,7 @@ module Foobara
|
|
17
17
|
"accomplishing the goal."
|
18
18
|
|
19
19
|
klass.inputs do
|
20
|
-
goal :string, :required, "
|
20
|
+
goal :string, :required, "The current (possibly already accomplished) goal"
|
21
21
|
context Context, :required, "Context of the progress towards the goal so far"
|
22
22
|
llm_model :string,
|
23
23
|
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
@@ -12,8 +12,17 @@ module Foobara
|
|
12
12
|
command_name = "Foobara::Agent::#{agent_id}::DetermineNextCommand"
|
13
13
|
klass = Util.make_class_p(command_name, self)
|
14
14
|
|
15
|
+
klass.description "Accepts the current goal, which might already be accomplished, " \
|
16
|
+
"and context of the work " \
|
17
|
+
"so far and returns the name of " \
|
18
|
+
"the next command to run to make progress towards " \
|
19
|
+
"accomplishing the goal. If the goal has already been accomplished then choose the " \
|
20
|
+
"NotifyUserThatCurrentGoalHasBeenAccomplished command."
|
21
|
+
|
15
22
|
klass.inputs do
|
16
|
-
goal :string, :required, "
|
23
|
+
goal :string, :required, "The current goal to accomplish. If the goal has already been accomplished " \
|
24
|
+
"by the previous command runs then choose " \
|
25
|
+
"NotifyUserThatCurrentGoalHasBeenAccomplished to stop the loop."
|
17
26
|
context Context, :required, "Context of progress so far"
|
18
27
|
llm_model :string,
|
19
28
|
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
@@ -3,12 +3,16 @@ require "foobara/llm_backed_command"
|
|
3
3
|
module Foobara
|
4
4
|
class Agent
|
5
5
|
class DetermineNextCommandNameAndInputs < Foobara::LlmBackedCommand
|
6
|
-
description "Accepts
|
6
|
+
description "Accepts the current goal, which might already be accomplished, and context of the work " \
|
7
|
+
"so far and returns the inputs for " \
|
7
8
|
"the next command to run to make progress towards " \
|
8
|
-
"accomplishing the goal."
|
9
|
+
"accomplishing the goal. If the goal has already been accomplished then choose the " \
|
10
|
+
"NotifyUserThatCurrentGoalHasBeenAccomplished command."
|
9
11
|
|
10
12
|
inputs do
|
11
|
-
goal :string, :required, "
|
13
|
+
goal :string, :required, "The current goal to accomplish. If the goal has already been accomplished " \
|
14
|
+
"by the previous command runs then choose " \
|
15
|
+
"NotifyUserThatCurrentGoalHasBeenAccomplished to stop the loop."
|
12
16
|
context Context, :required, "Context of the progress towards the goal so far"
|
13
17
|
command_class_names [:string], :required
|
14
18
|
llm_model :string,
|
data/src/foobara/agent.rb
CHANGED
@@ -1,19 +1,39 @@
|
|
1
1
|
module Foobara
|
2
2
|
class Agent
|
3
|
-
|
3
|
+
StateMachine = Foobara::StateMachine.for(
|
4
|
+
[:initialized, :idle, :error, :failure] => {
|
5
|
+
kill: :killed,
|
6
|
+
accomplish_goal: :accomplishing_goal
|
7
|
+
},
|
8
|
+
accomplishing_goal: {
|
9
|
+
goal_accomplished: :idle,
|
10
|
+
goal_errored: :error,
|
11
|
+
goal_failed: :failure,
|
12
|
+
kill: :killed
|
13
|
+
}
|
14
|
+
)
|
15
|
+
|
16
|
+
attr_accessor :context,
|
17
|
+
:agent_command_connector,
|
18
|
+
:agent_name,
|
19
|
+
:llm_model,
|
20
|
+
:current_accomplish_goal_command,
|
21
|
+
:result_type
|
4
22
|
|
5
23
|
def initialize(
|
6
24
|
context: nil,
|
7
25
|
agent_name: nil,
|
8
26
|
command_classes: nil,
|
9
27
|
agent_command_connector: nil,
|
10
|
-
llm_model: nil
|
28
|
+
llm_model: nil,
|
29
|
+
result_type: nil
|
11
30
|
)
|
12
31
|
# TODO: shouldn't have to pass command_log here since it has a default, debug that
|
13
32
|
self.context = context
|
14
33
|
self.agent_command_connector = agent_command_connector
|
15
34
|
self.agent_name = agent_name if agent_name
|
16
35
|
self.llm_model = llm_model
|
36
|
+
self.result_type = result_type
|
17
37
|
|
18
38
|
build_initial_context
|
19
39
|
build_agent_command_connector
|
@@ -23,34 +43,76 @@ module Foobara
|
|
23
43
|
end
|
24
44
|
end
|
25
45
|
|
46
|
+
def state_machine
|
47
|
+
@state_machine ||= StateMachine.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def kill!
|
51
|
+
state_machine.perform_transition!(:kill)
|
52
|
+
end
|
53
|
+
|
54
|
+
def killed?
|
55
|
+
state_machine.current_state == :killed
|
56
|
+
end
|
57
|
+
|
26
58
|
def accomplish_goal(
|
27
59
|
goal,
|
28
60
|
result_type: nil,
|
29
61
|
choose_next_command_and_next_inputs_separately: nil,
|
30
62
|
maximum_call_count: nil
|
31
63
|
)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
64
|
+
if result_type && self.result_type != result_type
|
65
|
+
if self.result_type
|
66
|
+
# :nocov:
|
67
|
+
raise ArgumentError, "You can only specify a result type once"
|
68
|
+
# :nocov:
|
69
|
+
elsif agent_command_connector.agent_commands_connected?
|
70
|
+
# :nocov:
|
71
|
+
raise ArgumentError, "You can't specify a result type this late in the process"
|
72
|
+
# :nocov:
|
73
|
+
else
|
74
|
+
self.result_type = result_type
|
75
|
+
end
|
76
|
+
end
|
38
77
|
|
39
|
-
|
78
|
+
state_machine.perform_transition!(:accomplish_goal)
|
40
79
|
|
41
|
-
|
42
|
-
inputs
|
43
|
-
|
80
|
+
begin
|
81
|
+
inputs = {
|
82
|
+
goal:,
|
83
|
+
final_result_type: self.result_type,
|
84
|
+
current_context: context,
|
85
|
+
existing_command_connector: agent_command_connector,
|
86
|
+
agent_name:
|
87
|
+
}
|
44
88
|
|
45
|
-
|
46
|
-
|
47
|
-
|
89
|
+
if llm_model
|
90
|
+
inputs[:llm_model] = llm_model
|
91
|
+
end
|
48
92
|
|
49
|
-
|
50
|
-
|
51
|
-
|
93
|
+
unless choose_next_command_and_next_inputs_separately.nil?
|
94
|
+
inputs[:choose_next_command_and_next_inputs_separately] = choose_next_command_and_next_inputs_separately
|
95
|
+
end
|
96
|
+
|
97
|
+
unless maximum_call_count.nil?
|
98
|
+
inputs[:maximum_command_calls] = maximum_call_count
|
99
|
+
end
|
52
100
|
|
53
|
-
|
101
|
+
self.current_accomplish_goal_command = AccomplishGoal.new(inputs)
|
102
|
+
|
103
|
+
current_accomplish_goal_command.run.tap do |outcome|
|
104
|
+
if outcome.success?
|
105
|
+
state_machine.perform_transition!(:goal_accomplished)
|
106
|
+
else
|
107
|
+
state_machine.perform_transition!(:goal_errored)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
rescue
|
111
|
+
# :nocov:
|
112
|
+
state_machine.perform_transition!(:goal_failed)
|
113
|
+
raise
|
114
|
+
# :nocov:
|
115
|
+
end
|
54
116
|
end
|
55
117
|
|
56
118
|
def build_initial_context
|