Class: Foobara::Agent

Inherits:
CommandConnector show all
Defined in:
foobara-agent-0.0.21/src/foobara/agent.rb,
foobara-agent-0.0.21/src/foobara/agent/give_up.rb,
foobara-agent-0.0.21/src/foobara/agent/types/goal.rb,
foobara-agent-0.0.21/src/foobara/agent/describe_type.rb,
foobara-agent-0.0.21/src/foobara/agent/list_commands.rb,
foobara-agent-0.0.21/src/foobara/agent/types/context.rb,
foobara-agent-0.0.21/src/foobara/agent/accomplish_goal.rb,
foobara-agent-0.0.21/src/foobara/agent/describe_command.rb,
foobara-agent-0.0.21/src/foobara/agent/types/command_log_entry.rb,
foobara-agent-0.0.21/src/foobara/agent/concerns/subclass_cacheable.rb,
foobara-agent-0.0.21/src/foobara/agent/determine_next_command_name_and_inputs.rb,
foobara-agent-0.0.21/src/foobara/agent/connector/set_command_connector_inputs_transformer.rb,
foobara-agent-0.0.21/src/foobara/agent/notify_user_that_current_goal_has_been_accomplished.rb,
foobara-agent-0.0.21/lib/foobara/agent.rb

Defined Under Namespace

Modules: Concerns Classes: AccomplishGoal, CommandLogEntry, Context, DescribeCommand, DescribeType, DetermineNextCommandNameAndInputs, GiveUp, Goal, ListCommands, NotifyUserThatCurrentGoalHasBeenAccomplished, SetCommandConnectorInputsTransformer

Constant Summary collapse

StateMachine =
Foobara::StateMachine.for(
  [:initialized, :idle, :error, :failure] => {
    kill: :killed,
    accomplish_goal: :accomplishing_goal
  },
  accomplishing_goal: {
    goal_accomplished: :idle,
    goal_errored: :error,
    goal_failed: :failure,
    kill: :killed
  }
)

Instance Attribute Summary collapse

Attributes inherited from CommandConnector

#authenticator, #capture_unknown_error, #command_registry

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from CommandConnector

#all_exposed_commands, #all_exposed_type_names, allowed_rules_to_register, authenticator_registry, #build_command_instance, #build_request, #build_response, #connect_delayed, #delayed_connections, #desugarize_connect_args, #determine_command_class, find_builtin_command_class, #find_builtin_command_class, #foobara_manifest, #foobara_manifest_in_current_namespace, #lookup_command, #mutate_response, #normalize_manifest, #patch_up_broken_parents_for_errors_with_missing_command_parents, #process_delayed_connections, register_allowed_rule, register_authenticator, #request_to_command_class, #request_to_command_inputs, #request_to_command_instance, #request_to_response, #run_command, #run_request, #serialize_response_body, #set_response_body, #set_response_status, to_authenticator, #transform_command_class, #type_from_name

Constructor Details

#initialize(context: nil, agent_name: nil, command_classes: nil, llm_model: nil, result_type: nil, include_message_to_user_in_result: true, verbose: false, io_out: nil, io_err: nil, result_entity_depth: AssociationDepth::AGGREGATE, pass_aggregates_to_llm: nil, maximum_command_calls: nil, **opts) ⇒ Agent

Returns a new instance of Agent.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 31

def initialize(
  context: nil,
  agent_name: nil,
  command_classes: nil,
  llm_model: nil,
  result_type: nil,
  include_message_to_user_in_result: true,
  verbose: false,
  io_out: nil,
  io_err: nil,
  result_entity_depth: AssociationDepth::AGGREGATE,
  pass_aggregates_to_llm: nil,
  maximum_command_calls: nil,
  **opts
)
  # TODO: shouldn't have to pass command_log here since it has a default, debug that
  self.context = context || Context.new(command_log: [])

  if agent_name
    self.agent_name = agent_name
  end
  self.agent_id = agent_name || "Anon#{SecureRandom.hex(2)}"
  self.llm_model = llm_model || Ai.default_llm_model
  self.result_type = result_type
  self.include_message_to_user_in_result = include_message_to_user_in_result
  self.verbose = verbose
  self.io_out = io_out
  self.io_err = io_err
  self.result_entity_depth = result_entity_depth
  self.pass_aggregates_to_llm = pass_aggregates_to_llm
  self.maximum_command_calls = maximum_command_calls

  pre_commit_transformer = if pass_aggregates_to_llm
                             CommandConnectors::Transformers::LoadAggregatesPreCommitTransformer
                           else
                             CommandConnectors::Transformers::LoadAtomsPreCommitTransformer
                           end

  opts = opts.merge(default_pre_commit_transformers: [
    *opts[:default_pre_commit_transformers],
    pre_commit_transformer
  ].uniq)

  super(**opts)

  # TODO: push this convenience method up into base class?
  command_classes&.each do |command_class|
    connect(command_class)
  end
end

Instance Attribute Details

#agent_commands_connectedObject

Returns the value of attribute agent_commands_connected.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def agent_commands_connected
  @agent_commands_connected
end

#agent_idObject

Returns the value of attribute agent_id.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def agent_id
  @agent_id
end

#agent_nameObject

Returns the value of attribute agent_name.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def agent_name
  @agent_name
end

#contextObject

Returns the value of attribute context.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def context
  @context
end

#current_accomplish_goal_commandObject

Returns the value of attribute current_accomplish_goal_command.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def current_accomplish_goal_command
  @current_accomplish_goal_command
end

#include_message_to_user_in_resultObject

Returns the value of attribute include_message_to_user_in_result.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def include_message_to_user_in_result
  @include_message_to_user_in_result
end

#io_errObject

Returns the value of attribute io_err.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def io_err
  @io_err
end

#io_outObject

Returns the value of attribute io_out.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def io_out
  @io_out
end

#llm_modelObject

Returns the value of attribute llm_model.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def llm_model
  @llm_model
end

#maximum_command_callsObject

Returns the value of attribute maximum_command_calls.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def maximum_command_calls
  @maximum_command_calls
end

#pass_aggregates_to_llmObject

Returns the value of attribute pass_aggregates_to_llm.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def pass_aggregates_to_llm
  @pass_aggregates_to_llm
end

#result_entity_depthObject

Returns the value of attribute result_entity_depth.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def result_entity_depth
  @result_entity_depth
end

#result_typeObject

Returns the value of attribute result_type.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def result_type
  @result_type
end

#verboseObject

Returns the value of attribute verbose.



16
17
18
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 16

def verbose
  @verbose
end

Class Method Details

.reset_allObject



12
13
14
15
# File 'foobara-agent-0.0.21/lib/foobara/agent.rb', line 12

def reset_all
  NotifyUserThatCurrentGoalHasBeenAccomplished.clear_subclass_cache
  Util.descendants(NotifyUserThatCurrentGoalHasBeenAccomplished).each(&:clear_subclass_cache)
end

Instance Method Details

#accomplish_goal(goal, result_type: nil, maximum_command_calls: nil, include_message_to_user_in_result: nil, llm_model: nil) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 125

def accomplish_goal(
  goal,
  result_type: nil,
  maximum_command_calls: nil,
  include_message_to_user_in_result: nil,
  llm_model: nil
)
  unless include_message_to_user_in_result.nil?
    self.include_message_to_user_in_result = include_message_to_user_in_result
  end

  set_context_goal(goal)

  if result_type && self.result_type != result_type
    if self.result_type
      # :nocov:
      raise ArgumentError, "You can only specify a result type once"
      # :nocov:
    elsif agent_commands_connected?
      # :nocov:
      raise ArgumentError, "You can't specify a result type this late in the process"
      # :nocov:
    else
      self.result_type = result_type
    end
  end

  unless agent_commands_connected?
    connect_agent_commands
  end

  state_machine.perform_transition!(:accomplish_goal)

  begin
    inputs = accomplish_goal_inputs(goal,
                                    result_type:,
                                    maximum_command_calls:,
                                    llm_model:)

    self.current_accomplish_goal_command = AccomplishGoal.new(inputs)

    current_accomplish_goal_command.run.tap do |outcome|
      transition = if outcome.success?
                     :goal_accomplished
                   else
                     :goal_failed
                   end

      unless killed?
        state_machine.perform_transition!(transition) do
          context.current_goal.state = if outcome.success?
                                         Goal::States::ACCOMPLISHED
                                       else
                                         Goal::States::FAILED
                                       end
        end
      end
    end
  rescue
    # :nocov:
    unless killed?
      state_machine.perform_transition!(:goal_errored) do
        context.current_goal.state = Goal::States::ERROR
      end
    end

    raise
    # :nocov:
  end
end

#accomplish_goal_inputs(goal, result_type: nil, maximum_command_calls: nil, llm_model: nil) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 196

def accomplish_goal_inputs(goal,
                           result_type: nil,
                           maximum_command_calls: nil,
                           llm_model: nil)
  inputs = {
    goal:,
    final_result_type: self.result_type,
    agent: self
  }

  llm_model ||= self.llm_model

  if llm_model
    inputs[:llm_model] = llm_model
  end

  if maximum_command_calls.nil?
    maximum_command_calls = self.maximum_command_calls
  end

  unless maximum_command_calls.nil?
    inputs[:maximum_command_calls] = maximum_command_calls
  end

  if verbose
    inputs[:verbose] = verbose
  end

  if io_out
    inputs[:io_out] = io_out
  end

  if io_err
    inputs[:io_err] = io_err
  end

  if include_message_to_user_in_result || include_message_to_user_in_result == false
    inputs[:include_message_to_user_in_result] = include_message_to_user_in_result
  end

  if result_entity_depth
    inputs[:result_entity_depth] = result_entity_depth
  end

  unless pass_aggregates_to_llm.nil?
    inputs[:pass_aggregates_to_llm] = pass_aggregates_to_llm
  end

  inputs
end

#agent_commands_connected?Boolean

Returns:

  • (Boolean)


261
262
263
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 261

def agent_commands_connected?
  agent_commands_connected
end

#connect(*args, **opts) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 82

def connect(*args, **opts)
  args, opts = desugarize_connect_args(args, opts)

  inputs_transformers = opts[:inputs_transformers] || []
  inputs_transformers = Util.array(inputs_transformers)
  inputs_transformers << CommandConnectors::Transformers::EntityToPrimaryKeyInputsTransformer

  # unless opts.key?(:aggregate_entities)
  #   opts = opts.merge(aggregate_entities: true)
  # end

  super(*args, **opts.merge(inputs_transformers:))
end

#connect_agent_commandsObject



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 265

def connect_agent_commands
  command_classes = [
    DescribeCommand,
    DescribeType,
    GiveUp,
    ListCommands
  ]

  command_classes << if result_type || include_message_to_user_in_result
                       # TODO: Support changing the final result type when the goal changes
                       NotifyUserThatCurrentGoalHasBeenAccomplished.for(
                         result_type:,
                         agent_id:,
                         # TODO: Support changing this flag when the goal changes
                         include_message_to_user_in_result:,
                         result_entity_depth:
                       )
                     else
                       NotifyUserThatCurrentGoalHasBeenAccomplished
                     end

  set_command_connector_transformer = SetCommandConnectorInputsTransformer.for(self)

  command_classes.each do |command_class|
    connect(command_class, inputs: set_command_connector_transformer)
  end

  self.agent_commands_connected = true
end

#give_up(reason) ⇒ Object



257
258
259
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 257

def give_up(reason)
  current_accomplish_goal_command.give_up!(reason)
end

#kill!Object



112
113
114
115
116
117
118
119
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 112

def kill!
  current_accomplish_goal_command&.kill!
  state_machine.perform_transition!(:kill) do
    if context
      context.current_goal.state = Goal::States::KILLED
    end
  end
end

#killed?Boolean

Returns:

  • (Boolean)


121
122
123
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 121

def killed?
  state_machine.current_state == Goal::States::KILLED
end

#mark_mission_accomplished(final_result, message_to_user) ⇒ Object



251
252
253
254
255
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 251

def mark_mission_accomplished(final_result, message_to_user)
  # TODO: this is a pretty awkward way to communicate between commands hmmm...
  # maybe see if there's a less hacky way to pull this off.
  current_accomplish_goal_command.mission_accomplished!(final_result, message_to_user)
end

#run(*args) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 96

def run(*args, **)
  if args.first.is_a?(::String)
    accomplish_goal(*args, **)
  else
    unless agent_commands_connected?
      connect_agent_commands
    end

    super
  end
end

#set_context_goal(goal) ⇒ Object



247
248
249
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 247

def set_context_goal(goal)
  context.set_new_goal(goal)
end

#state_machineObject



108
109
110
# File 'foobara-agent-0.0.21/src/foobara/agent.rb', line 108

def state_machine
  @state_machine ||= StateMachine.new
end