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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7288308d62332addbe98233f7652d718b16f496b02a632426c418642959c5aa4
4
- data.tar.gz: 2da140d10bf62e8858de3d5f77c732a0295a4179dfa22e3ddb2c3becf7421029
3
+ metadata.gz: 88666f0c9353fd88d386752d774b3a5abac7bf035b9349769049a1e633fb6a4e
4
+ data.tar.gz: 60a09c5cd82d3c4b580ab3224875bc78b3086f04d91e3c28da6708483d4b3806
5
5
  SHA512:
6
- metadata.gz: 830b3932828ccb30587c64214425f7b4eea862f5deec5c6a9a8d2a8d80733720b66e0d73f936444d91f5dffa6b7f27d8736638c000e17d2caa37cbff477fe6d2
7
- data.tar.gz: cd9e89b6dc3dc81f2bd71ed5d6269f7dc8268d950bb120508c8295d1b89b64cd345d79fec8aa2f85cba0945e534e5fc62305ca070f78490f5c3f0303bf72f011
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 or timed_out
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 = 1)
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 = determine_command.run
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 = 1)
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 = command.run
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 = 1)
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 = command.run
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
- context.command_log << CommandLogEntry.new(
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, "What do you want the agent to attempt to accomplish?"
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, "What do you want the agent to attempt to accomplish?"
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 a goal and context of the work so far and returns the inputs for " \
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, "What do you want the agent to attempt to accomplish?"
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
- attr_accessor :context, :agent_command_connector, :agent_name, :llm_model
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
- inputs = {
33
- goal:,
34
- final_result_type: result_type,
35
- current_context: context,
36
- existing_command_connector: agent_command_connector,
37
- agent_name:
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
- if llm_model
42
- inputs[:llm_model] = llm_model
43
- end
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
- unless choose_next_command_and_next_inputs_separately.nil?
46
- inputs[:choose_next_command_and_next_inputs_separately] = choose_next_command_and_next_inputs_separately
47
- end
89
+ if llm_model
90
+ inputs[:llm_model] = llm_model
91
+ end
48
92
 
49
- unless maximum_call_count.nil?
50
- inputs[:maximum_command_calls] = maximum_call_count
51
- end
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
- AccomplishGoal.run(inputs)
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi