foobara-agent 0.0.1
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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE-MPL-2.0.txt +373 -0
- data/LICENSE.txt +9 -0
- data/README.md +113 -0
- data/lib/foobara/agent.rb +27 -0
- data/src/foobara/agent/accomplish_goal.rb +240 -0
- data/src/foobara/agent/connector/connector.rb +59 -0
- data/src/foobara/agent/connector/set_command_connector_inputs_transformer.rb +28 -0
- data/src/foobara/agent/describe_command.rb +59 -0
- data/src/foobara/agent/describe_type.rb +39 -0
- data/src/foobara/agent/determine_inputs_for_next_command.rb +75 -0
- data/src/foobara/agent/determine_next_command.rb +67 -0
- data/src/foobara/agent/end_session_because_goal_has_been_accomplished.rb +99 -0
- data/src/foobara/agent/give_up.rb +20 -0
- data/src/foobara/agent/list_commands.rb +39 -0
- data/src/foobara/agent/list_types.rb +23 -0
- data/src/foobara/agent/types/command_log_entry.rb +16 -0
- data/src/foobara/agent/types/context.rb +11 -0
- data/src/foobara/agent.rb +61 -0
- metadata +91 -0
@@ -0,0 +1,240 @@
|
|
1
|
+
require_relative "list_commands"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
# TODO: should agent maybe be a command connector? It feels a bit more like a command connector.
|
5
|
+
class Agent
|
6
|
+
class AccomplishGoal < Foobara::Command
|
7
|
+
possible_error :gave_up, context: { reason: :string }, message: "Gave up."
|
8
|
+
|
9
|
+
inputs do
|
10
|
+
agent_name :string, "Name of the agent"
|
11
|
+
goal :string, :required, "What do you want the agent to attempt to accomplish?"
|
12
|
+
# 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"
|
14
|
+
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
|
+
llm_model :string,
|
18
|
+
:allow_nil,
|
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 do
|
25
|
+
message_to_user :string, :required, "Message to the user about successfully accomplishing the goal"
|
26
|
+
result_data :duck, "Optional result data to return to the user if final_result_type was given"
|
27
|
+
end
|
28
|
+
|
29
|
+
depends_on ListCommands
|
30
|
+
|
31
|
+
def execute
|
32
|
+
build_initial_context_if_necessary
|
33
|
+
|
34
|
+
if command_connector_passed_in?
|
35
|
+
set_accomplished_goal_command
|
36
|
+
else
|
37
|
+
build_command_connector
|
38
|
+
connect_user_provided_commands
|
39
|
+
connect_agent_commands
|
40
|
+
end
|
41
|
+
|
42
|
+
unless command_connector.agent_commands_connected?
|
43
|
+
connect_agent_commands
|
44
|
+
end
|
45
|
+
|
46
|
+
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
|
52
|
+
else
|
53
|
+
choose_describe_command_instead
|
54
|
+
fetch_next_command_class
|
55
|
+
end
|
56
|
+
|
57
|
+
run_next_command
|
58
|
+
log_command_outcome
|
59
|
+
end
|
60
|
+
|
61
|
+
if given_up
|
62
|
+
add_given_up_error
|
63
|
+
end
|
64
|
+
|
65
|
+
build_result
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate
|
69
|
+
validate_either_command_classes_or_connector_given
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_either_command_classes_or_connector_given
|
73
|
+
# TODO: implement this!
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_accessor :context, :next_command_name, :next_command_inputs, :mission_accomplished, :given_up,
|
77
|
+
:next_command_class, :next_command, :command_outcome, :timed_out,
|
78
|
+
:final_result, :final_message, :command_response, :delayed_command_name
|
79
|
+
attr_writer :command_connector
|
80
|
+
|
81
|
+
def agent_name
|
82
|
+
@agent_name ||= inputs[:agent_name] || "Anon#{SecureRandom.hex(2)}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_initial_context_if_necessary
|
86
|
+
# TODO: shouldn't have to pass command_log here since it has a default, debug that
|
87
|
+
self.context = current_context || Context.new(command_log: [])
|
88
|
+
end
|
89
|
+
|
90
|
+
def command_connector_passed_in?
|
91
|
+
existing_command_connector
|
92
|
+
end
|
93
|
+
|
94
|
+
def command_connector
|
95
|
+
@command_connector ||= existing_command_connector
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_command_connector
|
99
|
+
self.command_connector ||= Connector.new(
|
100
|
+
accomplish_goal_command: self,
|
101
|
+
default_serializers: [
|
102
|
+
Foobara::CommandConnectors::Serializers::ErrorsSerializer,
|
103
|
+
Foobara::CommandConnectors::Serializers::AtomicSerializer,
|
104
|
+
Foobara::CommandConnectors::Serializers::JsonSerializer
|
105
|
+
],
|
106
|
+
llm_model:
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_accomplished_goal_command
|
111
|
+
command_connector.accomplish_goal_command = self
|
112
|
+
end
|
113
|
+
|
114
|
+
def connect_agent_commands
|
115
|
+
command_connector.connect_agent_commands(final_result_type:, agent_name:)
|
116
|
+
end
|
117
|
+
|
118
|
+
def connect_user_provided_commands
|
119
|
+
command_classes.each do |command_class|
|
120
|
+
command_connector.connect(command_class)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def determine_next_command_name
|
125
|
+
self.next_command_name = if context.command_log.empty?
|
126
|
+
ListCommands.full_command_name
|
127
|
+
elsif delayed_command_name
|
128
|
+
name = delayed_command_name
|
129
|
+
self.delayed_command_name = nil
|
130
|
+
name
|
131
|
+
else
|
132
|
+
command_class = DetermineNextCommand.for(
|
133
|
+
command_class_names: all_command_classes, agent_id: agent_name
|
134
|
+
)
|
135
|
+
|
136
|
+
inputs = { goal:, context: }
|
137
|
+
if llm_model
|
138
|
+
inputs[:llm_model] = llm_model
|
139
|
+
end
|
140
|
+
|
141
|
+
command_class.run!(inputs)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def choose_describe_command_instead
|
146
|
+
self.delayed_command_name = next_command_name
|
147
|
+
self.next_command_inputs = { command_name: next_command_name }
|
148
|
+
self.next_command_name = DescribeCommand.full_command_name
|
149
|
+
end
|
150
|
+
|
151
|
+
def all_command_classes
|
152
|
+
@all_command_classes ||= run_subcommand!(ListCommands, command_connector:).values.flatten
|
153
|
+
end
|
154
|
+
|
155
|
+
def fetch_next_command_class
|
156
|
+
self.next_command_class = command_connector.transformed_command_from_name(next_command_name)
|
157
|
+
end
|
158
|
+
|
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
|
+
)
|
166
|
+
|
167
|
+
inputs = { goal:, context: }
|
168
|
+
if llm_model
|
169
|
+
inputs[:llm_model] = llm_model
|
170
|
+
end
|
171
|
+
|
172
|
+
command_class.run!(inputs)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def run_next_command
|
177
|
+
self.command_response = command_connector.run(
|
178
|
+
full_command_name: next_command_name,
|
179
|
+
inputs: next_command_inputs,
|
180
|
+
action: "run"
|
181
|
+
)
|
182
|
+
|
183
|
+
self.command_outcome = command_response.outcome
|
184
|
+
end
|
185
|
+
|
186
|
+
def log_command_outcome
|
187
|
+
outcome_hash = { success: command_outcome.success? }
|
188
|
+
|
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:
|
195
|
+
end
|
196
|
+
|
197
|
+
context.command_log << CommandLogEntry.new(
|
198
|
+
command_name: next_command_name,
|
199
|
+
inputs: next_command_inputs,
|
200
|
+
outcome: outcome_hash
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
# TODO: these are awkwardly called from outside. Come up with a better solution.
|
205
|
+
def mission_accomplished!(final_result, message)
|
206
|
+
self.mission_accomplished = true
|
207
|
+
self.final_result = final_result
|
208
|
+
self.final_message = message
|
209
|
+
end
|
210
|
+
|
211
|
+
def give_up!(message)
|
212
|
+
self.given_up = true
|
213
|
+
self.final_message = message
|
214
|
+
end
|
215
|
+
|
216
|
+
def add_given_up_error
|
217
|
+
add_runtime_error(:gave_up, reason: final_message)
|
218
|
+
end
|
219
|
+
|
220
|
+
def build_result
|
221
|
+
{
|
222
|
+
message_to_user: final_message,
|
223
|
+
result_data: final_result
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def command_described?
|
228
|
+
described_commands.include?(next_command_name)
|
229
|
+
end
|
230
|
+
|
231
|
+
def described_commands
|
232
|
+
@described_commands ||= Set.new
|
233
|
+
end
|
234
|
+
|
235
|
+
def empty_attributes?(type)
|
236
|
+
type.extends_type?(BuiltinTypes[:attributes]) && type.element_types.empty?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "foobara/command_connectors"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
class Agent
|
5
|
+
class Connector < Foobara::CommandConnector
|
6
|
+
attr_accessor :accomplish_goal_command, :agent_commands_connected, :llm_model
|
7
|
+
|
8
|
+
def initialize(*, accomplish_goal_command:, llm_model: nil, **)
|
9
|
+
self.accomplish_goal_command = accomplish_goal_command
|
10
|
+
self.llm_model = llm_model
|
11
|
+
|
12
|
+
super(*, **)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mark_mission_accomplished(final_result, message_to_user)
|
16
|
+
# TODO: this is a pretty awkward way to communicate between commands hmmm...
|
17
|
+
# maybe see if there's a less hacky way to pull this off.
|
18
|
+
accomplish_goal_command.mission_accomplished!(final_result, message_to_user)
|
19
|
+
end
|
20
|
+
|
21
|
+
def give_up(reason)
|
22
|
+
accomplish_goal_command.give_up!(reason)
|
23
|
+
end
|
24
|
+
|
25
|
+
def agent_commands_connected?
|
26
|
+
agent_commands_connected
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect_agent_commands(final_result_type: nil, agent_name: nil)
|
30
|
+
command_classes = [
|
31
|
+
DescribeCommand,
|
32
|
+
DescribeType,
|
33
|
+
GiveUp,
|
34
|
+
ListCommands,
|
35
|
+
ListTypes
|
36
|
+
]
|
37
|
+
|
38
|
+
command_classes << if final_result_type
|
39
|
+
EndSessionBecauseGoalHasBeenAccomplished.for(
|
40
|
+
result_type: final_result_type,
|
41
|
+
agent_id: agent_name
|
42
|
+
)
|
43
|
+
else
|
44
|
+
EndSessionBecauseGoalHasBeenAccomplished
|
45
|
+
end
|
46
|
+
|
47
|
+
command_classes.each do |command_class|
|
48
|
+
connect(command_class, inputs: set_command_connector_transformer)
|
49
|
+
end
|
50
|
+
|
51
|
+
self.agent_commands_connected = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_command_connector_transformer
|
55
|
+
@set_command_connector_transformer ||= SetCommandConnectorInputsTransformer.for(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent
|
3
|
+
class SetCommandConnectorInputsTransformer < TypeDeclarations::TypedTransformer
|
4
|
+
class << self
|
5
|
+
attr_accessor :command_connector
|
6
|
+
|
7
|
+
def for(command_connector)
|
8
|
+
Class.new(self).tap do |subclass|
|
9
|
+
subclass.command_connector = command_connector
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def command_connector
|
15
|
+
self.class.command_connector
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_type_declaration
|
19
|
+
to_declaration = to_type.declaration_data
|
20
|
+
TypeDeclarations::Attributes.reject(to_declaration, :command_connector)
|
21
|
+
end
|
22
|
+
|
23
|
+
def transform(inputs)
|
24
|
+
inputs.merge(command_connector:)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent
|
3
|
+
class DescribeCommand < Foobara::Command
|
4
|
+
inputs do
|
5
|
+
command_connector :duck, :required, "Connector to find relevant command in"
|
6
|
+
command_name :string, :required, "Name of the command to describe"
|
7
|
+
end
|
8
|
+
|
9
|
+
result :duck, description: "Information about the command"
|
10
|
+
|
11
|
+
def execute
|
12
|
+
find_command_class
|
13
|
+
|
14
|
+
set_command_name
|
15
|
+
set_description
|
16
|
+
set_inputs_type
|
17
|
+
set_result_type
|
18
|
+
|
19
|
+
mark_command_as_described
|
20
|
+
|
21
|
+
command_description
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :command_class
|
25
|
+
|
26
|
+
def find_command_class
|
27
|
+
self.command_class = command_connector.transformed_command_from_name(command_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def command_description
|
31
|
+
@command_description ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_command_name
|
35
|
+
command_description[:full_command_name] = command_class.full_command_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_description
|
39
|
+
command_description[:description] = command_class.description
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_inputs_type
|
43
|
+
if command_class.inputs_type
|
44
|
+
command_description[:inputs_type] = JsonSchemaGenerator.to_json_schema(command_class.inputs_type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_result_type
|
49
|
+
if command_class.result_type
|
50
|
+
command_description[:result_type] = JsonSchemaGenerator.to_json_schema(command_class.result_type)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def mark_command_as_described
|
55
|
+
command_connector.accomplish_goal_command.described_commands << command_class.full_command_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent
|
3
|
+
class DescribeType < Foobara::Command
|
4
|
+
inputs do
|
5
|
+
command_connector :duck, :required, "Connector to find relevant type in"
|
6
|
+
type_name :string, :required, "Name of the type to describe"
|
7
|
+
end
|
8
|
+
|
9
|
+
result :duck, description: "Information about the type"
|
10
|
+
|
11
|
+
def execute
|
12
|
+
find_type
|
13
|
+
|
14
|
+
set_type_name
|
15
|
+
set_json_schema
|
16
|
+
|
17
|
+
type_description
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :type
|
21
|
+
|
22
|
+
def find_type
|
23
|
+
self.type = command_connector.command_registry.foobara_lookup_type(type_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def type_description
|
27
|
+
@type_description ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_type_name
|
31
|
+
type_description[:full_type_name] = type.scoped_full_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_json_schema
|
35
|
+
type_description[:json_schema] = JsonSchemaGenerator.to_json_schema(type)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "foobara/llm_backed_command"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
class Agent
|
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
|
26
|
+
|
27
|
+
def for(command_class:, agent_id:)
|
28
|
+
cached_command(agent_id, command_class.full_command_name) do
|
29
|
+
command_short_name = Util.non_full_name(command_class.command_name)
|
30
|
+
class_name = "Foobara::Agent::#{agent_id}::DetermineInputsForNext#{command_short_name}Command"
|
31
|
+
klass = Util.make_class_p(class_name, self)
|
32
|
+
|
33
|
+
klass.command_class = command_class
|
34
|
+
|
35
|
+
klass.description "Accepts a goal and context of the work so far and returns the inputs for " \
|
36
|
+
"the next #{command_short_name} command to run to make progress towards " \
|
37
|
+
"accomplishing the goal."
|
38
|
+
|
39
|
+
klass.inputs do
|
40
|
+
goal :string, :required, "What do you want the agent to attempt to accomplish?"
|
41
|
+
context Context, :required, "Context of the progress towards the goal so far"
|
42
|
+
llm_model :string,
|
43
|
+
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
44
|
+
default: "claude-3-7-sonnet-20250219",
|
45
|
+
description: "The model to use for the LLM"
|
46
|
+
end
|
47
|
+
|
48
|
+
if command_class.inputs_type
|
49
|
+
klass.result command_class.inputs_type
|
50
|
+
end
|
51
|
+
|
52
|
+
klass
|
53
|
+
end
|
54
|
+
end
|
55
|
+
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
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "foobara/llm_backed_command"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
class Agent
|
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
|
24
|
+
|
25
|
+
def for(command_class_names:, agent_id:)
|
26
|
+
cached_command(agent_id) do
|
27
|
+
command_name = "Foobara::Agent::#{agent_id}::DetermineNextCommand"
|
28
|
+
klass = Util.make_class_p(command_name, self)
|
29
|
+
|
30
|
+
klass.command_class_names = command_class_names
|
31
|
+
|
32
|
+
klass.inputs do
|
33
|
+
goal :string, :required, "What do you want the agent to attempt to accomplish?"
|
34
|
+
context Context, :required, "Context of the current mission so far"
|
35
|
+
llm_model :string,
|
36
|
+
one_of: Foobara::Ai::AnswerBot::Types::ModelEnum,
|
37
|
+
default: "claude-3-7-sonnet-20250219",
|
38
|
+
description: "The model to use for the LLM"
|
39
|
+
end
|
40
|
+
|
41
|
+
klass.result :string,
|
42
|
+
one_of: command_class_names,
|
43
|
+
description: "Name of the next command to run to make progress " \
|
44
|
+
"towards accomplishing the mission"
|
45
|
+
|
46
|
+
klass
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
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."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent
|
3
|
+
class EndSessionBecauseGoalHasBeenAccomplished < Foobara::Command
|
4
|
+
class << self
|
5
|
+
attr_accessor :command_class
|
6
|
+
|
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
|
+
def for(result_type:, agent_id:)
|
28
|
+
cached_command(agent_id, result_type) do
|
29
|
+
command_name = "Foobara::Agent::#{agent_id}::EndSessionBecauseGoalHasBeenAccomplished"
|
30
|
+
klass = Util.make_class_p(command_name, self)
|
31
|
+
|
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."
|
34
|
+
|
35
|
+
inputs do
|
36
|
+
# TODO: Are we still not able to uses classes as foobara types??
|
37
|
+
command_connector :duck, :required, "Connector to end"
|
38
|
+
message_to_user :string, "Optional message to the user"
|
39
|
+
end
|
40
|
+
|
41
|
+
if result_type
|
42
|
+
add_inputs do
|
43
|
+
result_data(*result_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
klass.result do
|
47
|
+
message_to_user :string
|
48
|
+
result_data(*result_type)
|
49
|
+
end
|
50
|
+
|
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
|
+
else
|
54
|
+
# TODO: test this code path
|
55
|
+
# :nocov:
|
56
|
+
klass.description "Ends the session giving an optional message to the user."
|
57
|
+
# :nocov:
|
58
|
+
end
|
59
|
+
|
60
|
+
klass
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
description "Ends the session giving a final result formatted according to the " \
|
66
|
+
"result schema if relevant and an optional message to the user."
|
67
|
+
|
68
|
+
inputs do
|
69
|
+
# TODO: Are we still not able to uses classes as foobara types??
|
70
|
+
command_connector :duck, :required, "Connector to end"
|
71
|
+
message_to_user :string, "Optional message to the user"
|
72
|
+
result_data :duck, "The final result of the work if relevant/expected"
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute
|
76
|
+
mark_mission_accomplished
|
77
|
+
|
78
|
+
parsed_result
|
79
|
+
end
|
80
|
+
|
81
|
+
def mark_mission_accomplished
|
82
|
+
data = if result_type
|
83
|
+
inputs[:result_data]
|
84
|
+
end
|
85
|
+
command_connector.mark_mission_accomplished(data, message_to_user)
|
86
|
+
end
|
87
|
+
|
88
|
+
def parsed_result
|
89
|
+
h = { message_to_user: }
|
90
|
+
|
91
|
+
if inputs[:result_data] && result_type
|
92
|
+
h[:result_data] = result_data
|
93
|
+
end
|
94
|
+
|
95
|
+
h
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Agent
|
3
|
+
class GiveUp < Foobara::Command
|
4
|
+
inputs do
|
5
|
+
command_connector :duck, :required, "Connector to end"
|
6
|
+
message_to_user :string, "Optional message to the user explaining why you decided to give up"
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
mark_given_up
|
11
|
+
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def mark_given_up
|
16
|
+
command_connector.give_up(message_to_user)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|