foobara-agent 0.0.11 → 0.0.14
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 +15 -0
- data/src/foobara/agent/accomplish_goal.rb +91 -48
- data/src/foobara/agent/determine_base.rb +13 -27
- data/src/foobara/agent/determine_next_command_name_and_inputs.rb +42 -0
- data/src/foobara/agent/notify_user_that_current_goal_has_been_accomplished.rb +55 -18
- data/src/foobara/agent/types/command_log_entry.rb +11 -0
- data/src/foobara/agent.rb +70 -52
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc32aeaa66ec947920e085944f7948d22d97b4fa3b39d1a4b4fbf58755ca4321
|
4
|
+
data.tar.gz: 0dc7b3d02061bf0a615ab6299ea5f607fff1d703804d5368b57a9157d4edf64a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1a1d3780d7a3112cff93e7e626ec677ad558ce605e65c60f00ec3b8213b604e248ca4428311303b1902451f2bcc7d2a5f17f3725dde77bbb4725aeb10b9898d
|
7
|
+
data.tar.gz: f6113517024dc34e999fafcb3bc5ee0ec98fb63628f61725b5631e454d6edd4714933a745232e57f2a1c8f686f0d2544b481b1726cfff3e4dbbc47666bdd74c4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.0.14] - 2025-07-08
|
2
|
+
|
3
|
+
- Better handle serialization/pre-commit loading for various entity-depth scenarios
|
4
|
+
|
5
|
+
## [0.0.13] - 2025-07-06
|
6
|
+
|
7
|
+
- Eliminate NotifyUser... result_type to save tokens/improve accuracy
|
8
|
+
- Choose DescribeCommand for commands with inputs that haven't been described
|
9
|
+
- Log failure output when trying to determine commands/inputs
|
10
|
+
|
11
|
+
## [0.0.12] - 2025-07-05
|
12
|
+
|
13
|
+
- Choose DescribeCommand for commands that have inputs and haven't been described
|
14
|
+
- Log determine command/input failures when verbose
|
15
|
+
|
1
16
|
## [0.0.11] - 2025-07-01
|
2
17
|
|
3
18
|
- Fix some bugs with result type/value mismatches
|
@@ -19,7 +19,6 @@ module Foobara
|
|
19
19
|
io_out :duck
|
20
20
|
io_err :duck
|
21
21
|
agent Agent, :required
|
22
|
-
context Context, :required, "The current context of the agent"
|
23
22
|
maximum_command_calls :integer,
|
24
23
|
:allow_nil,
|
25
24
|
default: 25,
|
@@ -30,6 +29,9 @@ module Foobara
|
|
30
29
|
default: "claude-3-7-sonnet-20250219",
|
31
30
|
description: "The model to use for the LLM"
|
32
31
|
max_llm_calls_per_minute :integer, :allow_nil
|
32
|
+
user_association_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
|
33
|
+
result_entity_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
|
34
|
+
pass_aggregates_to_llm :boolean, :allow_nil
|
33
35
|
end
|
34
36
|
|
35
37
|
result do
|
@@ -40,8 +42,10 @@ module Foobara
|
|
40
42
|
depends_on ListCommands
|
41
43
|
|
42
44
|
def execute
|
43
|
-
|
44
|
-
|
45
|
+
unless list_commands_already_ran?
|
46
|
+
simulate_describe_list_commands_command
|
47
|
+
simulate_list_commands_run
|
48
|
+
end
|
45
49
|
# simulate_describe_command_run_for_all_commands
|
46
50
|
|
47
51
|
until mission_accomplished or given_up
|
@@ -51,9 +55,8 @@ module Foobara
|
|
51
55
|
throttle_llm_calls_if_necessary
|
52
56
|
|
53
57
|
determine_next_command_and_inputs
|
54
|
-
run_next_command
|
55
58
|
|
56
|
-
|
59
|
+
run_next_command
|
57
60
|
end
|
58
61
|
|
59
62
|
if given_up
|
@@ -68,6 +71,10 @@ module Foobara
|
|
68
71
|
:final_result, :final_message, :command_response, :delayed_command_name,
|
69
72
|
:command_calls
|
70
73
|
|
74
|
+
def list_commands_already_ran?
|
75
|
+
context.command_log.any? { |log_entry| log_entry.command_name =~ /\bListCommands\z/ }
|
76
|
+
end
|
77
|
+
|
71
78
|
def simulate_list_commands_run
|
72
79
|
self.next_command_name = ListCommands.full_command_name
|
73
80
|
self.next_command_raw_inputs = nil
|
@@ -75,7 +82,6 @@ module Foobara
|
|
75
82
|
fetch_next_command_class
|
76
83
|
|
77
84
|
run_next_command
|
78
|
-
log_last_command_outcome
|
79
85
|
end
|
80
86
|
|
81
87
|
def simulate_describe_list_commands_command
|
@@ -85,7 +91,6 @@ module Foobara
|
|
85
91
|
fetch_next_command_class
|
86
92
|
|
87
93
|
run_next_command
|
88
|
-
log_last_command_outcome
|
89
94
|
end
|
90
95
|
|
91
96
|
def simulate_describe_command(command_name = next_command_name)
|
@@ -100,7 +105,6 @@ module Foobara
|
|
100
105
|
fetch_next_command_class
|
101
106
|
|
102
107
|
run_next_command
|
103
|
-
log_last_command_outcome
|
104
108
|
|
105
109
|
self.next_command_name = old_next_command_name
|
106
110
|
self.next_command_inputs = old_next_command_inputs
|
@@ -114,7 +118,7 @@ module Foobara
|
|
114
118
|
return if context.command_log.size > 1
|
115
119
|
|
116
120
|
ListCommands.run!(command_connector: agent)[:user_provided_commands].each do |full_command_name|
|
117
|
-
next if
|
121
|
+
next if command_described?(full_command_name)
|
118
122
|
|
119
123
|
self.next_command_name = DescribeCommand.full_command_name
|
120
124
|
self.next_command_inputs = { command_name: full_command_name }
|
@@ -122,7 +126,6 @@ module Foobara
|
|
122
126
|
fetch_next_command_class
|
123
127
|
|
124
128
|
run_next_command
|
125
|
-
log_last_command_outcome
|
126
129
|
end
|
127
130
|
# :nocov:
|
128
131
|
end
|
@@ -145,11 +148,10 @@ module Foobara
|
|
145
148
|
|
146
149
|
compact_command_log
|
147
150
|
|
148
|
-
inputs_for_determine = {
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
151
|
+
inputs_for_determine = { agent:, llm_model: }
|
152
|
+
unless pass_aggregates_to_llm.nil?
|
153
|
+
inputs_for_determine[:pass_aggregates_to_llm] = pass_aggregates_to_llm
|
154
|
+
end
|
153
155
|
determine_command = DetermineNextCommandNameAndInputs.new(inputs_for_determine)
|
154
156
|
|
155
157
|
outcome = begin
|
@@ -171,10 +173,17 @@ module Foobara
|
|
171
173
|
if outcome.success?
|
172
174
|
fetch_next_command_class
|
173
175
|
|
176
|
+
if need_to_describe_next_command?
|
177
|
+
simulate_describe_command
|
178
|
+
return determine_next_command_and_inputs(retries, outcome)
|
179
|
+
end
|
180
|
+
|
174
181
|
if next_command_has_inputs?
|
175
182
|
outcome = validate_next_command_inputs
|
176
183
|
|
177
184
|
unless outcome.success?
|
185
|
+
# TODO: test this path
|
186
|
+
# :nocov:
|
178
187
|
log_command_outcome(
|
179
188
|
command_name: next_command_name,
|
180
189
|
inputs: next_command_inputs,
|
@@ -184,6 +193,7 @@ module Foobara
|
|
184
193
|
simulate_describe_command
|
185
194
|
|
186
195
|
determine_next_command_and_inputs(retries - 1, outcome)
|
196
|
+
# :nocov:
|
187
197
|
end
|
188
198
|
else
|
189
199
|
self.next_command_inputs = {}
|
@@ -260,23 +270,7 @@ module Foobara
|
|
260
270
|
end
|
261
271
|
|
262
272
|
def run_next_command
|
263
|
-
|
264
|
-
args = if next_command_inputs.nil? || next_command_inputs.empty?
|
265
|
-
""
|
266
|
-
else
|
267
|
-
s = next_command_inputs.to_s
|
268
|
-
|
269
|
-
if s =~ /\A\{(.*)}\z/
|
270
|
-
"(#{::Regexp.last_match(1)})"
|
271
|
-
else
|
272
|
-
# :nocov:
|
273
|
-
raise "Unexpected next_command_inputs: #{next_command_inputs}"
|
274
|
-
# :nocov:
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
(io_out || $stdout).puts "#{next_command_name}.run#{args}"
|
279
|
-
end
|
273
|
+
log_command_code(command_name: next_command_name, inputs: next_command_inputs)
|
280
274
|
|
281
275
|
self.command_response = agent.run(
|
282
276
|
full_command_name: next_command_name,
|
@@ -286,19 +280,7 @@ module Foobara
|
|
286
280
|
|
287
281
|
self.command_outcome = command_response.outcome
|
288
282
|
|
289
|
-
|
290
|
-
unless command_outcome.success?
|
291
|
-
# :nocov:
|
292
|
-
(io_err || $stderr).puts(
|
293
|
-
"Command #{command_response.command.class.full_command_name} failed #{command_outcome.errors_hash}"
|
294
|
-
)
|
295
|
-
# :nocov:
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
def log_last_command_outcome
|
301
|
-
log_command_outcome(command: command_response.command)
|
283
|
+
log_command_outcome(command: command_response.command, log_command_code: false)
|
302
284
|
end
|
303
285
|
|
304
286
|
def compact_command_log
|
@@ -337,13 +319,14 @@ module Foobara
|
|
337
319
|
next unless last_success
|
338
320
|
|
339
321
|
failure_indexes.each do |failure_index|
|
322
|
+
# TODO: test this path
|
323
|
+
# :nocov:
|
340
324
|
if failure_index < last_success
|
341
325
|
indexes_to_delete << failure_index
|
342
326
|
else
|
343
|
-
# :nocov:
|
344
327
|
break
|
345
|
-
# :nocov:
|
346
328
|
end
|
329
|
+
# :nocov:
|
347
330
|
end
|
348
331
|
end
|
349
332
|
|
@@ -377,7 +360,12 @@ module Foobara
|
|
377
360
|
end
|
378
361
|
end
|
379
362
|
|
380
|
-
def log_command_outcome(command: nil,
|
363
|
+
def log_command_outcome(command: nil,
|
364
|
+
command_name: nil,
|
365
|
+
inputs: nil,
|
366
|
+
outcome: nil,
|
367
|
+
result: nil,
|
368
|
+
log_command_code: true)
|
381
369
|
if command
|
382
370
|
command_name ||= command.class.full_command_name
|
383
371
|
inputs ||= command.raw_inputs
|
@@ -405,6 +393,43 @@ module Foobara
|
|
405
393
|
)
|
406
394
|
|
407
395
|
context.command_log << log_entry
|
396
|
+
|
397
|
+
if verbose?
|
398
|
+
if log_command_code
|
399
|
+
# TODO: test this code path hmmm
|
400
|
+
# :nocov:
|
401
|
+
self.log_command_code(command_name:, inputs:)
|
402
|
+
# :nocov:
|
403
|
+
end
|
404
|
+
|
405
|
+
unless log_entry.success?
|
406
|
+
# :nocov:
|
407
|
+
(io_err || $stderr).puts(
|
408
|
+
"Command #{log_entry.command_name} failed:\n#{log_entry.errors_hash}"
|
409
|
+
)
|
410
|
+
# :nocov:
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def log_command_code(command_name:, inputs:)
|
416
|
+
if verbose?
|
417
|
+
args = if next_command_inputs.nil? || next_command_inputs.empty?
|
418
|
+
""
|
419
|
+
else
|
420
|
+
s = next_command_inputs.to_s
|
421
|
+
|
422
|
+
if s =~ /\A\{(.*)}\z/
|
423
|
+
"(#{::Regexp.last_match(1)})"
|
424
|
+
else
|
425
|
+
# :nocov:
|
426
|
+
raise "Unexpected next_command_inputs: #{next_command_inputs}"
|
427
|
+
# :nocov:
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
(io_out || $stdout).puts "#{next_command_name}.run#{args}"
|
432
|
+
end
|
408
433
|
end
|
409
434
|
|
410
435
|
# TODO: these are awkwardly called from outside. Come up with a better solution.
|
@@ -442,6 +467,20 @@ module Foobara
|
|
442
467
|
verbose
|
443
468
|
end
|
444
469
|
|
470
|
+
def need_to_describe_next_command?
|
471
|
+
return false if command_described?(next_command_name)
|
472
|
+
return false if next_command_name == DescribeCommand.full_command_name
|
473
|
+
return true if next_command_has_inputs?
|
474
|
+
|
475
|
+
# check if inputs were unexpectedly given for a command that doesn't need them,
|
476
|
+
# in which case we should describe it
|
477
|
+
next_command_inputs && !next_command_inputs.empty?
|
478
|
+
end
|
479
|
+
|
480
|
+
def command_described?(command_name)
|
481
|
+
described_commands.include?(command_name)
|
482
|
+
end
|
483
|
+
|
445
484
|
def llm_call_timestamps
|
446
485
|
@llm_call_timestamps ||= []
|
447
486
|
end
|
@@ -487,6 +526,10 @@ module Foobara
|
|
487
526
|
sleep seconds
|
488
527
|
end
|
489
528
|
end
|
529
|
+
|
530
|
+
def context
|
531
|
+
agent.context
|
532
|
+
end
|
490
533
|
end
|
491
534
|
end
|
492
535
|
end
|
@@ -2,9 +2,12 @@ require "foobara/llm_backed_command"
|
|
2
2
|
|
3
3
|
module Foobara
|
4
4
|
class Agent < CommandConnector
|
5
|
+
# TODO: just move this back to DetermineNextCommandAndInputs since it's now the only base class
|
5
6
|
class DetermineBase < Foobara::LlmBackedCommand
|
6
7
|
inputs do
|
7
|
-
|
8
|
+
pass_aggregates_to_llm :boolean, :allow_nil, "Should we send aggregates to the LLM or " \
|
9
|
+
"require it to fetch what it needs?"
|
10
|
+
agent Agent, :required
|
8
11
|
llm_model :string,
|
9
12
|
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
10
13
|
default: "claude-3-7-sonnet-20250219",
|
@@ -12,7 +15,11 @@ module Foobara
|
|
12
15
|
end
|
13
16
|
|
14
17
|
def association_depth
|
15
|
-
|
18
|
+
if pass_aggregates_to_llm
|
19
|
+
Foobara::AssociationDepth::AGGREGATE
|
20
|
+
else
|
21
|
+
Foobara::AssociationDepth::ATOM
|
22
|
+
end
|
16
23
|
end
|
17
24
|
|
18
25
|
def build_messages
|
@@ -49,34 +56,13 @@ module Foobara
|
|
49
56
|
p
|
50
57
|
end
|
51
58
|
|
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
59
|
def goal
|
78
60
|
context.current_goal
|
79
61
|
end
|
62
|
+
|
63
|
+
def context
|
64
|
+
agent.context
|
65
|
+
end
|
80
66
|
end
|
81
67
|
end
|
82
68
|
end
|
@@ -3,6 +3,44 @@ require_relative "determine_base"
|
|
3
3
|
module Foobara
|
4
4
|
class Agent < CommandConnector
|
5
5
|
class DetermineNextCommandNameAndInputs < DetermineBase
|
6
|
+
class << self
|
7
|
+
def llm_instructions(assistant_association_depth, goal)
|
8
|
+
key = [assistant_association_depth, goal]
|
9
|
+
|
10
|
+
@llm_instructions_cache ||= {}
|
11
|
+
|
12
|
+
if @llm_instructions_cache.key?(key)
|
13
|
+
@llm_instructions_cache[key]
|
14
|
+
else
|
15
|
+
@llm_instructions_cache[key] = build_llm_instructions(assistant_association_depth, goal)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_llm_instructions(assistant_association_depth, goal)
|
20
|
+
instructions = "You are the implementation of a command called #{scoped_full_name}"
|
21
|
+
|
22
|
+
instructions += if description && !description.empty?
|
23
|
+
" which has the following description:\n\n#{description}\n\n"
|
24
|
+
else
|
25
|
+
# :nocov:
|
26
|
+
". "
|
27
|
+
# :nocov:
|
28
|
+
end
|
29
|
+
|
30
|
+
result_schema = result_json_schema(assistant_association_depth)
|
31
|
+
|
32
|
+
instructions += "You are working towards accomplishing the following goal:\n\n#{goal}\n\n"
|
33
|
+
instructions += "Your response of which command to run next should match the following JSON schema:"
|
34
|
+
instructions += "\n\n#{result_schema}\n\n"
|
35
|
+
instructions += "You can get more details about the inputs and result schemas for a specific command by " \
|
36
|
+
"choosing the DescribeCommand command. " \
|
37
|
+
"You will reply with nothing more than the JSON you've generated so that the calling code " \
|
38
|
+
"can successfully parse your answer."
|
39
|
+
|
40
|
+
instructions
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
6
44
|
description "Returns the name of the next command to run and its inputs given the progress " \
|
7
45
|
"towards accomplishing the current goal. " \
|
8
46
|
"If the goal has been accomplished it will choose the " \
|
@@ -12,6 +50,10 @@ module Foobara
|
|
12
50
|
command :string, :required
|
13
51
|
inputs :attributes, :allow_nil
|
14
52
|
end
|
53
|
+
|
54
|
+
def determine_llm_instructions
|
55
|
+
self.llm_instructions = self.class.llm_instructions(computed_user_association_depth, goal)
|
56
|
+
end
|
15
57
|
end
|
16
58
|
end
|
17
59
|
end
|
@@ -1,15 +1,18 @@
|
|
1
1
|
module Foobara
|
2
2
|
class Agent < CommandConnector
|
3
|
+
# NOTE: we intentionally don't use a result type here
|
4
|
+
# to reduce tokens the LLM has to deal with when this command is described
|
3
5
|
class NotifyUserThatCurrentGoalHasBeenAccomplished < Foobara::Command
|
4
6
|
extend Concerns::SubclassCacheable
|
5
7
|
|
6
8
|
class << self
|
7
|
-
attr_accessor :command_class, :returns_message_to_user, :returns_result_data, :result_is_attributes
|
9
|
+
attr_accessor :command_class, :returns_message_to_user, :returns_result_data, :result_is_attributes,
|
10
|
+
:built_result_type
|
8
11
|
|
9
|
-
def for(agent_id: nil, result_type: nil, include_message_to_user_in_result: true)
|
12
|
+
def for(agent_id: nil, result_type: nil, include_message_to_user_in_result: true, result_entity_depth: nil)
|
10
13
|
agent_id ||= "Anon#{SecureRandom.hex(2)}"
|
11
14
|
|
12
|
-
cached_subclass([
|
15
|
+
cached_subclass([agent_id]) do
|
13
16
|
command_name = "Foobara::Agent::#{agent_id}::NotifyUserThatCurrentGoalHasBeenAccomplished"
|
14
17
|
klass = Util.make_class_p(command_name, self)
|
15
18
|
|
@@ -20,39 +23,40 @@ module Foobara
|
|
20
23
|
|
21
24
|
if result_type
|
22
25
|
klass.returns_result_data = true
|
26
|
+
unless result_type.is_a?(Types::Type)
|
27
|
+
result_type = Domain.current.foobara_type_from_declaration(result_type)
|
28
|
+
end
|
29
|
+
|
30
|
+
domain = result_type.created_in_namespace.foobara_domain
|
23
31
|
|
24
32
|
# TODO: fix this... agent backed command sets these via its own result type.
|
25
33
|
# check if message_to_user is already here and also search/fix result_data to be result for consistency.
|
26
34
|
if include_message_to_user_in_result
|
27
35
|
klass.returns_message_to_user = true
|
28
36
|
|
29
|
-
klass.
|
37
|
+
klass.built_result_type = domain.foobara_type_from_declaration do
|
30
38
|
result result_type, :required
|
31
39
|
message_to_user :string, :required, "Message to the user about what was done"
|
32
40
|
end
|
33
41
|
|
34
|
-
klass.add_inputs klass.
|
42
|
+
klass.add_inputs klass.built_result_type
|
35
43
|
|
36
44
|
klass.description "Notifies the user that the current goal has been accomplished and returns a final " \
|
37
45
|
"result formatted according to the " \
|
38
46
|
"result schema and a message to the user. " \
|
39
47
|
"The user might issue a new goal."
|
40
48
|
else
|
41
|
-
unless result_type.is_a?(Types::Type)
|
42
|
-
result_type = Domain.current.foobara_type_from_declaration(result_type)
|
43
|
-
end
|
44
|
-
|
45
49
|
if result_type.extends?(BuiltinTypes[:attributes])
|
50
|
+
klass.built_result_type = result_type
|
46
51
|
klass.result_is_attributes = true
|
47
52
|
klass.add_inputs result_type
|
48
53
|
else
|
49
|
-
klass.
|
54
|
+
klass.built_result_type = domain.foobara_type_from_declaration do
|
50
55
|
result result_type, :required
|
51
56
|
end
|
57
|
+
klass.add_inputs klass.built_result_type
|
52
58
|
end
|
53
59
|
|
54
|
-
klass.result result_type
|
55
|
-
|
56
60
|
klass.description "Notifies the user that the current goal has been accomplished and returns a final " \
|
57
61
|
"result formatted according to the " \
|
58
62
|
"result schema. " \
|
@@ -65,10 +69,6 @@ module Foobara
|
|
65
69
|
message_to_user :string, :required, "Message to the user about what was done"
|
66
70
|
end
|
67
71
|
|
68
|
-
klass.result do
|
69
|
-
message_to_user :string, :required, "Message to the user about what was done"
|
70
|
-
end
|
71
|
-
|
72
72
|
klass.description "Notifies the user that the current goal has been accomplished and results in a " \
|
73
73
|
"message to the user. " \
|
74
74
|
"The user might issue a new goal."
|
@@ -80,9 +80,46 @@ module Foobara
|
|
80
80
|
# :nocov:
|
81
81
|
end
|
82
82
|
|
83
|
+
apply_result_data_transformer(klass, result_entity_depth)
|
84
|
+
|
83
85
|
klass
|
84
86
|
end
|
85
87
|
end
|
88
|
+
|
89
|
+
def result_data_transformer(result_entity_depth)
|
90
|
+
if result_entity_depth
|
91
|
+
case result_entity_depth
|
92
|
+
when AssociationDepth::AGGREGATE
|
93
|
+
CommandConnectors::Transformers::LoadAggregatesTransformer
|
94
|
+
when AssociationDepth::ATOM
|
95
|
+
CommandConnectors::Transformers::LoadAtomsTransformer
|
96
|
+
else
|
97
|
+
# :nocov:
|
98
|
+
raise "Unsupported result entity depth: #{result_entity_depth}"
|
99
|
+
# :nocov:
|
100
|
+
end&.instance
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def apply_result_data_transformer(command_klass, result_entity_depth)
|
105
|
+
transformer = result_data_transformer(result_entity_depth)
|
106
|
+
return unless transformer
|
107
|
+
|
108
|
+
inputs_type = command_klass.inputs_type
|
109
|
+
return unless inputs_type
|
110
|
+
|
111
|
+
if inputs_type.extends?(BuiltinTypes[:detached_entity]) ||
|
112
|
+
DetachedEntity.contains_associations?(inputs_type)
|
113
|
+
command_klass.before_commit_transaction do |command:, **|
|
114
|
+
# TODO: why can't we just pass in the command??
|
115
|
+
built_result = command.built_result
|
116
|
+
|
117
|
+
if built_result
|
118
|
+
transformer.process_value!(built_result)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
86
123
|
end
|
87
124
|
|
88
125
|
description "Ends the session giving a final result formatted according to the " \
|
@@ -96,7 +133,7 @@ module Foobara
|
|
96
133
|
build_result
|
97
134
|
mark_mission_accomplished
|
98
135
|
|
99
|
-
|
136
|
+
nil
|
100
137
|
end
|
101
138
|
|
102
139
|
attr_accessor :built_result
|
@@ -116,7 +153,7 @@ module Foobara
|
|
116
153
|
inputs.slice(:result, :message_to_user)
|
117
154
|
elsif returns_result_data?
|
118
155
|
if result_is_attributes?
|
119
|
-
inputs.slice(*self.class.
|
156
|
+
inputs.slice(*self.class.built_result_type.element_types.keys)
|
120
157
|
else
|
121
158
|
inputs[:result]
|
122
159
|
end
|
@@ -11,6 +11,17 @@ module Foobara
|
|
11
11
|
errors_hash :duck, "Errors that occurred during the command"
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
def success?
|
16
|
+
outcome[:success]
|
17
|
+
end
|
18
|
+
|
19
|
+
def errors_hash
|
20
|
+
# TODO: test this path
|
21
|
+
# :nocov:
|
22
|
+
outcome[:errors_hash]
|
23
|
+
# :nocov:
|
24
|
+
end
|
14
25
|
end
|
15
26
|
end
|
16
27
|
end
|
data/src/foobara/agent.rb
CHANGED
@@ -23,7 +23,9 @@ module Foobara
|
|
23
23
|
:verbose,
|
24
24
|
:io_out,
|
25
25
|
:io_err,
|
26
|
-
:max_llm_calls_per_minute
|
26
|
+
:max_llm_calls_per_minute,
|
27
|
+
:pass_aggregates_to_llm,
|
28
|
+
:result_entity_depth
|
27
29
|
|
28
30
|
def initialize(
|
29
31
|
context: nil,
|
@@ -36,6 +38,8 @@ module Foobara
|
|
36
38
|
io_out: nil,
|
37
39
|
io_err: nil,
|
38
40
|
max_llm_calls_per_minute: nil,
|
41
|
+
result_entity_depth: AssociationDepth::AGGREGATE,
|
42
|
+
pass_aggregates_to_llm: nil,
|
39
43
|
**opts
|
40
44
|
)
|
41
45
|
# TODO: shouldn't have to pass command_log here since it has a default, debug that
|
@@ -48,25 +52,22 @@ module Foobara
|
|
48
52
|
self.io_out = io_out
|
49
53
|
self.io_err = io_err
|
50
54
|
self.max_llm_calls_per_minute = max_llm_calls_per_minute
|
55
|
+
self.result_entity_depth = result_entity_depth
|
56
|
+
self.pass_aggregates_to_llm = pass_aggregates_to_llm
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# end
|
58
|
+
pre_commit_transformer = if pass_aggregates_to_llm
|
59
|
+
CommandConnectors::Transformers::LoadAggregatesPreCommitTransformer
|
60
|
+
else
|
61
|
+
CommandConnectors::Transformers::LoadAtomsPreCommitTransformer
|
62
|
+
end
|
58
63
|
|
59
|
-
|
60
|
-
opts
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
+
opts = opts.merge(default_pre_commit_transformers: [
|
65
|
+
*opts[:default_pre_commit_transformers],
|
66
|
+
pre_commit_transformer
|
67
|
+
].uniq)
|
64
68
|
|
65
69
|
super(**opts)
|
66
70
|
|
67
|
-
# TODO: this should work now, switch to this approach
|
68
|
-
# add_default_inputs_transformer EntityToPrimaryKeyInputsTransformer
|
69
|
-
|
70
71
|
# TODO: push this convenience method up into base class?
|
71
72
|
command_classes&.each do |command_class|
|
72
73
|
connect(command_class)
|
@@ -140,42 +141,7 @@ module Foobara
|
|
140
141
|
state_machine.perform_transition!(:accomplish_goal)
|
141
142
|
|
142
143
|
begin
|
143
|
-
inputs =
|
144
|
-
goal:,
|
145
|
-
final_result_type: self.result_type,
|
146
|
-
context:,
|
147
|
-
agent: self
|
148
|
-
}
|
149
|
-
|
150
|
-
llm_model ||= self.llm_model
|
151
|
-
|
152
|
-
if llm_model
|
153
|
-
inputs[:llm_model] = llm_model
|
154
|
-
end
|
155
|
-
|
156
|
-
unless maximum_call_count.nil?
|
157
|
-
inputs[:maximum_command_calls] = maximum_call_count
|
158
|
-
end
|
159
|
-
|
160
|
-
if verbose
|
161
|
-
inputs[:verbose] = verbose
|
162
|
-
end
|
163
|
-
|
164
|
-
if io_out
|
165
|
-
inputs[:io_out] = io_out
|
166
|
-
end
|
167
|
-
|
168
|
-
if io_err
|
169
|
-
inputs[:io_err] = io_err
|
170
|
-
end
|
171
|
-
|
172
|
-
if include_message_to_user_in_result || include_message_to_user_in_result == false
|
173
|
-
inputs[:include_message_to_user_in_result] = include_message_to_user_in_result
|
174
|
-
end
|
175
|
-
|
176
|
-
if max_llm_calls_per_minute && max_llm_calls_per_minute > 0
|
177
|
-
inputs[:max_llm_calls_per_minute] = max_llm_calls_per_minute
|
178
|
-
end
|
144
|
+
inputs = accomplish_goal_inputs(goal, result_type:, maximum_call_count:, llm_model:)
|
179
145
|
|
180
146
|
self.current_accomplish_goal_command = AccomplishGoal.new(inputs)
|
181
147
|
|
@@ -196,6 +162,57 @@ module Foobara
|
|
196
162
|
end
|
197
163
|
end
|
198
164
|
|
165
|
+
def accomplish_goal_inputs(goal,
|
166
|
+
result_type: nil,
|
167
|
+
maximum_call_count: nil,
|
168
|
+
llm_model: nil)
|
169
|
+
inputs = {
|
170
|
+
goal:,
|
171
|
+
final_result_type: self.result_type,
|
172
|
+
agent: self
|
173
|
+
}
|
174
|
+
|
175
|
+
llm_model ||= self.llm_model
|
176
|
+
|
177
|
+
if llm_model
|
178
|
+
inputs[:llm_model] = llm_model
|
179
|
+
end
|
180
|
+
|
181
|
+
unless maximum_call_count.nil?
|
182
|
+
inputs[:maximum_command_calls] = maximum_call_count
|
183
|
+
end
|
184
|
+
|
185
|
+
if verbose
|
186
|
+
inputs[:verbose] = verbose
|
187
|
+
end
|
188
|
+
|
189
|
+
if io_out
|
190
|
+
inputs[:io_out] = io_out
|
191
|
+
end
|
192
|
+
|
193
|
+
if io_err
|
194
|
+
inputs[:io_err] = io_err
|
195
|
+
end
|
196
|
+
|
197
|
+
if include_message_to_user_in_result || include_message_to_user_in_result == false
|
198
|
+
inputs[:include_message_to_user_in_result] = include_message_to_user_in_result
|
199
|
+
end
|
200
|
+
|
201
|
+
if max_llm_calls_per_minute && max_llm_calls_per_minute > 0
|
202
|
+
inputs[:max_llm_calls_per_minute] = max_llm_calls_per_minute
|
203
|
+
end
|
204
|
+
|
205
|
+
if result_entity_depth
|
206
|
+
inputs[:result_entity_depth] = result_entity_depth
|
207
|
+
end
|
208
|
+
|
209
|
+
unless pass_aggregates_to_llm.nil?
|
210
|
+
inputs[:pass_aggregates_to_llm] = pass_aggregates_to_llm
|
211
|
+
end
|
212
|
+
|
213
|
+
inputs
|
214
|
+
end
|
215
|
+
|
199
216
|
def set_context_goal(goal)
|
200
217
|
if context
|
201
218
|
context.set_new_goal(goal)
|
@@ -232,7 +249,8 @@ module Foobara
|
|
232
249
|
result_type:,
|
233
250
|
agent_id: agent_name,
|
234
251
|
# TODO: Support changing this flag when the goal changes
|
235
|
-
include_message_to_user_in_result
|
252
|
+
include_message_to_user_in_result:,
|
253
|
+
result_entity_depth:
|
236
254
|
)
|
237
255
|
else
|
238
256
|
NotifyUserThatCurrentGoalHasBeenAccomplished
|
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.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -29,14 +29,14 @@ dependencies:
|
|
29
29
|
requirements:
|
30
30
|
- - "~>"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 0.0
|
32
|
+
version: 1.0.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0.0
|
39
|
+
version: 1.0.0
|
40
40
|
email:
|
41
41
|
- azimux@gmail.com
|
42
42
|
executables: []
|