foobara-agent 0.0.8 → 0.0.10
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 +12 -0
- data/lib/foobara/agent.rb +0 -1
- data/src/foobara/agent/accomplish_goal.rb +111 -174
- data/src/foobara/agent/concerns/subclass_cacheable.rb +2 -0
- data/src/foobara/agent/determine_base.rb +82 -0
- data/src/foobara/agent/determine_next_command_name_and_inputs.rb +6 -22
- data/src/foobara/agent/notify_user_that_current_goal_has_been_accomplished.rb +2 -0
- data/src/foobara/agent/types/context.rb +14 -0
- data/src/foobara/agent.rb +9 -11
- metadata +2 -3
- data/src/foobara/agent/determine_inputs_for_next_command.rb +0 -45
- data/src/foobara/agent/determine_next_command.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c8f095d06fb173bc816e83f19755cfe246f15bf2b8ef1f8f91bce08e5960e74
|
4
|
+
data.tar.gz: 15b48a59c43c909070f68bbc58c68ce02635d019f9d3af68f1c3aa4264cd4903
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f558c74920f9759a98b2605fe2dcbbc52678ff5d90a0977182a75d0949cfd400b07d3d17d4dbfe162b37a3f463c3c55b4e25dee6382f2daeb3256b5df9615f73
|
7
|
+
data.tar.gz: f0588e94676e579c1fd5d7b3b603452d0c283b2b14217511b74a8d341d3126ff42cf7d0cb1cc881048d356525c7b9250f73c1a7631dae556a589f23b8627e1f8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## [0.0.10] - 2025-06-30
|
2
|
+
|
3
|
+
- Allow more retries
|
4
|
+
- Eliminate ability to select command and inputs separately
|
5
|
+
|
6
|
+
## [0.0.9] - 2025-06-28
|
7
|
+
|
8
|
+
- Relocate some common Determine* behavior into a DetermineBase
|
9
|
+
- Move goal to Context and make use of LlmBackedCommand#messages
|
10
|
+
- Simulating a DescribeCommand selection on failure
|
11
|
+
- Compacting the command log
|
12
|
+
|
1
13
|
## [0.0.8] - 2025-06-27
|
2
14
|
|
3
15
|
- Improve what is logged and its formatting when verbose
|
data/lib/foobara/agent.rb
CHANGED
@@ -19,7 +19,7 @@ module Foobara
|
|
19
19
|
io_out :duck
|
20
20
|
io_err :duck
|
21
21
|
agent Agent, :required
|
22
|
-
|
22
|
+
context Context, :required, "The current context of the agent"
|
23
23
|
maximum_command_calls :integer,
|
24
24
|
:allow_nil,
|
25
25
|
default: 25,
|
@@ -29,12 +29,6 @@ module Foobara
|
|
29
29
|
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
30
30
|
default: "claude-3-7-sonnet-20250219",
|
31
31
|
description: "The model to use for the LLM"
|
32
|
-
choose_next_command_and_next_inputs_separately :boolean,
|
33
|
-
default: false,
|
34
|
-
description:
|
35
|
-
"By default, asks for next command and inputs together. " \
|
36
|
-
"You can experiment with getting the separately " \
|
37
|
-
"with this flag if you wish."
|
38
32
|
max_llm_calls_per_minute :integer, :allow_nil
|
39
33
|
end
|
40
34
|
|
@@ -46,8 +40,6 @@ module Foobara
|
|
46
40
|
depends_on ListCommands
|
47
41
|
|
48
42
|
def execute
|
49
|
-
build_initial_context_if_necessary
|
50
|
-
|
51
43
|
simulate_describe_list_commands_command
|
52
44
|
simulate_list_commands_run
|
53
45
|
# simulate_describe_command_run_for_all_commands
|
@@ -58,13 +50,9 @@ module Foobara
|
|
58
50
|
|
59
51
|
throttle_llm_calls_if_necessary
|
60
52
|
|
61
|
-
|
62
|
-
determine_next_command_then_inputs_separately
|
63
|
-
else
|
64
|
-
determine_next_command_and_inputs
|
65
|
-
end
|
66
|
-
|
53
|
+
determine_next_command_and_inputs
|
67
54
|
run_next_command
|
55
|
+
|
68
56
|
log_last_command_outcome
|
69
57
|
end
|
70
58
|
|
@@ -75,16 +63,11 @@ module Foobara
|
|
75
63
|
build_result
|
76
64
|
end
|
77
65
|
|
78
|
-
attr_accessor :
|
66
|
+
attr_accessor :next_command_name, :next_command_inputs, :next_command_raw_inputs, :mission_accomplished,
|
79
67
|
:given_up, :next_command_class, :next_command, :command_outcome, :timed_out,
|
80
68
|
:final_result, :final_message, :command_response, :delayed_command_name,
|
81
69
|
:command_calls
|
82
70
|
|
83
|
-
def build_initial_context_if_necessary
|
84
|
-
# TODO: shouldn't have to pass command_log here since it has a default, debug that
|
85
|
-
self.context = current_context || Context.new(command_log: [])
|
86
|
-
end
|
87
|
-
|
88
71
|
def simulate_list_commands_run
|
89
72
|
self.next_command_name = ListCommands.full_command_name
|
90
73
|
self.next_command_raw_inputs = nil
|
@@ -105,6 +88,26 @@ module Foobara
|
|
105
88
|
log_last_command_outcome
|
106
89
|
end
|
107
90
|
|
91
|
+
def simulate_describe_command(command_name = next_command_name)
|
92
|
+
old_next_command_name = next_command_name
|
93
|
+
old_next_command_inputs = next_command_inputs
|
94
|
+
old_next_command_raw_inputs = next_command_raw_inputs
|
95
|
+
old_next_command_class = next_command_class
|
96
|
+
|
97
|
+
self.next_command_name = DescribeCommand.full_command_name
|
98
|
+
self.next_command_inputs = { command_name: }
|
99
|
+
self.next_command_raw_inputs = next_command_inputs
|
100
|
+
fetch_next_command_class
|
101
|
+
|
102
|
+
run_next_command
|
103
|
+
log_last_command_outcome
|
104
|
+
|
105
|
+
self.next_command_name = old_next_command_name
|
106
|
+
self.next_command_inputs = old_next_command_inputs
|
107
|
+
self.next_command_raw_inputs = old_next_command_raw_inputs
|
108
|
+
self.next_command_class = old_next_command_class
|
109
|
+
end
|
110
|
+
|
108
111
|
def simulate_describe_command_run_for_all_commands
|
109
112
|
# TODO: currently not using this code path. Unclear if it is worth it.
|
110
113
|
# :nocov:
|
@@ -124,9 +127,25 @@ module Foobara
|
|
124
127
|
# :nocov:
|
125
128
|
end
|
126
129
|
|
127
|
-
def determine_next_command_and_inputs(retries =
|
130
|
+
def determine_next_command_and_inputs(retries = 3, error_outcome = nil)
|
131
|
+
if retries == 0
|
132
|
+
# TODO: test this path by irreparably breaking the needed commands
|
133
|
+
# :nocov:
|
134
|
+
self.next_command_name = GiveUp.full_command_name
|
135
|
+
self.next_command_inputs = {
|
136
|
+
message_to_user: "While trying to choose the next command and inputs, " \
|
137
|
+
"I've ran into an error several times that I couldn't figure out how to get past. " \
|
138
|
+
"The last error looked like this:\n#{error_outcome.errors_hash}"
|
139
|
+
}
|
140
|
+
self.next_command_raw_inputs = next_command_inputs
|
141
|
+
|
142
|
+
return
|
143
|
+
# :nocov:
|
144
|
+
end
|
145
|
+
|
146
|
+
compact_command_log
|
147
|
+
|
128
148
|
inputs_for_determine = {
|
129
|
-
goal:,
|
130
149
|
context:,
|
131
150
|
llm_model:
|
132
151
|
}
|
@@ -143,7 +162,7 @@ module Foobara
|
|
143
162
|
end
|
144
163
|
|
145
164
|
if outcome.success?
|
146
|
-
self.next_command_name = outcome.result[:
|
165
|
+
self.next_command_name = outcome.result[:command]
|
147
166
|
self.next_command_inputs = outcome.result[:inputs]
|
148
167
|
self.next_command_raw_inputs = next_command_inputs
|
149
168
|
|
@@ -162,7 +181,9 @@ module Foobara
|
|
162
181
|
outcome:
|
163
182
|
)
|
164
183
|
|
165
|
-
|
184
|
+
simulate_describe_command
|
185
|
+
|
186
|
+
determine_next_command_and_inputs(retries - 1, outcome)
|
166
187
|
end
|
167
188
|
else
|
168
189
|
self.next_command_inputs = {}
|
@@ -176,11 +197,7 @@ module Foobara
|
|
176
197
|
result: nil
|
177
198
|
)
|
178
199
|
|
179
|
-
|
180
|
-
determine_next_command_and_inputs(retries - 1)
|
181
|
-
else
|
182
|
-
determine_next_command_then_inputs_separately
|
183
|
-
end
|
200
|
+
determine_next_command_and_inputs(retries - 1, outcome)
|
184
201
|
end
|
185
202
|
else
|
186
203
|
log_command_outcome(
|
@@ -190,23 +207,7 @@ module Foobara
|
|
190
207
|
result: outcome.result || determine_command.raw_result
|
191
208
|
)
|
192
209
|
|
193
|
-
|
194
|
-
determine_next_command_and_inputs(retries - 1)
|
195
|
-
else
|
196
|
-
determine_next_command_then_inputs_separately
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def determine_next_command_then_inputs_separately
|
202
|
-
determine_next_command_name
|
203
|
-
|
204
|
-
if command_described?
|
205
|
-
fetch_next_command_class
|
206
|
-
determine_next_command_inputs
|
207
|
-
else
|
208
|
-
choose_describe_command_instead
|
209
|
-
fetch_next_command_class
|
210
|
+
determine_next_command_and_inputs(retries - 1, outcome)
|
210
211
|
end
|
211
212
|
end
|
212
213
|
|
@@ -245,74 +246,6 @@ module Foobara
|
|
245
246
|
@command_name_type ||= Agent.foobara_type_from_declaration(:string, one_of: all_command_classes)
|
246
247
|
end
|
247
248
|
|
248
|
-
def determine_next_command_name(retries = 2)
|
249
|
-
self.next_command_name = if delayed_command_name
|
250
|
-
name = delayed_command_name
|
251
|
-
self.delayed_command_name = nil
|
252
|
-
name
|
253
|
-
else
|
254
|
-
inputs = { goal:, context: }
|
255
|
-
if llm_model
|
256
|
-
inputs[:llm_model] = llm_model
|
257
|
-
end
|
258
|
-
|
259
|
-
command = DetermineNextCommand.new(inputs)
|
260
|
-
outcome = begin
|
261
|
-
record_llm_call_timestamp
|
262
|
-
command.run
|
263
|
-
rescue CommandPatternImplementation::Concerns::Result::CouldNotProcessResult => e
|
264
|
-
# :nocov:
|
265
|
-
Outcome.errors(e.errors)
|
266
|
-
# :nocov:
|
267
|
-
end
|
268
|
-
|
269
|
-
if outcome.success?
|
270
|
-
self.next_command_name = outcome.result
|
271
|
-
|
272
|
-
outcome = validate_next_command_name
|
273
|
-
|
274
|
-
unless outcome.success?
|
275
|
-
# TODO: figure out a way to hit this path in the test suite or delete it
|
276
|
-
# :nocov:
|
277
|
-
log_command_outcome(
|
278
|
-
command:,
|
279
|
-
inputs: command.inputs.except(:context),
|
280
|
-
outcome:,
|
281
|
-
result: outcome.result || command.raw_result
|
282
|
-
)
|
283
|
-
|
284
|
-
if retries > 0
|
285
|
-
return determine_next_command_name(retries - 1)
|
286
|
-
end
|
287
|
-
# :nocov:
|
288
|
-
end
|
289
|
-
else
|
290
|
-
# TODO: either figure out a way to hit this path in the test suite or delete it
|
291
|
-
# :nocov:
|
292
|
-
log_command_outcome(
|
293
|
-
command:,
|
294
|
-
inputs: command.inputs.except(:context),
|
295
|
-
outcome:,
|
296
|
-
result: outcome.result || command.raw_result
|
297
|
-
)
|
298
|
-
|
299
|
-
if retries > 0
|
300
|
-
return determine_next_command_name(retries - 1)
|
301
|
-
end
|
302
|
-
# :nocov:
|
303
|
-
end
|
304
|
-
|
305
|
-
outcome.raise!
|
306
|
-
outcome.result
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def choose_describe_command_instead
|
311
|
-
self.delayed_command_name = next_command_name
|
312
|
-
self.next_command_inputs = { command_name: next_command_name }
|
313
|
-
self.next_command_name = DescribeCommand.full_command_name
|
314
|
-
end
|
315
|
-
|
316
249
|
def all_command_classes
|
317
250
|
@all_command_classes ||= run_subcommand!(ListCommands, command_connector: agent).values.flatten
|
318
251
|
end
|
@@ -321,56 +254,11 @@ module Foobara
|
|
321
254
|
self.next_command_class = agent.transformed_command_from_name(next_command_name)
|
322
255
|
end
|
323
256
|
|
324
|
-
def determine_next_command_inputs(retries = 2)
|
325
|
-
self.next_command_inputs = if next_command_has_inputs?
|
326
|
-
command_class = command_class_for_determine_inputs_for_next_command
|
327
|
-
|
328
|
-
inputs = { goal:, context: }
|
329
|
-
if llm_model
|
330
|
-
inputs[:llm_model] = llm_model
|
331
|
-
end
|
332
|
-
|
333
|
-
command = command_class.new(inputs)
|
334
|
-
outcome = begin
|
335
|
-
record_llm_call_timestamp
|
336
|
-
command.run
|
337
|
-
rescue CommandPatternImplementation::Concerns::Result::CouldNotProcessResult => e
|
338
|
-
# :nocov:
|
339
|
-
Outcome.errors(e.errors)
|
340
|
-
# :nocov:
|
341
|
-
end
|
342
|
-
|
343
|
-
unless outcome.success?
|
344
|
-
# TODO: either figure out a way to hit this path in the test suite or delete it
|
345
|
-
# :nocov:
|
346
|
-
log_command_outcome(
|
347
|
-
command_name: next_command_name,
|
348
|
-
inputs: command.raw_result,
|
349
|
-
outcome:
|
350
|
-
)
|
351
|
-
|
352
|
-
if retries > 0
|
353
|
-
return determine_next_command_inputs(retries - 1)
|
354
|
-
end
|
355
|
-
# :nocov:
|
356
|
-
end
|
357
|
-
|
358
|
-
outcome.raise!
|
359
|
-
outcome.result
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
257
|
def next_command_has_inputs?
|
364
258
|
type = next_command_class.inputs_type
|
365
259
|
type && !empty_attributes?(type)
|
366
260
|
end
|
367
261
|
|
368
|
-
def command_class_for_determine_inputs_for_next_command
|
369
|
-
DetermineInputsForNextCommand.for(
|
370
|
-
command_class: next_command_class, agent_id: agent_name
|
371
|
-
)
|
372
|
-
end
|
373
|
-
|
374
262
|
def run_next_command
|
375
263
|
if verbose?
|
376
264
|
args = if next_command_inputs.nil? || next_command_inputs.empty?
|
@@ -386,6 +274,7 @@ module Foobara
|
|
386
274
|
# :nocov:
|
387
275
|
end
|
388
276
|
end
|
277
|
+
|
389
278
|
(io_out || $stdout).puts "#{next_command_name}.run#{args}"
|
390
279
|
end
|
391
280
|
|
@@ -412,6 +301,66 @@ module Foobara
|
|
412
301
|
log_command_outcome(command: command_response.command)
|
413
302
|
end
|
414
303
|
|
304
|
+
def compact_command_log
|
305
|
+
# Rules:
|
306
|
+
# Delete errors for any command that has succeeded since
|
307
|
+
# Delete all but the last DescribeCommand call for a given
|
308
|
+
describe_command_call_indexes = {}
|
309
|
+
commands = {}
|
310
|
+
|
311
|
+
describe_command_name = DescribeCommand.full_command_name
|
312
|
+
context.command_log.each.with_index do |command_log_entry, index|
|
313
|
+
command_name = command_log_entry.command_name
|
314
|
+
|
315
|
+
if command_name == describe_command_name
|
316
|
+
described_command = command_log_entry.inputs[:command_name]
|
317
|
+
|
318
|
+
if command_log_entry.outcome[:success]
|
319
|
+
describe_command_call_indexes[described_command] ||= []
|
320
|
+
describe_command_call_indexes[described_command] << index
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
commands[command_name] ||= [[], []]
|
325
|
+
bucket_index = command_log_entry.outcome[:success] ? 0 : 1
|
326
|
+
commands[command_name][bucket_index] << index
|
327
|
+
end
|
328
|
+
|
329
|
+
indexes_to_delete = []
|
330
|
+
|
331
|
+
describe_command_call_indexes.each_value do |indexes|
|
332
|
+
indexes_to_delete += indexes[0..-2]
|
333
|
+
end
|
334
|
+
|
335
|
+
commands.each_value do |(success_indexes, failure_indexes)|
|
336
|
+
last_success = success_indexes.last
|
337
|
+
next unless last_success
|
338
|
+
|
339
|
+
failure_indexes.each do |failure_index|
|
340
|
+
if failure_index < last_success
|
341
|
+
indexes_to_delete << failure_index
|
342
|
+
else
|
343
|
+
# :nocov:
|
344
|
+
break
|
345
|
+
# :nocov:
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
if indexes_to_delete.empty?
|
351
|
+
return
|
352
|
+
end
|
353
|
+
|
354
|
+
new_log = []
|
355
|
+
context.command_log = context.command_log.each.with_index do |entry, index|
|
356
|
+
unless indexes_to_delete.include?(index)
|
357
|
+
new_log << entry
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context.command_log = new_log
|
362
|
+
end
|
363
|
+
|
415
364
|
def increment_command_calls
|
416
365
|
self.command_calls ||= -1
|
417
366
|
self.command_calls += 1
|
@@ -435,7 +384,7 @@ module Foobara
|
|
435
384
|
outcome ||= command.outcome
|
436
385
|
end
|
437
386
|
|
438
|
-
if outcome
|
387
|
+
if outcome&.success?
|
439
388
|
result ||= outcome.result
|
440
389
|
end
|
441
390
|
|
@@ -481,10 +430,6 @@ module Foobara
|
|
481
430
|
}
|
482
431
|
end
|
483
432
|
|
484
|
-
def command_described?
|
485
|
-
described_commands.include?(next_command_name)
|
486
|
-
end
|
487
|
-
|
488
433
|
def described_commands
|
489
434
|
@described_commands ||= Set.new
|
490
435
|
end
|
@@ -493,14 +438,6 @@ module Foobara
|
|
493
438
|
type.extends_type?(BuiltinTypes[:attributes]) && type.element_types.empty?
|
494
439
|
end
|
495
440
|
|
496
|
-
def choose_next_command_and_next_inputs_separately?
|
497
|
-
choose_next_command_and_next_inputs_separately
|
498
|
-
end
|
499
|
-
|
500
|
-
def agent_name
|
501
|
-
agent.agent_name
|
502
|
-
end
|
503
|
-
|
504
441
|
def verbose?
|
505
442
|
verbose
|
506
443
|
end
|
@@ -529,7 +466,7 @@ module Foobara
|
|
529
466
|
first_to_expire = calls.first
|
530
467
|
|
531
468
|
if first_to_expire
|
532
|
-
(first_to_expire + SECONDS_PER_MINUTE) - Time.now
|
469
|
+
[0, (first_to_expire + SECONDS_PER_MINUTE) - Time.now].max
|
533
470
|
else
|
534
471
|
# TODO: figure out how to test this code path
|
535
472
|
# :nocov:
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "foobara/llm_backed_command"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
class Agent < CommandConnector
|
5
|
+
class DetermineBase < Foobara::LlmBackedCommand
|
6
|
+
inputs do
|
7
|
+
context Context, :required, "Context of the progress towards the goal so far"
|
8
|
+
llm_model :string,
|
9
|
+
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
10
|
+
default: "claude-3-7-sonnet-20250219",
|
11
|
+
description: "The model to use for the LLM"
|
12
|
+
end
|
13
|
+
|
14
|
+
def association_depth
|
15
|
+
Foobara::JsonSchemaGenerator::AssociationDepth::ATOM
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_messages
|
19
|
+
p = [
|
20
|
+
{
|
21
|
+
content: llm_instructions,
|
22
|
+
role: :system
|
23
|
+
}
|
24
|
+
]
|
25
|
+
|
26
|
+
context.command_log.each do |command_log_entry|
|
27
|
+
agent_entry = {
|
28
|
+
command: command_log_entry.command_name
|
29
|
+
}
|
30
|
+
|
31
|
+
inputs = command_log_entry.inputs
|
32
|
+
|
33
|
+
if inputs && !inputs.empty?
|
34
|
+
agent_entry[:inputs] = inputs
|
35
|
+
end
|
36
|
+
|
37
|
+
outcome_entry = command_log_entry.outcome
|
38
|
+
|
39
|
+
p << {
|
40
|
+
content: agent_entry,
|
41
|
+
role: :assistant
|
42
|
+
}
|
43
|
+
p << {
|
44
|
+
content: outcome_entry,
|
45
|
+
role: :user
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
p
|
50
|
+
end
|
51
|
+
|
52
|
+
def llm_instructions
|
53
|
+
return @llm_instructions if defined?(@llm_instructions)
|
54
|
+
|
55
|
+
description = self.class.description
|
56
|
+
|
57
|
+
instructions = "You are the implementation of a command called #{self.class.scoped_full_name}"
|
58
|
+
|
59
|
+
instructions += if description && !description.empty?
|
60
|
+
" which has the following description:\n\n#{self.class.description}\n\n"
|
61
|
+
else
|
62
|
+
# :nocov:
|
63
|
+
". "
|
64
|
+
# :nocov:
|
65
|
+
end
|
66
|
+
|
67
|
+
instructions += "You are working towards accomplishing the following goal:\n\n#{goal}\n\n"
|
68
|
+
instructions += "Your response should match the following JSON schema: \n\n#{self.class.result_json_schema}\n\n"
|
69
|
+
instructions += "You can get more details about the result schema for a specific command by " \
|
70
|
+
"choosing the DescribeCommand command. " \
|
71
|
+
"You will reply with nothing more than the JSON you've generated so that the calling code " \
|
72
|
+
"can successfully parse your answer."
|
73
|
+
|
74
|
+
@llm_instructions = instructions
|
75
|
+
end
|
76
|
+
|
77
|
+
def goal
|
78
|
+
context.current_goal
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,33 +1,17 @@
|
|
1
|
-
|
1
|
+
require_relative "determine_base"
|
2
2
|
|
3
3
|
module Foobara
|
4
4
|
class Agent < CommandConnector
|
5
|
-
class DetermineNextCommandNameAndInputs <
|
6
|
-
description "
|
7
|
-
"
|
8
|
-
"the
|
9
|
-
"accomplishing the goal. If the goal has already been accomplished then choose the " \
|
5
|
+
class DetermineNextCommandNameAndInputs < DetermineBase
|
6
|
+
description "Returns the name of the next command to run and its inputs given the progress " \
|
7
|
+
"towards accomplishing the current goal. " \
|
8
|
+
"If the goal has been accomplished it will choose the " \
|
10
9
|
"NotifyUserThatCurrentGoalHasBeenAccomplished command."
|
11
10
|
|
12
|
-
inputs do
|
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."
|
16
|
-
context Context, :required, "Context of the progress towards the goal so far"
|
17
|
-
llm_model :string,
|
18
|
-
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
19
|
-
default: "claude-3-7-sonnet-20250219",
|
20
|
-
description: "The model to use for the LLM"
|
21
|
-
end
|
22
|
-
|
23
11
|
result do
|
24
|
-
|
12
|
+
command :string, :required
|
25
13
|
inputs :attributes, :allow_nil
|
26
14
|
end
|
27
|
-
|
28
|
-
def association_depth
|
29
|
-
Foobara::JsonSchemaGenerator::AssociationDepth::ATOM
|
30
|
-
end
|
31
15
|
end
|
32
16
|
end
|
33
17
|
end
|
@@ -19,6 +19,8 @@ module Foobara
|
|
19
19
|
"The user might issue a new goal."
|
20
20
|
|
21
21
|
if result_type
|
22
|
+
# TODO: fix this... agent backed command sets these via its own result type.
|
23
|
+
# check if message_to_user is already here and also search/fix result_data to be result for consistency.
|
22
24
|
if include_message_to_user_in_result
|
23
25
|
klass.add_inputs do
|
24
26
|
result result_type, :required
|
@@ -1,11 +1,25 @@
|
|
1
1
|
module Foobara
|
2
2
|
class Agent < CommandConnector
|
3
3
|
class Context < Foobara::Model
|
4
|
+
class << self
|
5
|
+
def for(goal)
|
6
|
+
new(command_log: [], current_goal: goal)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
attributes do
|
11
|
+
current_goal :string, :required, "The current goal the agent needs to accomplish"
|
12
|
+
previous_goals [:string]
|
5
13
|
# TODO: why doesn't this default of [] work as expected on newly created models?
|
6
14
|
command_log [CommandLogEntry], default: [],
|
7
15
|
description: "Log of all commands run so far and their outcomes"
|
8
16
|
end
|
17
|
+
|
18
|
+
def set_new_goal(goal)
|
19
|
+
self.previous_goals ||= []
|
20
|
+
previous_goals << current_goal
|
21
|
+
self.current_goal = goal
|
22
|
+
end
|
9
23
|
end
|
10
24
|
end
|
11
25
|
end
|
data/src/foobara/agent.rb
CHANGED
@@ -67,8 +67,6 @@ module Foobara
|
|
67
67
|
# TODO: this should work now, switch to this approach
|
68
68
|
# add_default_inputs_transformer EntityToPrimaryKeyInputsTransformer
|
69
69
|
|
70
|
-
build_initial_context
|
71
|
-
|
72
70
|
# TODO: push this convenience method up into base class?
|
73
71
|
command_classes&.each do |command_class|
|
74
72
|
connect(command_class)
|
@@ -116,10 +114,11 @@ module Foobara
|
|
116
114
|
def accomplish_goal(
|
117
115
|
goal,
|
118
116
|
result_type: nil,
|
119
|
-
choose_next_command_and_next_inputs_separately: nil,
|
120
117
|
maximum_call_count: nil,
|
121
118
|
llm_model: nil
|
122
119
|
)
|
120
|
+
set_context_goal(goal)
|
121
|
+
|
123
122
|
if result_type && self.result_type != result_type
|
124
123
|
if self.result_type
|
125
124
|
# :nocov:
|
@@ -144,7 +143,7 @@ module Foobara
|
|
144
143
|
inputs = {
|
145
144
|
goal:,
|
146
145
|
final_result_type: self.result_type,
|
147
|
-
|
146
|
+
context:,
|
148
147
|
agent: self
|
149
148
|
}
|
150
149
|
|
@@ -154,10 +153,6 @@ module Foobara
|
|
154
153
|
inputs[:llm_model] = llm_model
|
155
154
|
end
|
156
155
|
|
157
|
-
unless choose_next_command_and_next_inputs_separately.nil?
|
158
|
-
inputs[:choose_next_command_and_next_inputs_separately] = choose_next_command_and_next_inputs_separately
|
159
|
-
end
|
160
|
-
|
161
156
|
unless maximum_call_count.nil?
|
162
157
|
inputs[:maximum_command_calls] = maximum_call_count
|
163
158
|
end
|
@@ -201,9 +196,12 @@ module Foobara
|
|
201
196
|
end
|
202
197
|
end
|
203
198
|
|
204
|
-
def
|
205
|
-
|
206
|
-
|
199
|
+
def set_context_goal(goal)
|
200
|
+
if context
|
201
|
+
context.set_new_goal(goal)
|
202
|
+
else
|
203
|
+
self.context = Context.for(goal)
|
204
|
+
end
|
207
205
|
end
|
208
206
|
|
209
207
|
def mark_mission_accomplished(final_result, message_to_user)
|
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.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -54,8 +54,7 @@ files:
|
|
54
54
|
- src/foobara/agent/connector/set_command_connector_inputs_transformer.rb
|
55
55
|
- src/foobara/agent/describe_command.rb
|
56
56
|
- src/foobara/agent/describe_type.rb
|
57
|
-
- src/foobara/agent/
|
58
|
-
- src/foobara/agent/determine_next_command.rb
|
57
|
+
- src/foobara/agent/determine_base.rb
|
59
58
|
- src/foobara/agent/determine_next_command_name_and_inputs.rb
|
60
59
|
- src/foobara/agent/give_up.rb
|
61
60
|
- src/foobara/agent/list_commands.rb
|
@@ -1,45 +0,0 @@
|
|
1
|
-
require "foobara/llm_backed_command"
|
2
|
-
|
3
|
-
module Foobara
|
4
|
-
class Agent < CommandConnector
|
5
|
-
class DetermineInputsForNextCommand < Foobara::LlmBackedCommand
|
6
|
-
extend Concerns::SubclassCacheable
|
7
|
-
|
8
|
-
class << self
|
9
|
-
def for(command_class:, agent_id:)
|
10
|
-
cached_subclass([command_class.full_command_name, agent_id]) do
|
11
|
-
command_short_name = Util.non_full_name(command_class.command_name)
|
12
|
-
class_name = "Foobara::Agent::#{agent_id}::DetermineInputsForNext#{command_short_name}Command"
|
13
|
-
klass = Util.make_class_p(class_name, self)
|
14
|
-
|
15
|
-
klass.description "Accepts a goal and context of the work so far and returns the inputs for " \
|
16
|
-
"the next #{command_short_name} command to run to make progress towards " \
|
17
|
-
"accomplishing the goal."
|
18
|
-
|
19
|
-
klass.inputs do
|
20
|
-
goal :string, :required, "The current (possibly already accomplished) goal"
|
21
|
-
context Context, :required, "Context of the progress towards the goal so far"
|
22
|
-
llm_model :string,
|
23
|
-
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
24
|
-
default: "claude-3-7-sonnet-20250219",
|
25
|
-
description: "The model to use for the LLM"
|
26
|
-
end
|
27
|
-
|
28
|
-
if command_class.inputs_type
|
29
|
-
transformer = CommandConnectors::Transformers::EntityToPrimaryKeyInputsTransformer.new(
|
30
|
-
to: command_class.inputs_type
|
31
|
-
)
|
32
|
-
klass.result transformer.from_type
|
33
|
-
end
|
34
|
-
|
35
|
-
klass
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def association_depth
|
41
|
-
Foobara::JsonSchemaGenerator::AssociationDepth::ATOM
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require "foobara/llm_backed_command"
|
2
|
-
|
3
|
-
module Foobara
|
4
|
-
class Agent < CommandConnector
|
5
|
-
class DetermineNextCommand < Foobara::LlmBackedCommand
|
6
|
-
description "Accepts the current goal, which might already be accomplished, " \
|
7
|
-
"and context of the work " \
|
8
|
-
"so far and returns the name of " \
|
9
|
-
"the next command to run to make progress towards " \
|
10
|
-
"accomplishing the goal. If the goal has already been accomplished then choose the " \
|
11
|
-
"NotifyUserThatCurrentGoalHasBeenAccomplished command."
|
12
|
-
|
13
|
-
inputs do
|
14
|
-
goal :string, :required, "The current goal to accomplish. If the goal has already been accomplished " \
|
15
|
-
"by the previous command runs then choose " \
|
16
|
-
"NotifyUserThatCurrentGoalHasBeenAccomplished to stop the loop."
|
17
|
-
context Context, :required, "Context of progress so far"
|
18
|
-
llm_model :string,
|
19
|
-
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
20
|
-
default: "claude-3-7-sonnet-20250219",
|
21
|
-
description: "The model to use for the LLM"
|
22
|
-
end
|
23
|
-
|
24
|
-
result :string,
|
25
|
-
description: "Name of the next command to run to make progress " \
|
26
|
-
"towards accomplishing the mission"
|
27
|
-
def association_depth
|
28
|
-
Foobara::JsonSchemaGenerator::AssociationDepth::ATOM
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|