foobara-agent 0.0.16 → 0.0.18

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: 240c7fa690a890cd583288e9ed1ae2fb7fe5c70a5322778eccf29ac4bc6554f9
4
- data.tar.gz: 1d41b1f61ef8126fda960b91231558cf3911fc91a806e005d6c1d36db17d16d6
3
+ metadata.gz: ff591852377b5821eabf7ff4b49c4cc4bcacbebfdceaed539af14b831aa9cea3
4
+ data.tar.gz: 35de585c33d57e7f52f91baf95efaccd685e42fb342a0ac407875c8eb1790dad
5
5
  SHA512:
6
- metadata.gz: c02671a8ad44659aa2c5aed14bf57f71d3bf14252757302d040e4e2b6060b89c3cbccbc54e35e647cd2c5433a03ef15b12c7dcb97c602fcf4d5edb153d7b2c01
7
- data.tar.gz: 7b250f7059fb4fda74fff80c4b7c886219abe44b234b48e41a54b954df20a7f549b161cada201b9215d259f17ace85c2ad174f3c95631f45c1ce7c6e965bffa1
6
+ metadata.gz: 3ba055dccfe9e32a9ec6a9397a47707de6804d43c1e0e99a23f798951f246ea158b08048b939b51278cf0f552d16268cd83fe22a46bc2915f3434de693a1040e
7
+ data.tar.gz: '091f144c40b376ee7431c9ba4e906258f6d68704ebee1b568383bb66f7708cb40abeb4fab0650aeafd9bc2a9bb2b69f0428014566783f966d2545e07cfd0eb6e'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.0.18] - 2025-07-21
2
+
3
+ - Add maximum_command_calls option to Agent#initialize
4
+
5
+ ## [0.0.17] - 2025-07-10
6
+
7
+ - Prefix verbose output with the agent name if there is one
8
+
1
9
  ## [0.0.16] - 2025-07-09
2
10
 
3
11
  - Tell the llm about previous goals
data/lib/foobara/agent.rb CHANGED
@@ -10,12 +10,8 @@ module Foobara
10
10
 
11
11
  class << self
12
12
  def reset_all
13
- [
14
- NotifyUserThatCurrentGoalHasBeenAccomplished
15
- ].each do |command_class|
16
- command_class.clear_subclass_cache
17
- Util.descendants(command_class).each(&:clear_subclass_cache)
18
- end
13
+ NotifyUserThatCurrentGoalHasBeenAccomplished.clear_subclass_cache
14
+ Util.descendants(NotifyUserThatCurrentGoalHasBeenAccomplished).each(&:clear_subclass_cache)
19
15
  end
20
16
  end
21
17
  end
@@ -3,9 +3,6 @@ require_relative "list_commands"
3
3
  module Foobara
4
4
  class Agent < CommandConnector
5
5
  class AccomplishGoal < Foobara::Command
6
- # Using a const here so we can stub it in the test suite to speed things up
7
- SECONDS_PER_MINUTE = 60
8
-
9
6
  possible_error :gave_up, context: { reason: :string }, message: "Gave up."
10
7
  possible_error :too_many_command_calls,
11
8
  context: { maximum_command_calls: :integer }
@@ -28,7 +25,6 @@ module Foobara
28
25
  one_of: Ai::AnswerBot::Types::ModelEnum,
29
26
  default: Ai.default_llm_model,
30
27
  description: "The model to use for the LLM"
31
- max_llm_calls_per_minute :integer, :allow_nil
32
28
  user_association_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
33
29
  result_entity_depth :symbol, :allow_nil, one_of: Foobara::AssociationDepth
34
30
  pass_aggregates_to_llm :boolean, :allow_nil
@@ -52,8 +48,6 @@ module Foobara
52
48
  increment_command_calls
53
49
  check_if_too_many_calls
54
50
 
55
- throttle_llm_calls_if_necessary
56
-
57
51
  determine_next_command_and_inputs
58
52
 
59
53
  run_next_command
@@ -430,7 +424,10 @@ module Foobara
430
424
  end
431
425
  end
432
426
 
433
- (io_out || $stdout).puts "#{next_command_name}.run#{args}"
427
+ agent_name = agent.agent_name
428
+ prefix = agent_name && !agent_name.empty? ? "#{agent_name}: " : ""
429
+
430
+ (io_out || $stdout).puts "#{prefix}#{next_command_name}.run#{args}"
434
431
  end
435
432
  end
436
433
 
@@ -497,42 +494,6 @@ module Foobara
497
494
  llm_call_timestamps.unshift(Time.now)
498
495
  end
499
496
 
500
- def llm_calls_in_last_minute
501
- llm_call_timestamps.select { |t| t > (Time.now - 60) }
502
- end
503
-
504
- def llm_call_count_in_last_minute
505
- llm_calls_in_last_minute.size
506
- end
507
-
508
- def time_until_llm_call_count_in_last_minute_changes
509
- calls = llm_calls_in_last_minute
510
-
511
- first_to_expire = calls.first
512
-
513
- if first_to_expire
514
- [0, (first_to_expire + SECONDS_PER_MINUTE) - Time.now].max
515
- else
516
- # TODO: figure out how to test this code path
517
- # :nocov:
518
- 0
519
- # :nocov:
520
- end
521
- end
522
-
523
- def throttle_llm_calls_if_necessary
524
- return unless max_llm_calls_per_minute && max_llm_calls_per_minute > 0
525
-
526
- if llm_call_count_in_last_minute >= max_llm_calls_per_minute
527
- seconds = time_until_llm_call_count_in_last_minute_changes
528
- if verbose?
529
- (io_out || $stdout).puts "Sleeping for #{seconds} seconds to avoid LLM calls per minute limit"
530
- end
531
-
532
- sleep seconds
533
- end
534
- end
535
-
536
497
  def context
537
498
  agent.context
538
499
  end
@@ -1,8 +1,8 @@
1
- require_relative "determine_base"
1
+ require "foobara/llm_backed_command"
2
2
 
3
3
  module Foobara
4
4
  class Agent < CommandConnector
5
- class DetermineNextCommandNameAndInputs < DetermineBase
5
+ class DetermineNextCommandNameAndInputs < Foobara::LlmBackedCommand
6
6
  class << self
7
7
  def llm_instructions(assistant_association_depth, goal, previous_goals = nil)
8
8
  key = [assistant_association_depth, goal, previous_goals]
@@ -61,6 +61,16 @@ module Foobara
61
61
  inputs :attributes, :allow_nil
62
62
  end
63
63
 
64
+ inputs do
65
+ agent Agent, :required
66
+ pass_aggregates_to_llm :boolean, :allow_nil, "Should we send aggregates to the LLM or " \
67
+ "require it to fetch what it needs?"
68
+ llm_model :string,
69
+ one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
70
+ default: Ai.default_llm_model,
71
+ description: "The model to use for the LLM"
72
+ end
73
+
64
74
  def determine_llm_instructions
65
75
  self.llm_instructions = self.class.llm_instructions(
66
76
  computed_user_association_depth,
@@ -68,6 +78,64 @@ module Foobara
68
78
  previous_goal_and_status_pairs
69
79
  )
70
80
  end
81
+
82
+ def association_depth
83
+ if pass_aggregates_to_llm
84
+ Foobara::AssociationDepth::AGGREGATE
85
+ else
86
+ Foobara::AssociationDepth::ATOM
87
+ end
88
+ end
89
+
90
+ def build_messages
91
+ p = [
92
+ {
93
+ content: llm_instructions,
94
+ role: :system
95
+ }
96
+ ]
97
+
98
+ context.command_log.each do |command_log_entry|
99
+ agent_entry = {
100
+ command: command_log_entry.command_name
101
+ }
102
+
103
+ inputs = command_log_entry.inputs
104
+
105
+ if inputs && !inputs.empty?
106
+ agent_entry[:inputs] = inputs
107
+ end
108
+
109
+ outcome_entry = command_log_entry.outcome
110
+
111
+ p << {
112
+ content: agent_entry,
113
+ role: :assistant
114
+ }
115
+ p << {
116
+ content: outcome_entry,
117
+ role: :user
118
+ }
119
+ end
120
+
121
+ p
122
+ end
123
+
124
+ def goal
125
+ context.current_goal.text
126
+ end
127
+
128
+ def previous_goal_and_status_pairs
129
+ if context.previous_goals && !context.previous_goals.empty?
130
+ context.previous_goals.map do |previous_goal|
131
+ [previous_goal.text, previous_goal.state]
132
+ end
133
+ end
134
+ end
135
+
136
+ def context
137
+ agent.context
138
+ end
71
139
  end
72
140
  end
73
141
  end
data/src/foobara/agent.rb CHANGED
@@ -24,9 +24,9 @@ module Foobara
24
24
  :verbose,
25
25
  :io_out,
26
26
  :io_err,
27
- :max_llm_calls_per_minute,
28
27
  :pass_aggregates_to_llm,
29
- :result_entity_depth
28
+ :result_entity_depth,
29
+ :maximum_command_calls
30
30
 
31
31
  def initialize(
32
32
  context: nil,
@@ -38,9 +38,9 @@ module Foobara
38
38
  verbose: false,
39
39
  io_out: nil,
40
40
  io_err: nil,
41
- max_llm_calls_per_minute: nil,
42
41
  result_entity_depth: AssociationDepth::AGGREGATE,
43
42
  pass_aggregates_to_llm: nil,
43
+ maximum_command_calls: nil,
44
44
  **opts
45
45
  )
46
46
  # TODO: shouldn't have to pass command_log here since it has a default, debug that
@@ -55,9 +55,9 @@ module Foobara
55
55
  self.verbose = verbose
56
56
  self.io_out = io_out
57
57
  self.io_err = io_err
58
- self.max_llm_calls_per_minute = max_llm_calls_per_minute
59
58
  self.result_entity_depth = result_entity_depth
60
59
  self.pass_aggregates_to_llm = pass_aggregates_to_llm
60
+ self.maximum_command_calls = maximum_command_calls
61
61
 
62
62
  pre_commit_transformer = if pass_aggregates_to_llm
63
63
  CommandConnectors::Transformers::LoadAggregatesPreCommitTransformer
@@ -124,7 +124,7 @@ module Foobara
124
124
  def accomplish_goal(
125
125
  goal,
126
126
  result_type: nil,
127
- maximum_call_count: nil,
127
+ maximum_command_calls: nil,
128
128
  llm_model: nil
129
129
  )
130
130
  set_context_goal(goal)
@@ -150,7 +150,7 @@ module Foobara
150
150
  state_machine.perform_transition!(:accomplish_goal)
151
151
 
152
152
  begin
153
- inputs = accomplish_goal_inputs(goal, result_type:, maximum_call_count:, llm_model:)
153
+ inputs = accomplish_goal_inputs(goal, result_type:, maximum_command_calls:, llm_model:)
154
154
 
155
155
  self.current_accomplish_goal_command = AccomplishGoal.new(inputs)
156
156
 
@@ -186,7 +186,7 @@ module Foobara
186
186
 
187
187
  def accomplish_goal_inputs(goal,
188
188
  result_type: nil,
189
- maximum_call_count: nil,
189
+ maximum_command_calls: nil,
190
190
  llm_model: nil)
191
191
  inputs = {
192
192
  goal:,
@@ -200,8 +200,12 @@ module Foobara
200
200
  inputs[:llm_model] = llm_model
201
201
  end
202
202
 
203
- unless maximum_call_count.nil?
204
- inputs[:maximum_command_calls] = maximum_call_count
203
+ if maximum_command_calls.nil?
204
+ maximum_command_calls = self.maximum_command_calls
205
+ end
206
+
207
+ unless maximum_command_calls.nil?
208
+ inputs[:maximum_command_calls] = maximum_command_calls
205
209
  end
206
210
 
207
211
  if verbose
@@ -220,10 +224,6 @@ module Foobara
220
224
  inputs[:include_message_to_user_in_result] = include_message_to_user_in_result
221
225
  end
222
226
 
223
- if max_llm_calls_per_minute && max_llm_calls_per_minute > 0
224
- inputs[:max_llm_calls_per_minute] = max_llm_calls_per_minute
225
- end
226
-
227
227
  if result_entity_depth
228
228
  inputs[:result_entity_depth] = result_entity_depth
229
229
  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.16
4
+ version: 0.0.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -66,7 +66,6 @@ files:
66
66
  - src/foobara/agent/connector/set_command_connector_inputs_transformer.rb
67
67
  - src/foobara/agent/describe_command.rb
68
68
  - src/foobara/agent/describe_type.rb
69
- - src/foobara/agent/determine_base.rb
70
69
  - src/foobara/agent/determine_next_command_name_and_inputs.rb
71
70
  - src/foobara/agent/give_up.rb
72
71
  - src/foobara/agent/list_commands.rb
@@ -1,76 +0,0 @@
1
- require "foobara/llm_backed_command"
2
-
3
- module Foobara
4
- class Agent < CommandConnector
5
- # TODO: just move this back to DetermineNextCommandAndInputs since it's now the only base class
6
- class DetermineBase < Foobara::LlmBackedCommand
7
- inputs do
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
11
- llm_model :string,
12
- one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
13
- default: Ai.default_llm_model,
14
- description: "The model to use for the LLM"
15
- end
16
-
17
- def association_depth
18
- if pass_aggregates_to_llm
19
- Foobara::AssociationDepth::AGGREGATE
20
- else
21
- Foobara::AssociationDepth::ATOM
22
- end
23
- end
24
-
25
- def build_messages
26
- p = [
27
- {
28
- content: llm_instructions,
29
- role: :system
30
- }
31
- ]
32
-
33
- context.command_log.each do |command_log_entry|
34
- agent_entry = {
35
- command: command_log_entry.command_name
36
- }
37
-
38
- inputs = command_log_entry.inputs
39
-
40
- if inputs && !inputs.empty?
41
- agent_entry[:inputs] = inputs
42
- end
43
-
44
- outcome_entry = command_log_entry.outcome
45
-
46
- p << {
47
- content: agent_entry,
48
- role: :assistant
49
- }
50
- p << {
51
- content: outcome_entry,
52
- role: :user
53
- }
54
- end
55
-
56
- p
57
- end
58
-
59
- def goal
60
- context.current_goal.text
61
- end
62
-
63
- def previous_goal_and_status_pairs
64
- if context.previous_goals && !context.previous_goals.empty?
65
- context.previous_goals.map do |previous_goal|
66
- [previous_goal.text, previous_goal.state]
67
- end
68
- end
69
- end
70
-
71
- def context
72
- agent.context
73
- end
74
- end
75
- end
76
- end