foobara-agent 0.0.1 → 0.0.2

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: db38ddbbaba0842ca16f3155372979cae6ed6d2807c5dd2a62dcaf7f9b93941b
4
- data.tar.gz: 0415660f250abc75647efc61ed27635fd82c7cd2ceadb4d9ac93e1cf5bb4c3bf
3
+ metadata.gz: 7288308d62332addbe98233f7652d718b16f496b02a632426c418642959c5aa4
4
+ data.tar.gz: 2da140d10bf62e8858de3d5f77c732a0295a4179dfa22e3ddb2c3becf7421029
5
5
  SHA512:
6
- metadata.gz: a9c2401005ddc1f5cb03ed562a5a811b01d575234713d9d6c0cd4b720fc6a776637bb3195b0a96b38945a4b1c4bdeb862a2de12bb83d2fb7267e8fbd6f30903c
7
- data.tar.gz: abf74b0929795f22d434a813794b0f99516d1e817b57dfd9de31cd50833096c384c423deae386c68d91d9692e7b5eebad0739cd38d2e122dc8990c6c6fdb3822
6
+ metadata.gz: 830b3932828ccb30587c64214425f7b4eea862f5deec5c6a9a8d2a8d80733720b66e0d73f936444d91f5dffa6b7f27d8736638c000e17d2caa37cbff477fe6d2
7
+ data.tar.gz: cd9e89b6dc3dc81f2bd71ed5d6269f7dc8268d950bb120508c8295d1b89b64cd345d79fec8aa2f85cba0945e534e5fc62305ca070f78490f5c3f0303bf72f011
data/lib/foobara/agent.rb CHANGED
@@ -13,10 +13,10 @@ module Foobara
13
13
  [
14
14
  DetermineInputsForNextCommand,
15
15
  DetermineNextCommand,
16
- EndSessionBecauseGoalHasBeenAccomplished
16
+ NotifyUserThatCurrentGoalHasBeenAccomplished
17
17
  ].each do |command_class|
18
- command_class.clear_cache
19
- Util.descendants(command_class).each(&:clear_cache)
18
+ command_class.clear_subclass_cache
19
+ Util.descendants(command_class).each(&:clear_subclass_cache)
20
20
  end
21
21
  end
22
22
  end
@@ -5,20 +5,40 @@ module Foobara
5
5
  class Agent
6
6
  class AccomplishGoal < Foobara::Command
7
7
  possible_error :gave_up, context: { reason: :string }, message: "Gave up."
8
+ possible_error :too_many_command_calls,
9
+ context: { maximum_command_calls: :integer }
8
10
 
9
11
  inputs do
10
12
  agent_name :string, "Name of the agent"
11
13
  goal :string, :required, "What do you want the agent to attempt to accomplish?"
12
14
  # TODO: we should be able to specify a subclass as a type
13
- command_classes [:duck], "Commands that can be ran to accomplish the goal"
15
+ command_classes [Class], "Commands that can be ran to accomplish the goal"
14
16
  final_result_type :duck, "Specifies how the result of the goal is to be structured"
15
- existing_command_connector :duck, "A connector containing already-connected commands for the agent to use"
16
- current_context :duck, "The current context of the agent"
17
+ existing_command_connector CommandConnector, :allow_nil,
18
+ "A connector containing already-connected commands for the agent to use"
19
+ current_context Context, :allow_nil, "The current context of the agent"
20
+ maximum_command_calls :integer,
21
+ :allow_nil,
22
+ default: 25,
23
+ description: "Maximum number of commands to run before giving up"
17
24
  llm_model :string,
18
25
  :allow_nil,
19
26
  one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
20
27
  default: "claude-3-7-sonnet-20250219",
21
28
  description: "The model to use for the LLM"
29
+ log_successful_determine_command_and_inputs_outcomes(
30
+ :boolean,
31
+ default: true,
32
+ description: "You can experiment with turning this off " \
33
+ "if you want to see what happens if we don't log " \
34
+ "successful command/input selection outcomes"
35
+ )
36
+ choose_next_command_and_next_inputs_separately :boolean,
37
+ default: false,
38
+ description:
39
+ "By default, asks for next command and inputs together. " \
40
+ "You can experiment with getting the separately " \
41
+ "with this flag if you wish."
22
42
  end
23
43
 
24
44
  result do
@@ -36,26 +56,22 @@ module Foobara
36
56
  else
37
57
  build_command_connector
38
58
  connect_user_provided_commands
39
- connect_agent_commands
40
59
  end
41
60
 
42
- unless command_connector.agent_commands_connected?
61
+ unless agent_commands_connected?
43
62
  connect_agent_commands
44
63
  end
45
64
 
46
65
  until mission_accomplished or given_up or timed_out
47
- determine_next_command_name
48
-
49
- if command_described?
50
- fetch_next_command_class
51
- determine_next_command_inputs
66
+ if choose_next_command_and_next_inputs_separately?
67
+ determine_next_command_then_inputs_separately
52
68
  else
53
- choose_describe_command_instead
54
- fetch_next_command_class
69
+ determine_next_command_and_inputs
55
70
  end
56
71
 
57
72
  run_next_command
58
- log_command_outcome
73
+ log_last_command_outcome
74
+ check_if_too_many_calls
59
75
  end
60
76
 
61
77
  if given_up
@@ -65,6 +81,10 @@ module Foobara
65
81
  build_result
66
82
  end
67
83
 
84
+ def agent_commands_connected?
85
+ command_connector.agent_commands_connected?
86
+ end
87
+
68
88
  def validate
69
89
  validate_either_command_classes_or_connector_given
70
90
  end
@@ -100,8 +120,7 @@ module Foobara
100
120
  accomplish_goal_command: self,
101
121
  default_serializers: [
102
122
  Foobara::CommandConnectors::Serializers::ErrorsSerializer,
103
- Foobara::CommandConnectors::Serializers::AtomicSerializer,
104
- Foobara::CommandConnectors::Serializers::JsonSerializer
123
+ Foobara::CommandConnectors::Serializers::AtomicSerializer
105
124
  ],
106
125
  llm_model:
107
126
  )
@@ -121,7 +140,125 @@ module Foobara
121
140
  end
122
141
  end
123
142
 
124
- def determine_next_command_name
143
+ def determine_next_command_and_inputs(retries = 1)
144
+ if context.command_log.empty?
145
+ self.next_command_name = ListCommands.full_command_name
146
+ self.next_command_inputs = nil
147
+ fetch_next_command_class
148
+ return
149
+ end
150
+
151
+ inputs_for_determine = {
152
+ goal:,
153
+ context:,
154
+ llm_model:,
155
+ command_class_names: all_command_classes
156
+ }
157
+
158
+ determine_command = DetermineNextCommandNameAndInputs.new(inputs_for_determine)
159
+
160
+ outcome = determine_command.run
161
+
162
+ if outcome.success?
163
+ self.next_command_name = outcome.result[:command_name]
164
+ self.next_command_inputs = outcome.result[:inputs]
165
+
166
+ outcome = validate_next_command_name
167
+
168
+ if outcome.success?
169
+ fetch_next_command_class
170
+
171
+ if next_command_has_inputs?
172
+ outcome = validate_next_command_inputs
173
+
174
+ if outcome.success?
175
+ if log_successful_determine_command_and_inputs_outcomes?
176
+ log_command_outcome(
177
+ command: determine_command,
178
+ inputs: determine_command.inputs.except(:context)
179
+ )
180
+ end
181
+ else
182
+ log_command_outcome(
183
+ command: determine_command,
184
+ inputs: determine_command.inputs.except(:context),
185
+ outcome:
186
+ )
187
+
188
+ determine_next_command_inputs
189
+ end
190
+ else
191
+ self.next_command_inputs = {}
192
+ end
193
+ else
194
+ log_command_outcome(
195
+ command: determine_command,
196
+ inputs: determine_command.inputs&.except(:context),
197
+ outcome:
198
+ )
199
+
200
+ if retries > 0
201
+ determine_next_command_and_inputs(retries - 1)
202
+ else
203
+ determine_next_command_then_inputs_separately
204
+ end
205
+ end
206
+ else
207
+ log_command_outcome(
208
+ command_name: determine_command.class.full_command_name,
209
+ inputs: determine_command.inputs&.except(:context),
210
+ outcome:
211
+ )
212
+
213
+ if retries > 0
214
+ determine_next_command_and_inputs(retries - 1)
215
+ else
216
+ determine_next_command_then_inputs_separately
217
+ end
218
+ end
219
+ end
220
+
221
+ def determine_next_command_then_inputs_separately
222
+ determine_next_command_name
223
+
224
+ if command_described?
225
+ fetch_next_command_class
226
+ determine_next_command_inputs
227
+ else
228
+ choose_describe_command_instead
229
+ fetch_next_command_class
230
+ end
231
+ end
232
+
233
+ def validate_next_command_name
234
+ outcome = command_name_type.process_value(next_command_name)
235
+
236
+ if outcome.success?
237
+ self.next_command_name = outcome.result
238
+ end
239
+
240
+ outcome
241
+ end
242
+
243
+ def validate_next_command_inputs
244
+ inputs_type = next_command_class.inputs_type
245
+
246
+ outcome = NestedTransactionable.with_needed_transactions_for_type(inputs_type) do
247
+ inputs_type.process_value(next_command_inputs)
248
+ end
249
+
250
+ if outcome.success?
251
+ self.next_command_inputs = outcome.result
252
+ end
253
+
254
+ outcome
255
+ end
256
+
257
+ def command_name_type
258
+ @command_name_type ||= Agent.foobara_type_from_declaration(:string, one_of: all_command_classes)
259
+ end
260
+
261
+ def determine_next_command_name(retries = 1)
125
262
  self.next_command_name = if context.command_log.empty?
126
263
  ListCommands.full_command_name
127
264
  elsif delayed_command_name
@@ -138,7 +275,32 @@ module Foobara
138
275
  inputs[:llm_model] = llm_model
139
276
  end
140
277
 
141
- command_class.run!(inputs)
278
+ command = command_class.new(inputs)
279
+ outcome = command.run
280
+
281
+ if outcome.success?
282
+ if log_successful_determine_command_and_inputs_outcomes?
283
+ log_command_outcome(
284
+ command:,
285
+ inputs: command.inputs.except(:context)
286
+ )
287
+ end
288
+ else
289
+ # TODO: either figure out a way to hit this path in the test suite or delete it
290
+ # :nocov:
291
+ log_command_outcome(
292
+ command:,
293
+ inputs: command.inputs.except(:context)
294
+ )
295
+
296
+ if retries > 0
297
+ return determine_next_command_name(retries - 1)
298
+ end
299
+ # :nocov:
300
+ end
301
+
302
+ outcome.raise!
303
+ outcome.result
142
304
  end
143
305
  end
144
306
 
@@ -156,23 +318,54 @@ module Foobara
156
318
  self.next_command_class = command_connector.transformed_command_from_name(next_command_name)
157
319
  end
158
320
 
159
- def determine_next_command_inputs
160
- type = next_command_class.inputs_type
161
-
162
- self.next_command_inputs = if type && !empty_attributes?(type)
163
- command_class = DetermineInputsForNextCommand.for(
164
- command_class: next_command_class, agent_id: agent_name
165
- )
321
+ def determine_next_command_inputs(retries = 1)
322
+ self.next_command_inputs = if next_command_has_inputs?
323
+ command_class = command_class_for_determine_inputs_for_next_command
166
324
 
167
325
  inputs = { goal:, context: }
168
326
  if llm_model
169
327
  inputs[:llm_model] = llm_model
170
328
  end
171
329
 
172
- command_class.run!(inputs)
330
+ command = command_class.new(inputs)
331
+ outcome = command.run
332
+
333
+ if outcome.success?
334
+ if log_successful_determine_command_and_inputs_outcomes?
335
+ log_command_outcome(
336
+ command:,
337
+ inputs: command.inputs.except(:context)
338
+ )
339
+ end
340
+ else
341
+ # TODO: either figure out a way to hit this path in the test suite or delete it
342
+ # :nocov:
343
+ log_command_outcome(
344
+ command:,
345
+ inputs: command.inputs.except(:context)
346
+ )
347
+ if retries > 0
348
+ return determine_next_command_inputs(retries - 1)
349
+ end
350
+ # :nocov:
351
+ end
352
+
353
+ outcome.raise!
354
+ outcome.result
173
355
  end
174
356
  end
175
357
 
358
+ def next_command_has_inputs?
359
+ type = next_command_class.inputs_type
360
+ type && !empty_attributes?(type)
361
+ end
362
+
363
+ def command_class_for_determine_inputs_for_next_command
364
+ DetermineInputsForNextCommand.for(
365
+ command_class: next_command_class, agent_id: agent_name
366
+ )
367
+ end
368
+
176
369
  def run_next_command
177
370
  self.command_response = command_connector.run(
178
371
  full_command_name: next_command_name,
@@ -183,20 +376,42 @@ module Foobara
183
376
  self.command_outcome = command_response.outcome
184
377
  end
185
378
 
186
- def log_command_outcome
187
- outcome_hash = { success: command_outcome.success? }
379
+ def log_last_command_outcome
380
+ log_command_outcome(command: command_response.command)
381
+ end
382
+
383
+ def check_if_too_many_calls
384
+ if context.command_log.size > maximum_command_calls
385
+ add_runtime_error(
386
+ :too_many_command_calls,
387
+ "Too many command calls. " \
388
+ "Stopping. Increase maximum_command_calls if #{maximum_command_calls} is not enough.",
389
+ maximum_command_calls:
390
+ )
391
+ end
392
+ end
188
393
 
189
- if command_outcome.success?
190
- outcome_hash[:result] = command_response.body
191
- else
192
- # :nocov:
193
- outcome_hash[:errors_hash] = command_response.body
194
- # :nocov:
394
+ def log_command_outcome(command: nil, command_name: nil, inputs: nil, outcome: nil, result: nil)
395
+ if command
396
+ command_name ||= command.class.full_command_name
397
+ inputs ||= command.inputs
398
+ outcome ||= command.outcome
399
+ result ||= outcome.result
400
+ end
401
+
402
+ outcome_hash = { success: outcome.success? }
403
+
404
+ if outcome.success? || result
405
+ outcome_hash[:result] = result
406
+ end
407
+
408
+ unless outcome.success?
409
+ outcome_hash[:errors_hash] = outcome.errors_hash
195
410
  end
196
411
 
197
412
  context.command_log << CommandLogEntry.new(
198
- command_name: next_command_name,
199
- inputs: next_command_inputs,
413
+ command_name:,
414
+ inputs:,
200
415
  outcome: outcome_hash
201
416
  )
202
417
  end
@@ -235,6 +450,14 @@ module Foobara
235
450
  def empty_attributes?(type)
236
451
  type.extends_type?(BuiltinTypes[:attributes]) && type.element_types.empty?
237
452
  end
453
+
454
+ def log_successful_determine_command_and_inputs_outcomes?
455
+ log_successful_determine_command_and_inputs_outcomes
456
+ end
457
+
458
+ def choose_next_command_and_next_inputs_separately?
459
+ choose_next_command_and_next_inputs_separately
460
+ end
238
461
  end
239
462
  end
240
463
  end
@@ -0,0 +1,24 @@
1
+ module Foobara
2
+ class Agent
3
+ module Concerns
4
+ # There's nothing really subclass-specific about this concern, maybe rename it...
5
+ module SubclassCacheable
6
+ def subclass_cache
7
+ @subclass_cache ||= {}
8
+ end
9
+
10
+ def clear_subclass_cache
11
+ @subclass_cache = nil
12
+ end
13
+
14
+ def cached_subclass(key)
15
+ if subclass_cache.key?(key)
16
+ subclass_cache[key]
17
+ else
18
+ subclass_cache[key] = yield
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -36,12 +36,12 @@ module Foobara
36
36
  ]
37
37
 
38
38
  command_classes << if final_result_type
39
- EndSessionBecauseGoalHasBeenAccomplished.for(
39
+ NotifyUserThatCurrentGoalHasBeenAccomplished.for(
40
40
  result_type: final_result_type,
41
41
  agent_id: agent_name
42
42
  )
43
43
  else
44
- EndSessionBecauseGoalHasBeenAccomplished
44
+ NotifyUserThatCurrentGoalHasBeenAccomplished
45
45
  end
46
46
 
47
47
  command_classes.each do |command_class|
@@ -41,13 +41,13 @@ module Foobara
41
41
 
42
42
  def set_inputs_type
43
43
  if command_class.inputs_type
44
- command_description[:inputs_type] = JsonSchemaGenerator.to_json_schema(command_class.inputs_type)
44
+ command_description[:inputs_type] = JSON.parse(JsonSchemaGenerator.to_json_schema(command_class.inputs_type))
45
45
  end
46
46
  end
47
47
 
48
48
  def set_result_type
49
49
  if command_class.result_type
50
- command_description[:result_type] = JsonSchemaGenerator.to_json_schema(command_class.result_type)
50
+ command_description[:result_type] = JSON.parse(JsonSchemaGenerator.to_json_schema(command_class.result_type))
51
51
  end
52
52
  end
53
53
 
@@ -3,35 +3,15 @@ require "foobara/llm_backed_command"
3
3
  module Foobara
4
4
  class Agent
5
5
  class DetermineInputsForNextCommand < Foobara::LlmBackedCommand
6
- class << self
7
- attr_accessor :command_class
8
-
9
- def command_cache
10
- @command_cache ||= {}
11
- end
12
-
13
- def clear_cache
14
- @command_cache = nil
15
- end
16
-
17
- def cached_command(agent_id, full_command_name)
18
- key = [agent_id, full_command_name]
19
-
20
- if command_cache.key?(key)
21
- command_cache[key]
22
- else
23
- command_cache[key] = yield
24
- end
25
- end
6
+ extend Concerns::SubclassCacheable
26
7
 
8
+ class << self
27
9
  def for(command_class:, agent_id:)
28
- cached_command(agent_id, command_class.full_command_name) do
10
+ cached_subclass([command_class.full_command_name, agent_id]) do
29
11
  command_short_name = Util.non_full_name(command_class.command_name)
30
12
  class_name = "Foobara::Agent::#{agent_id}::DetermineInputsForNext#{command_short_name}Command"
31
13
  klass = Util.make_class_p(class_name, self)
32
14
 
33
- klass.command_class = command_class
34
-
35
15
  klass.description "Accepts a goal and context of the work so far and returns the inputs for " \
36
16
  "the next #{command_short_name} command to run to make progress towards " \
37
17
  "accomplishing the goal."
@@ -53,23 +33,6 @@ module Foobara
53
33
  end
54
34
  end
55
35
  end
56
-
57
- description "Accepts a goal and context of the work so far and returns the inputs for the next command to " \
58
- "run to make progress towards accomplishing the mission."
59
-
60
- inputs do
61
- goal :string, :required, "What do you want the agent to attempt to accomplish?"
62
- context Context, :required, "Context of the current mission so far"
63
- command_class :duck, :required, "Command to run to accomplish the goal"
64
- llm_model :string,
65
- one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
66
- default: "claude-3-7-sonnet-20250219",
67
- description: "The model to use for the LLM"
68
- end
69
-
70
- result :duck,
71
- description: "Inputs to pass to the next command to run to make progress " \
72
- "towards accomplishing the mission."
73
36
  end
74
37
  end
75
38
  end
@@ -3,35 +3,18 @@ require "foobara/llm_backed_command"
3
3
  module Foobara
4
4
  class Agent
5
5
  class DetermineNextCommand < Foobara::LlmBackedCommand
6
- class << self
7
- attr_accessor :command_class_names
8
-
9
- def command_cache
10
- @command_cache ||= {}
11
- end
12
-
13
- def clear_cache
14
- @command_cache = nil
15
- end
16
-
17
- def cached_command(agent_id)
18
- if command_cache.key?(agent_id)
19
- command_cache[agent_id]
20
- else
21
- command_cache[agent_id] = yield
22
- end
23
- end
6
+ extend Concerns::SubclassCacheable
24
7
 
8
+ class << self
9
+ # Allows us to give a more meaningful result type
25
10
  def for(command_class_names:, agent_id:)
26
- cached_command(agent_id) do
11
+ cached_subclass(agent_id) do
27
12
  command_name = "Foobara::Agent::#{agent_id}::DetermineNextCommand"
28
13
  klass = Util.make_class_p(command_name, self)
29
14
 
30
- klass.command_class_names = command_class_names
31
-
32
15
  klass.inputs do
33
16
  goal :string, :required, "What do you want the agent to attempt to accomplish?"
34
- context Context, :required, "Context of the current mission so far"
17
+ context Context, :required, "Context of progress so far"
35
18
  llm_model :string,
36
19
  one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
37
20
  default: "claude-3-7-sonnet-20250219",
@@ -49,19 +32,7 @@ module Foobara
49
32
  end
50
33
 
51
34
  description "Accepts a goal and context of the work so far and returns the name of the next command to run to " \
52
- "make progress towards accomplishing the mission. Make sure you have called DescribeCommand the" \
53
- "command first so that you will know how to construct its inputs in the next step."
54
-
55
- inputs do
56
- goal :string, :required, "What do you want the agent to attempt to accomplish?"
57
- context Context, :required, "Context of the current mission so far"
58
- llm_model :string,
59
- one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
60
- default: "claude-3-7-sonnet-20250219",
61
- description: "The model to use for the LLM"
62
- end
63
-
64
- result :string, description: "Name of the next command to run to make progress towards accomplishing the mission."
35
+ "make progress towards accomplishing the mission."
65
36
  end
66
37
  end
67
38
  end
@@ -0,0 +1,26 @@
1
+ require "foobara/llm_backed_command"
2
+
3
+ module Foobara
4
+ class Agent
5
+ class DetermineNextCommandNameAndInputs < Foobara::LlmBackedCommand
6
+ description "Accepts a goal and context of the work so far and returns the inputs for " \
7
+ "the next command to run to make progress towards " \
8
+ "accomplishing the goal."
9
+
10
+ inputs do
11
+ goal :string, :required, "What do you want the agent to attempt to accomplish?"
12
+ context Context, :required, "Context of the progress towards the goal so far"
13
+ command_class_names [:string], :required
14
+ llm_model :string,
15
+ one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
16
+ default: "claude-3-7-sonnet-20250219",
17
+ description: "The model to use for the LLM"
18
+ end
19
+
20
+ result do
21
+ command_name :string, :required
22
+ inputs :attributes, :allow_nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,7 +2,7 @@ module Foobara
2
2
  class Agent
3
3
  class GiveUp < Foobara::Command
4
4
  inputs do
5
- command_connector :duck, :required, "Connector to end"
5
+ command_connector CommandConnector, :required, "Connector to end"
6
6
  message_to_user :string, "Optional message to the user explaining why you decided to give up"
7
7
  end
8
8
 
@@ -1,59 +1,48 @@
1
1
  module Foobara
2
2
  class Agent
3
- class EndSessionBecauseGoalHasBeenAccomplished < Foobara::Command
3
+ class NotifyUserThatCurrentGoalHasBeenAccomplished < Foobara::Command
4
+ extend Concerns::SubclassCacheable
5
+
4
6
  class << self
5
7
  attr_accessor :command_class
6
8
 
7
- def command_cache
8
- @command_cache ||= {}
9
- end
10
-
11
- def clear_cache
12
- @command_cache = nil
13
- end
14
-
15
- def cached_command(agent_id, result_type)
16
- key = [agent_id, result_type]
17
-
18
- if command_cache.key?(key)
19
- # :nocov:
20
- command_cache[key]
21
- # :nocov:
22
- else
23
- command_cache[key] = yield
24
- end
25
- end
26
-
27
9
  def for(result_type:, agent_id:)
28
- cached_command(agent_id, result_type) do
29
- command_name = "Foobara::Agent::#{agent_id}::EndSessionBecauseGoalHasBeenAccomplished"
10
+ cached_subclass([result_type, agent_id]) do
11
+ command_name = "Foobara::Agent::#{agent_id}::NotifyUserThatCurrentGoalHasBeenAccomplished"
30
12
  klass = Util.make_class_p(command_name, self)
31
13
 
32
- klass.description "Ends the session giving a final result formatted according to the " \
33
- "result schema if relevant and an optional message to the user."
14
+ klass.description "Notifies the user that the current goal has been accomplished and returns a final " \
15
+ "result formatted according to the " \
16
+ "result schema and an optional message to the user. " \
17
+ "The user might issue a new goal."
34
18
 
35
19
  inputs do
36
- # TODO: Are we still not able to uses classes as foobara types??
37
- command_connector :duck, :required, "Connector to end"
20
+ command_connector CommandConnector, :required, "Connector to notify user through"
38
21
  message_to_user :string, "Optional message to the user"
39
22
  end
40
23
 
41
24
  if result_type
42
25
  add_inputs do
43
- result_data(*result_type)
26
+ result_data result_type
44
27
  end
45
28
 
46
29
  klass.result do
47
30
  message_to_user :string
48
- result_data(*result_type)
31
+ result_data result_type
49
32
  end
33
+ klass.description "Notifies the user that the current goal has been accomplished and returns a final " \
34
+ "result formatted according to the " \
35
+ "result schema if relevant and an optional message to the user. " \
36
+ "The user might issue a new goal."
50
37
 
51
- klass.description "Ends the session giving a final result formatted according to the " \
52
- "result schema and an optional message to the user."
53
38
  else
54
39
  # TODO: test this code path
55
40
  # :nocov:
56
- klass.description "Ends the session giving an optional message to the user."
41
+ klass.description "Notifies the user that the current goal has been accomplished and, if relevant, " \
42
+ "returns a final " \
43
+ "result formatted according to the " \
44
+ "result schema and an optional message to the user. " \
45
+ "The user might issue a new goal."
57
46
  # :nocov:
58
47
  end
59
48
 
@@ -3,12 +3,12 @@ module Foobara
3
3
  class CommandLogEntry < Foobara::Model
4
4
  attributes do
5
5
  command_name :string, :required, "Name of the command that was run"
6
- inputs :duck, :required, "Inputs to the command" # TODO: Allow :attributes to be used as a type succesfully
6
+ inputs :attributes, :allow_nil, "Inputs to the command"
7
7
  outcome :required do
8
8
  success :boolean, :required, "Whether the command succeeded or not"
9
- result :duck, :allow_nil, "Result of the command"
9
+ result :duck, "Result of the command"
10
10
  # TODO: create a type for error hash structure
11
- errors_hash :duck, :allow_nil, "Errors that occurred during the command"
11
+ errors_hash :duck, "Errors that occurred during the command"
12
12
  end
13
13
  end
14
14
  end
data/src/foobara/agent.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "io/wait"
2
-
3
1
  module Foobara
4
2
  class Agent
5
3
  attr_accessor :context, :agent_command_connector, :agent_name, :llm_model
@@ -25,19 +23,33 @@ module Foobara
25
23
  end
26
24
  end
27
25
 
28
- def accomplish_goal(goal, result_type: nil)
26
+ def accomplish_goal(
27
+ goal,
28
+ result_type: nil,
29
+ choose_next_command_and_next_inputs_separately: nil,
30
+ maximum_call_count: nil
31
+ )
29
32
  inputs = {
30
33
  goal:,
31
34
  final_result_type: result_type,
32
35
  current_context: context,
33
36
  existing_command_connector: agent_command_connector,
34
37
  agent_name:
38
+
35
39
  }
36
40
 
37
41
  if llm_model
38
42
  inputs[:llm_model] = llm_model
39
43
  end
40
44
 
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
48
+
49
+ unless maximum_call_count.nil?
50
+ inputs[:maximum_command_calls] = maximum_call_count
51
+ end
52
+
41
53
  AccomplishGoal.run(inputs)
42
54
  end
43
55
 
@@ -52,8 +64,7 @@ module Foobara
52
64
  llm_model:,
53
65
  default_serializers: [
54
66
  Foobara::CommandConnectors::Serializers::ErrorsSerializer,
55
- Foobara::CommandConnectors::Serializers::AtomicSerializer,
56
- Foobara::CommandConnectors::Serializers::JsonSerializer
67
+ Foobara::CommandConnectors::Serializers::AtomicSerializer
57
68
  ]
58
69
  )
59
70
  end
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.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -50,16 +50,18 @@ files:
50
50
  - lib/foobara/agent.rb
51
51
  - src/foobara/agent.rb
52
52
  - src/foobara/agent/accomplish_goal.rb
53
+ - src/foobara/agent/concerns/subclass_cacheable.rb
53
54
  - src/foobara/agent/connector/connector.rb
54
55
  - src/foobara/agent/connector/set_command_connector_inputs_transformer.rb
55
56
  - src/foobara/agent/describe_command.rb
56
57
  - src/foobara/agent/describe_type.rb
57
58
  - src/foobara/agent/determine_inputs_for_next_command.rb
58
59
  - src/foobara/agent/determine_next_command.rb
59
- - src/foobara/agent/end_session_because_goal_has_been_accomplished.rb
60
+ - src/foobara/agent/determine_next_command_name_and_inputs.rb
60
61
  - src/foobara/agent/give_up.rb
61
62
  - src/foobara/agent/list_commands.rb
62
63
  - src/foobara/agent/list_types.rb
64
+ - src/foobara/agent/notify_user_that_current_goal_has_been_accomplished.rb
63
65
  - src/foobara/agent/types/command_log_entry.rb
64
66
  - src/foobara/agent/types/context.rb
65
67
  homepage: https://github.com/foobara/agent