LolorzoloL commited on
Commit
12bfabc
·
1 Parent(s): fee0a8b

update autogpt

Browse files
autogpt/agent/agent.py CHANGED
@@ -1,14 +1,15 @@
1
  from colorama import Fore, Style
2
 
3
  from autogpt.app import execute_command, get_command
4
- from autogpt.chat import chat_with_ai, create_chat_message
5
  from autogpt.config import Config
6
  from autogpt.json_utils.json_fix_llm import fix_json_using_multiple_techniques
7
- from autogpt.json_utils.utilities import validate_json
 
8
  from autogpt.logs import logger, print_assistant_thoughts
9
  from autogpt.speech import say_text
10
  from autogpt.spinner import Spinner
11
  from autogpt.utils import clean_input
 
12
 
13
 
14
  class Agent:
@@ -19,18 +20,25 @@ class Agent:
19
  memory: The memory object to use.
20
  full_message_history: The full message history.
21
  next_action_count: The number of actions to execute.
22
- system_prompt: The system prompt is the initial prompt that defines everything the AI needs to know to achieve its task successfully.
23
- Currently, the dynamic and customizable information in the system prompt are ai_name, description and goals.
 
 
24
 
25
- triggering_prompt: The last sentence the AI will see before answering. For Auto-GPT, this prompt is:
26
- Determine which next command to use, and respond using the format specified above:
27
- The triggering prompt is not part of the system prompt because between the system prompt and the triggering
28
- prompt we have contextual information that can distract the AI and make it forget that its goal is to find the next task to achieve.
 
 
 
 
29
  SYSTEM PROMPT
30
  CONTEXTUAL INFORMATION (memory, previous conversations, anything relevant)
31
  TRIGGERING PROMPT
32
 
33
- The triggering prompt reminds the AI about its short term meta task (defining the next task)
 
34
  """
35
 
36
  def __init__(
@@ -39,15 +47,26 @@ class Agent:
39
  memory,
40
  full_message_history,
41
  next_action_count,
 
 
42
  system_prompt,
43
  triggering_prompt,
 
44
  ):
 
45
  self.ai_name = ai_name
46
  self.memory = memory
 
 
 
 
47
  self.full_message_history = full_message_history
48
  self.next_action_count = next_action_count
 
 
49
  self.system_prompt = system_prompt
50
  self.triggering_prompt = triggering_prompt
 
51
 
52
  def start_interaction_loop(self):
53
  # Interaction Loop
@@ -69,10 +88,10 @@ class Agent:
69
  "Continuous Limit Reached: ", Fore.YELLOW, f"{cfg.continuous_limit}"
70
  )
71
  break
72
-
73
  # Send message to AI, get response
74
  with Spinner("Thinking... "):
75
  assistant_reply = chat_with_ai(
 
76
  self.system_prompt,
77
  self.triggering_prompt,
78
  self.full_message_history,
@@ -81,60 +100,92 @@ class Agent:
81
  ) # TODO: This hardcodes the model to use GPT3.5. Make this an argument
82
 
83
  assistant_reply_json = fix_json_using_multiple_techniques(assistant_reply)
 
 
 
 
84
 
85
  # Print Assistant thoughts
86
  if assistant_reply_json != {}:
87
- validate_json(assistant_reply_json, "llm_response_format_1")
88
  # Get command name and arguments
89
  try:
90
- print_assistant_thoughts(self.ai_name, assistant_reply_json)
 
 
91
  command_name, arguments = get_command(assistant_reply_json)
92
- # command_name, arguments = assistant_reply_json_valid["command"]["name"], assistant_reply_json_valid["command"]["args"]
93
  if cfg.speak_mode:
94
  say_text(f"I want to execute {command_name}")
 
 
 
95
  except Exception as e:
96
  logger.error("Error: \n", str(e))
97
 
98
  if not cfg.continuous_mode and self.next_action_count == 0:
99
- ### GET USER AUTHORIZATION TO EXECUTE COMMAND ###
100
  # Get key press: Prompt the user to press enter to continue or escape
101
  # to exit
 
102
  logger.typewriter_log(
103
  "NEXT ACTION: ",
104
  Fore.CYAN,
105
  f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} "
106
  f"ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
107
  )
108
- print(
109
- "Enter 'y' to authorise command, 'y -N' to run N continuous "
110
- "commands, 'n' to exit program, or enter feedback for "
111
- f"{self.ai_name}...",
112
- flush=True,
113
  )
114
  while True:
115
- console_input = clean_input(
116
- Fore.MAGENTA + "Input:" + Style.RESET_ALL
117
- )
118
- if console_input.lower().strip() == "y":
 
 
 
119
  user_input = "GENERATE NEXT COMMAND JSON"
120
  break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  elif console_input.lower().strip() == "":
122
- print("Invalid input format.")
123
  continue
124
- elif console_input.lower().startswith("y -"):
125
  try:
126
  self.next_action_count = abs(
127
  int(console_input.split(" ")[1])
128
  )
129
  user_input = "GENERATE NEXT COMMAND JSON"
130
  except ValueError:
131
- print(
132
  "Invalid input format. Please enter 'y -n' where n is"
133
  " the number of continuous tasks."
134
  )
135
  continue
136
  break
137
- elif console_input.lower() == "n":
138
  user_input = "EXIT"
139
  break
140
  else:
@@ -149,7 +200,7 @@ class Agent:
149
  "",
150
  )
151
  elif user_input == "EXIT":
152
- print("Exiting...", flush=True)
153
  break
154
  else:
155
  # Print command
@@ -168,21 +219,27 @@ class Agent:
168
  elif command_name == "human_feedback":
169
  result = f"Human feedback: {user_input}"
170
  else:
171
- result = (
172
- f"Command {command_name} returned: "
173
- f"{execute_command(command_name, arguments)}"
 
 
 
 
 
 
 
 
174
  )
 
 
 
 
 
 
175
  if self.next_action_count > 0:
176
  self.next_action_count -= 1
177
 
178
- memory_to_add = (
179
- f"Assistant Reply: {assistant_reply} "
180
- f"\nResult: {result} "
181
- f"\nHuman Feedback: {user_input} "
182
- )
183
-
184
- self.memory.add(memory_to_add)
185
-
186
  # Check if there's a result from the command append it to the message
187
  # history
188
  if result is not None:
@@ -195,3 +252,39 @@ class Agent:
195
  logger.typewriter_log(
196
  "SYSTEM: ", Fore.YELLOW, "Unable to execute command"
197
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from colorama import Fore, Style
2
 
3
  from autogpt.app import execute_command, get_command
 
4
  from autogpt.config import Config
5
  from autogpt.json_utils.json_fix_llm import fix_json_using_multiple_techniques
6
+ from autogpt.json_utils.utilities import LLM_DEFAULT_RESPONSE_FORMAT, validate_json
7
+ from autogpt.llm import chat_with_ai, create_chat_completion, create_chat_message
8
  from autogpt.logs import logger, print_assistant_thoughts
9
  from autogpt.speech import say_text
10
  from autogpt.spinner import Spinner
11
  from autogpt.utils import clean_input
12
+ from autogpt.workspace import Workspace
13
 
14
 
15
  class Agent:
 
20
  memory: The memory object to use.
21
  full_message_history: The full message history.
22
  next_action_count: The number of actions to execute.
23
+ system_prompt: The system prompt is the initial prompt that defines everything
24
+ the AI needs to know to achieve its task successfully.
25
+ Currently, the dynamic and customizable information in the system prompt are
26
+ ai_name, description and goals.
27
 
28
+ triggering_prompt: The last sentence the AI will see before answering.
29
+ For Auto-GPT, this prompt is:
30
+ Determine which next command to use, and respond using the format specified
31
+ above:
32
+ The triggering prompt is not part of the system prompt because between the
33
+ system prompt and the triggering
34
+ prompt we have contextual information that can distract the AI and make it
35
+ forget that its goal is to find the next task to achieve.
36
  SYSTEM PROMPT
37
  CONTEXTUAL INFORMATION (memory, previous conversations, anything relevant)
38
  TRIGGERING PROMPT
39
 
40
+ The triggering prompt reminds the AI about its short term meta task
41
+ (defining the next task)
42
  """
43
 
44
  def __init__(
 
47
  memory,
48
  full_message_history,
49
  next_action_count,
50
+ command_registry,
51
+ config,
52
  system_prompt,
53
  triggering_prompt,
54
+ workspace_directory,
55
  ):
56
+ cfg = Config()
57
  self.ai_name = ai_name
58
  self.memory = memory
59
+ self.summary_memory = (
60
+ "I was created." # Initial memory necessary to avoid hilucination
61
+ )
62
+ self.last_memory_index = 0
63
  self.full_message_history = full_message_history
64
  self.next_action_count = next_action_count
65
+ self.command_registry = command_registry
66
+ self.config = config
67
  self.system_prompt = system_prompt
68
  self.triggering_prompt = triggering_prompt
69
+ self.workspace = Workspace(workspace_directory, cfg.restrict_to_workspace)
70
 
71
  def start_interaction_loop(self):
72
  # Interaction Loop
 
88
  "Continuous Limit Reached: ", Fore.YELLOW, f"{cfg.continuous_limit}"
89
  )
90
  break
 
91
  # Send message to AI, get response
92
  with Spinner("Thinking... "):
93
  assistant_reply = chat_with_ai(
94
+ self,
95
  self.system_prompt,
96
  self.triggering_prompt,
97
  self.full_message_history,
 
100
  ) # TODO: This hardcodes the model to use GPT3.5. Make this an argument
101
 
102
  assistant_reply_json = fix_json_using_multiple_techniques(assistant_reply)
103
+ for plugin in cfg.plugins:
104
+ if not plugin.can_handle_post_planning():
105
+ continue
106
+ assistant_reply_json = plugin.post_planning(self, assistant_reply_json)
107
 
108
  # Print Assistant thoughts
109
  if assistant_reply_json != {}:
110
+ validate_json(assistant_reply_json, LLM_DEFAULT_RESPONSE_FORMAT)
111
  # Get command name and arguments
112
  try:
113
+ print_assistant_thoughts(
114
+ self.ai_name, assistant_reply_json, cfg.speak_mode
115
+ )
116
  command_name, arguments = get_command(assistant_reply_json)
 
117
  if cfg.speak_mode:
118
  say_text(f"I want to execute {command_name}")
119
+
120
+ arguments = self._resolve_pathlike_command_args(arguments)
121
+
122
  except Exception as e:
123
  logger.error("Error: \n", str(e))
124
 
125
  if not cfg.continuous_mode and self.next_action_count == 0:
126
+ # ### GET USER AUTHORIZATION TO EXECUTE COMMAND ###
127
  # Get key press: Prompt the user to press enter to continue or escape
128
  # to exit
129
+ self.user_input = ""
130
  logger.typewriter_log(
131
  "NEXT ACTION: ",
132
  Fore.CYAN,
133
  f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} "
134
  f"ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
135
  )
136
+
137
+ logger.info(
138
+ "Enter 'y' to authorise command, 'y -N' to run N continuous commands, 's' to run self-feedback commands"
139
+ "'n' to exit program, or enter feedback for "
140
+ f"{self.ai_name}..."
141
  )
142
  while True:
143
+ if cfg.chat_messages_enabled:
144
+ console_input = clean_input("Waiting for your response...")
145
+ else:
146
+ console_input = clean_input(
147
+ Fore.MAGENTA + "Input:" + Style.RESET_ALL
148
+ )
149
+ if console_input.lower().strip() == cfg.authorise_key:
150
  user_input = "GENERATE NEXT COMMAND JSON"
151
  break
152
+ elif console_input.lower().strip() == "s":
153
+ logger.typewriter_log(
154
+ "-=-=-=-=-=-=-= THOUGHTS, REASONING, PLAN AND CRITICISM WILL NOW BE VERIFIED BY AGENT -=-=-=-=-=-=-=",
155
+ Fore.GREEN,
156
+ "",
157
+ )
158
+ thoughts = assistant_reply_json.get("thoughts", {})
159
+ self_feedback_resp = self.get_self_feedback(
160
+ thoughts, cfg.fast_llm_model
161
+ )
162
+ logger.typewriter_log(
163
+ f"SELF FEEDBACK: {self_feedback_resp}",
164
+ Fore.YELLOW,
165
+ "",
166
+ )
167
+ if self_feedback_resp[0].lower().strip() == cfg.authorise_key:
168
+ user_input = "GENERATE NEXT COMMAND JSON"
169
+ else:
170
+ user_input = self_feedback_resp
171
+ break
172
  elif console_input.lower().strip() == "":
173
+ logger.warn("Invalid input format.")
174
  continue
175
+ elif console_input.lower().startswith(f"{cfg.authorise_key} -"):
176
  try:
177
  self.next_action_count = abs(
178
  int(console_input.split(" ")[1])
179
  )
180
  user_input = "GENERATE NEXT COMMAND JSON"
181
  except ValueError:
182
+ logger.warn(
183
  "Invalid input format. Please enter 'y -n' where n is"
184
  " the number of continuous tasks."
185
  )
186
  continue
187
  break
188
+ elif console_input.lower() == cfg.exit_key:
189
  user_input = "EXIT"
190
  break
191
  else:
 
200
  "",
201
  )
202
  elif user_input == "EXIT":
203
+ logger.info("Exiting...")
204
  break
205
  else:
206
  # Print command
 
219
  elif command_name == "human_feedback":
220
  result = f"Human feedback: {user_input}"
221
  else:
222
+ for plugin in cfg.plugins:
223
+ if not plugin.can_handle_pre_command():
224
+ continue
225
+ command_name, arguments = plugin.pre_command(
226
+ command_name, arguments
227
+ )
228
+ command_result = execute_command(
229
+ self.command_registry,
230
+ command_name,
231
+ arguments,
232
+ self.config.prompt_generator,
233
  )
234
+ result = f"Command {command_name} returned: " f"{command_result}"
235
+
236
+ for plugin in cfg.plugins:
237
+ if not plugin.can_handle_post_command():
238
+ continue
239
+ result = plugin.post_command(command_name, result)
240
  if self.next_action_count > 0:
241
  self.next_action_count -= 1
242
 
 
 
 
 
 
 
 
 
243
  # Check if there's a result from the command append it to the message
244
  # history
245
  if result is not None:
 
252
  logger.typewriter_log(
253
  "SYSTEM: ", Fore.YELLOW, "Unable to execute command"
254
  )
255
+
256
+ def _resolve_pathlike_command_args(self, command_args):
257
+ if "directory" in command_args and command_args["directory"] in {"", "/"}:
258
+ command_args["directory"] = str(self.workspace.root)
259
+ else:
260
+ for pathlike in ["filename", "directory", "clone_path"]:
261
+ if pathlike in command_args:
262
+ command_args[pathlike] = str(
263
+ self.workspace.get_path(command_args[pathlike])
264
+ )
265
+ return command_args
266
+
267
+ def get_self_feedback(self, thoughts: dict, llm_model: str) -> str:
268
+ """Generates a feedback response based on the provided thoughts dictionary.
269
+ This method takes in a dictionary of thoughts containing keys such as 'reasoning',
270
+ 'plan', 'thoughts', and 'criticism'. It combines these elements into a single
271
+ feedback message and uses the create_chat_completion() function to generate a
272
+ response based on the input message.
273
+ Args:
274
+ thoughts (dict): A dictionary containing thought elements like reasoning,
275
+ plan, thoughts, and criticism.
276
+ Returns:
277
+ str: A feedback response generated using the provided thoughts dictionary.
278
+ """
279
+ ai_role = self.config.ai_role
280
+
281
+ feedback_prompt = f"Below is a message from an AI agent with the role of {ai_role}. Please review the provided Thought, Reasoning, Plan, and Criticism. If these elements accurately contribute to the successful execution of the assumed role, respond with the letter 'Y' followed by a space, and then explain why it is effective. If the provided information is not suitable for achieving the role's objectives, please provide one or more sentences addressing the issue and suggesting a resolution."
282
+ reasoning = thoughts.get("reasoning", "")
283
+ plan = thoughts.get("plan", "")
284
+ thought = thoughts.get("thoughts", "")
285
+ criticism = thoughts.get("criticism", "")
286
+ feedback_thoughts = thought + reasoning + plan + criticism
287
+ return create_chat_completion(
288
+ [{"role": "user", "content": feedback_prompt + feedback_thoughts}],
289
+ llm_model,
290
+ )
autogpt/agent/agent_manager.py CHANGED
@@ -1,10 +1,11 @@
1
  """Agent manager for managing GPT agents"""
2
  from __future__ import annotations
3
 
4
- from typing import Union
5
 
6
- from autogpt.config.config import Singleton
7
- from autogpt.llm_utils import create_chat_completion
 
8
 
9
 
10
  class AgentManager(metaclass=Singleton):
@@ -13,6 +14,7 @@ class AgentManager(metaclass=Singleton):
13
  def __init__(self):
14
  self.next_key = 0
15
  self.agents = {} # key, (task, full_message_history, model)
 
16
 
17
  # Create new GPT agent
18
  # TODO: Centralise use of create_chat_completion() to globally enforce token limit
@@ -28,19 +30,32 @@ class AgentManager(metaclass=Singleton):
28
  Returns:
29
  The key of the new agent
30
  """
31
- messages = [
32
  {"role": "user", "content": prompt},
33
  ]
34
-
 
 
 
 
35
  # Start GPT instance
36
  agent_reply = create_chat_completion(
37
  model=model,
38
  messages=messages,
39
  )
40
 
41
- # Update full message history
42
  messages.append({"role": "assistant", "content": agent_reply})
43
 
 
 
 
 
 
 
 
 
 
 
44
  key = self.next_key
45
  # This is done instead of len(agents) to make keys unique even if agents
46
  # are deleted
@@ -48,6 +63,11 @@ class AgentManager(metaclass=Singleton):
48
 
49
  self.agents[key] = (task, messages, model)
50
 
 
 
 
 
 
51
  return key, agent_reply
52
 
53
  def message_agent(self, key: str | int, message: str) -> str:
@@ -65,15 +85,37 @@ class AgentManager(metaclass=Singleton):
65
  # Add user message to message history before sending to agent
66
  messages.append({"role": "user", "content": message})
67
 
 
 
 
 
 
 
 
68
  # Start GPT instance
69
  agent_reply = create_chat_completion(
70
  model=model,
71
  messages=messages,
72
  )
73
 
74
- # Update full message history
75
  messages.append({"role": "assistant", "content": agent_reply})
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  return agent_reply
78
 
79
  def list_agents(self) -> list[tuple[str | int, str]]:
@@ -86,7 +128,7 @@ class AgentManager(metaclass=Singleton):
86
  # Return a list of agent keys and their tasks
87
  return [(key, task) for key, (task, _, _) in self.agents.items()]
88
 
89
- def delete_agent(self, key: Union[str, int]) -> bool:
90
  """Delete an agent from the agent manager
91
 
92
  Args:
 
1
  """Agent manager for managing GPT agents"""
2
  from __future__ import annotations
3
 
4
+ from typing import List
5
 
6
+ from autogpt.config.config import Config
7
+ from autogpt.llm import Message, create_chat_completion
8
+ from autogpt.singleton import Singleton
9
 
10
 
11
  class AgentManager(metaclass=Singleton):
 
14
  def __init__(self):
15
  self.next_key = 0
16
  self.agents = {} # key, (task, full_message_history, model)
17
+ self.cfg = Config()
18
 
19
  # Create new GPT agent
20
  # TODO: Centralise use of create_chat_completion() to globally enforce token limit
 
30
  Returns:
31
  The key of the new agent
32
  """
33
+ messages: List[Message] = [
34
  {"role": "user", "content": prompt},
35
  ]
36
+ for plugin in self.cfg.plugins:
37
+ if not plugin.can_handle_pre_instruction():
38
+ continue
39
+ if plugin_messages := plugin.pre_instruction(messages):
40
+ messages.extend(iter(plugin_messages))
41
  # Start GPT instance
42
  agent_reply = create_chat_completion(
43
  model=model,
44
  messages=messages,
45
  )
46
 
 
47
  messages.append({"role": "assistant", "content": agent_reply})
48
 
49
+ plugins_reply = ""
50
+ for i, plugin in enumerate(self.cfg.plugins):
51
+ if not plugin.can_handle_on_instruction():
52
+ continue
53
+ if plugin_result := plugin.on_instruction(messages):
54
+ sep = "\n" if i else ""
55
+ plugins_reply = f"{plugins_reply}{sep}{plugin_result}"
56
+
57
+ if plugins_reply and plugins_reply != "":
58
+ messages.append({"role": "assistant", "content": plugins_reply})
59
  key = self.next_key
60
  # This is done instead of len(agents) to make keys unique even if agents
61
  # are deleted
 
63
 
64
  self.agents[key] = (task, messages, model)
65
 
66
+ for plugin in self.cfg.plugins:
67
+ if not plugin.can_handle_post_instruction():
68
+ continue
69
+ agent_reply = plugin.post_instruction(agent_reply)
70
+
71
  return key, agent_reply
72
 
73
  def message_agent(self, key: str | int, message: str) -> str:
 
85
  # Add user message to message history before sending to agent
86
  messages.append({"role": "user", "content": message})
87
 
88
+ for plugin in self.cfg.plugins:
89
+ if not plugin.can_handle_pre_instruction():
90
+ continue
91
+ if plugin_messages := plugin.pre_instruction(messages):
92
+ for plugin_message in plugin_messages:
93
+ messages.append(plugin_message)
94
+
95
  # Start GPT instance
96
  agent_reply = create_chat_completion(
97
  model=model,
98
  messages=messages,
99
  )
100
 
 
101
  messages.append({"role": "assistant", "content": agent_reply})
102
 
103
+ plugins_reply = agent_reply
104
+ for i, plugin in enumerate(self.cfg.plugins):
105
+ if not plugin.can_handle_on_instruction():
106
+ continue
107
+ if plugin_result := plugin.on_instruction(messages):
108
+ sep = "\n" if i else ""
109
+ plugins_reply = f"{plugins_reply}{sep}{plugin_result}"
110
+ # Update full message history
111
+ if plugins_reply and plugins_reply != "":
112
+ messages.append({"role": "assistant", "content": plugins_reply})
113
+
114
+ for plugin in self.cfg.plugins:
115
+ if not plugin.can_handle_post_instruction():
116
+ continue
117
+ agent_reply = plugin.post_instruction(agent_reply)
118
+
119
  return agent_reply
120
 
121
  def list_agents(self) -> list[tuple[str | int, str]]:
 
128
  # Return a list of agent keys and their tasks
129
  return [(key, task) for key, (task, _, _) in self.agents.items()]
130
 
131
+ def delete_agent(self, key: str | int) -> bool:
132
  """Delete an agent from the agent manager
133
 
134
  Args:
autogpt/commands/analyze_code.py CHANGED
@@ -1,9 +1,15 @@
1
  """Code evaluation module."""
2
  from __future__ import annotations
3
 
4
- from autogpt.llm_utils import call_ai_function
 
5
 
6
 
 
 
 
 
 
7
  def analyze_code(code: str) -> list[str]:
8
  """
9
  A function that takes in a string and returns a response from create chat
@@ -16,10 +22,10 @@ def analyze_code(code: str) -> list[str]:
16
  improve the code.
17
  """
18
 
19
- function_string = "def analyze_code(code: str) -> List[str]:"
20
  args = [code]
21
  description_string = (
22
- "Analyzes the given code and returns a list of suggestions" " for improvements."
23
  )
24
 
25
  return call_ai_function(function_string, args, description_string)
 
1
  """Code evaluation module."""
2
  from __future__ import annotations
3
 
4
+ from autogpt.commands.command import command
5
+ from autogpt.llm import call_ai_function
6
 
7
 
8
+ @command(
9
+ "analyze_code",
10
+ "Analyze Code",
11
+ '"code": "<full_code_string>"',
12
+ )
13
  def analyze_code(code: str) -> list[str]:
14
  """
15
  A function that takes in a string and returns a response from create chat
 
22
  improve the code.
23
  """
24
 
25
+ function_string = "def analyze_code(code: str) -> list[str]:"
26
  args = [code]
27
  description_string = (
28
+ "Analyzes the given code and returns a list of suggestions for improvements."
29
  )
30
 
31
  return call_ai_function(function_string, args, description_string)
autogpt/commands/command.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import importlib
3
+ import inspect
4
+ from typing import Any, Callable, Optional
5
+
6
+ # Unique identifier for auto-gpt commands
7
+ AUTO_GPT_COMMAND_IDENTIFIER = "auto_gpt_command"
8
+
9
+
10
+ class Command:
11
+ """A class representing a command.
12
+
13
+ Attributes:
14
+ name (str): The name of the command.
15
+ description (str): A brief description of what the command does.
16
+ signature (str): The signature of the function that the command executes. Defaults to None.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ name: str,
22
+ description: str,
23
+ method: Callable[..., Any],
24
+ signature: str = "",
25
+ enabled: bool = True,
26
+ disabled_reason: Optional[str] = None,
27
+ ):
28
+ self.name = name
29
+ self.description = description
30
+ self.method = method
31
+ self.signature = signature if signature else str(inspect.signature(self.method))
32
+ self.enabled = enabled
33
+ self.disabled_reason = disabled_reason
34
+
35
+ def __call__(self, *args, **kwargs) -> Any:
36
+ if not self.enabled:
37
+ return f"Command '{self.name}' is disabled: {self.disabled_reason}"
38
+ return self.method(*args, **kwargs)
39
+
40
+ def __str__(self) -> str:
41
+ return f"{self.name}: {self.description}, args: {self.signature}"
42
+
43
+
44
+ class CommandRegistry:
45
+ """
46
+ The CommandRegistry class is a manager for a collection of Command objects.
47
+ It allows the registration, modification, and retrieval of Command objects,
48
+ as well as the scanning and loading of command plugins from a specified
49
+ directory.
50
+ """
51
+
52
+ def __init__(self):
53
+ self.commands = {}
54
+
55
+ def _import_module(self, module_name: str) -> Any:
56
+ return importlib.import_module(module_name)
57
+
58
+ def _reload_module(self, module: Any) -> Any:
59
+ return importlib.reload(module)
60
+
61
+ def register(self, cmd: Command) -> None:
62
+ self.commands[cmd.name] = cmd
63
+
64
+ def unregister(self, command_name: str):
65
+ if command_name in self.commands:
66
+ del self.commands[command_name]
67
+ else:
68
+ raise KeyError(f"Command '{command_name}' not found in registry.")
69
+
70
+ def reload_commands(self) -> None:
71
+ """Reloads all loaded command plugins."""
72
+ for cmd_name in self.commands:
73
+ cmd = self.commands[cmd_name]
74
+ module = self._import_module(cmd.__module__)
75
+ reloaded_module = self._reload_module(module)
76
+ if hasattr(reloaded_module, "register"):
77
+ reloaded_module.register(self)
78
+
79
+ def get_command(self, name: str) -> Callable[..., Any]:
80
+ return self.commands[name]
81
+
82
+ def call(self, command_name: str, **kwargs) -> Any:
83
+ if command_name not in self.commands:
84
+ raise KeyError(f"Command '{command_name}' not found in registry.")
85
+ command = self.commands[command_name]
86
+ return command(**kwargs)
87
+
88
+ def command_prompt(self) -> str:
89
+ """
90
+ Returns a string representation of all registered `Command` objects for use in a prompt
91
+ """
92
+ commands_list = [
93
+ f"{idx + 1}. {str(cmd)}" for idx, cmd in enumerate(self.commands.values())
94
+ ]
95
+ return "\n".join(commands_list)
96
+
97
+ def import_commands(self, module_name: str) -> None:
98
+ """
99
+ Imports the specified Python module containing command plugins.
100
+
101
+ This method imports the associated module and registers any functions or
102
+ classes that are decorated with the `AUTO_GPT_COMMAND_IDENTIFIER` attribute
103
+ as `Command` objects. The registered `Command` objects are then added to the
104
+ `commands` dictionary of the `CommandRegistry` object.
105
+
106
+ Args:
107
+ module_name (str): The name of the module to import for command plugins.
108
+ """
109
+
110
+ module = importlib.import_module(module_name)
111
+
112
+ for attr_name in dir(module):
113
+ attr = getattr(module, attr_name)
114
+ # Register decorated functions
115
+ if hasattr(attr, AUTO_GPT_COMMAND_IDENTIFIER) and getattr(
116
+ attr, AUTO_GPT_COMMAND_IDENTIFIER
117
+ ):
118
+ self.register(attr.command)
119
+ # Register command classes
120
+ elif (
121
+ inspect.isclass(attr) and issubclass(attr, Command) and attr != Command
122
+ ):
123
+ cmd_instance = attr()
124
+ self.register(cmd_instance)
125
+
126
+
127
+ def command(
128
+ name: str,
129
+ description: str,
130
+ signature: str = "",
131
+ enabled: bool = True,
132
+ disabled_reason: Optional[str] = None,
133
+ ) -> Callable[..., Any]:
134
+ """The command decorator is used to create Command objects from ordinary functions."""
135
+
136
+ def decorator(func: Callable[..., Any]) -> Command:
137
+ cmd = Command(
138
+ name=name,
139
+ description=description,
140
+ method=func,
141
+ signature=signature,
142
+ enabled=enabled,
143
+ disabled_reason=disabled_reason,
144
+ )
145
+
146
+ @functools.wraps(func)
147
+ def wrapper(*args, **kwargs) -> Any:
148
+ return func(*args, **kwargs)
149
+
150
+ wrapper.command = cmd
151
+
152
+ setattr(wrapper, AUTO_GPT_COMMAND_IDENTIFIER, True)
153
+
154
+ return wrapper
155
+
156
+ return decorator
autogpt/commands/execute_code.py CHANGED
@@ -1,36 +1,38 @@
1
  """Execute code in a Docker container"""
2
  import os
3
  import subprocess
 
4
 
5
  import docker
6
  from docker.errors import ImageNotFound
7
 
8
- from autogpt.workspace import WORKSPACE_PATH, path_in_workspace
 
 
9
 
 
10
 
11
- def execute_python_file(file: str) -> str:
 
12
  """Execute a Python file in a Docker container and return the output
13
 
14
  Args:
15
- file (str): The name of the file to execute
16
 
17
  Returns:
18
  str: The output of the file
19
  """
 
20
 
21
- print(f"Executing file '{file}' in workspace '{WORKSPACE_PATH}'")
22
-
23
- if not file.endswith(".py"):
24
  return "Error: Invalid file type. Only .py files are allowed."
25
 
26
- file_path = path_in_workspace(file)
27
-
28
- if not os.path.isfile(file_path):
29
- return f"Error: File '{file}' does not exist."
30
 
31
  if we_are_running_in_a_docker_container():
32
  result = subprocess.run(
33
- f"python {file_path}", capture_output=True, encoding="utf8", shell=True
34
  )
35
  if result.returncode == 0:
36
  return result.stdout
@@ -39,16 +41,17 @@ def execute_python_file(file: str) -> str:
39
 
40
  try:
41
  client = docker.from_env()
42
-
43
  # You can replace this with the desired Python image/version
44
  # You can find available Python images on Docker Hub:
45
  # https://hub.docker.com/_/python
46
  image_name = "python:3-alpine"
47
  try:
48
  client.images.get(image_name)
49
- print(f"Image '{image_name}' found locally")
50
  except ImageNotFound:
51
- print(f"Image '{image_name}' not found locally, pulling from Docker Hub")
 
 
52
  # Use the low-level API to stream the pull response
53
  low_level_client = docker.APIClient()
54
  for line in low_level_client.pull(image_name, stream=True, decode=True):
@@ -56,15 +59,14 @@ def execute_python_file(file: str) -> str:
56
  status = line.get("status")
57
  progress = line.get("progress")
58
  if status and progress:
59
- print(f"{status}: {progress}")
60
  elif status:
61
- print(status)
62
-
63
  container = client.containers.run(
64
  image_name,
65
- f"python {file}",
66
  volumes={
67
- os.path.abspath(WORKSPACE_PATH): {
68
  "bind": "/workspace",
69
  "mode": "ro",
70
  }
@@ -85,7 +87,7 @@ def execute_python_file(file: str) -> str:
85
  return logs
86
 
87
  except docker.errors.DockerException as e:
88
- print(
89
  "Could not run the script in a container. If you haven't already, please install Docker https://docs.docker.com/get-docker/"
90
  )
91
  return f"Error: {str(e)}"
@@ -94,6 +96,15 @@ def execute_python_file(file: str) -> str:
94
  return f"Error: {str(e)}"
95
 
96
 
 
 
 
 
 
 
 
 
 
97
  def execute_shell(command_line: str) -> str:
98
  """Execute a shell command and return the output
99
 
@@ -103,12 +114,15 @@ def execute_shell(command_line: str) -> str:
103
  Returns:
104
  str: The output of the command
105
  """
106
- current_dir = os.getcwd()
 
107
  # Change dir into workspace if necessary
108
- if str(WORKSPACE_PATH) not in current_dir:
109
- os.chdir(WORKSPACE_PATH)
110
 
111
- print(f"Executing command '{command_line}' in working directory '{os.getcwd()}'")
 
 
112
 
113
  result = subprocess.run(command_line, capture_output=True, shell=True)
114
  output = f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
@@ -116,10 +130,18 @@ def execute_shell(command_line: str) -> str:
116
  # Change back to whatever the prior working dir was
117
 
118
  os.chdir(current_dir)
119
-
120
  return output
121
 
122
 
 
 
 
 
 
 
 
 
 
123
  def execute_shell_popen(command_line) -> str:
124
  """Execute a shell command with Popen and returns an english description
125
  of the event and the process id
@@ -130,12 +152,15 @@ def execute_shell_popen(command_line) -> str:
130
  Returns:
131
  str: Description of the fact that the process started and its id
132
  """
 
133
  current_dir = os.getcwd()
134
  # Change dir into workspace if necessary
135
- if str(WORKSPACE_PATH) not in current_dir:
136
- os.chdir(WORKSPACE_PATH)
137
 
138
- print(f"Executing command '{command_line}' in working directory '{os.getcwd()}'")
 
 
139
 
140
  do_not_show_output = subprocess.DEVNULL
141
  process = subprocess.Popen(
 
1
  """Execute code in a Docker container"""
2
  import os
3
  import subprocess
4
+ from pathlib import Path
5
 
6
  import docker
7
  from docker.errors import ImageNotFound
8
 
9
+ from autogpt.commands.command import command
10
+ from autogpt.config import Config
11
+ from autogpt.logs import logger
12
 
13
+ CFG = Config()
14
 
15
+ @command("execute_python_file", "Execute Python File", '"filename": "<filename>"')
16
+ def execute_python_file(filename: str) -> str:
17
  """Execute a Python file in a Docker container and return the output
18
 
19
  Args:
20
+ filename (str): The name of the file to execute
21
 
22
  Returns:
23
  str: The output of the file
24
  """
25
+ logger.info(f"Executing file '{filename}'")
26
 
27
+ if not filename.endswith(".py"):
 
 
28
  return "Error: Invalid file type. Only .py files are allowed."
29
 
30
+ if not os.path.isfile(filename):
31
+ return f"Error: File '{filename}' does not exist."
 
 
32
 
33
  if we_are_running_in_a_docker_container():
34
  result = subprocess.run(
35
+ f"python {filename}", capture_output=True, encoding="utf8", shell=True
36
  )
37
  if result.returncode == 0:
38
  return result.stdout
 
41
 
42
  try:
43
  client = docker.from_env()
 
44
  # You can replace this with the desired Python image/version
45
  # You can find available Python images on Docker Hub:
46
  # https://hub.docker.com/_/python
47
  image_name = "python:3-alpine"
48
  try:
49
  client.images.get(image_name)
50
+ logger.warn(f"Image '{image_name}' found locally")
51
  except ImageNotFound:
52
+ logger.info(
53
+ f"Image '{image_name}' not found locally, pulling from Docker Hub"
54
+ )
55
  # Use the low-level API to stream the pull response
56
  low_level_client = docker.APIClient()
57
  for line in low_level_client.pull(image_name, stream=True, decode=True):
 
59
  status = line.get("status")
60
  progress = line.get("progress")
61
  if status and progress:
62
+ logger.info(f"{status}: {progress}")
63
  elif status:
64
+ logger.info(status)
 
65
  container = client.containers.run(
66
  image_name,
67
+ f"python {Path(filename).relative_to(CFG.workspace_path)}",
68
  volumes={
69
+ CFG.workspace_path: {
70
  "bind": "/workspace",
71
  "mode": "ro",
72
  }
 
87
  return logs
88
 
89
  except docker.errors.DockerException as e:
90
+ logger.warn(
91
  "Could not run the script in a container. If you haven't already, please install Docker https://docs.docker.com/get-docker/"
92
  )
93
  return f"Error: {str(e)}"
 
96
  return f"Error: {str(e)}"
97
 
98
 
99
+ @command(
100
+ "execute_shell",
101
+ "Execute Shell Command, non-interactive commands only",
102
+ '"command_line": "<command_line>"',
103
+ CFG.execute_local_commands,
104
+ "You are not allowed to run local shell commands. To execute"
105
+ " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' "
106
+ "in your config. Do not attempt to bypass the restriction.",
107
+ )
108
  def execute_shell(command_line: str) -> str:
109
  """Execute a shell command and return the output
110
 
 
114
  Returns:
115
  str: The output of the command
116
  """
117
+
118
+ current_dir = Path.cwd()
119
  # Change dir into workspace if necessary
120
+ if not current_dir.is_relative_to(CFG.workspace_path):
121
+ os.chdir(CFG.workspace_path)
122
 
123
+ logger.info(
124
+ f"Executing command '{command_line}' in working directory '{os.getcwd()}'"
125
+ )
126
 
127
  result = subprocess.run(command_line, capture_output=True, shell=True)
128
  output = f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
 
130
  # Change back to whatever the prior working dir was
131
 
132
  os.chdir(current_dir)
 
133
  return output
134
 
135
 
136
+ @command(
137
+ "execute_shell_popen",
138
+ "Execute Shell Command, non-interactive commands only",
139
+ '"command_line": "<command_line>"',
140
+ CFG.execute_local_commands,
141
+ "You are not allowed to run local shell commands. To execute"
142
+ " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' "
143
+ "in your config. Do not attempt to bypass the restriction.",
144
+ )
145
  def execute_shell_popen(command_line) -> str:
146
  """Execute a shell command with Popen and returns an english description
147
  of the event and the process id
 
152
  Returns:
153
  str: Description of the fact that the process started and its id
154
  """
155
+
156
  current_dir = os.getcwd()
157
  # Change dir into workspace if necessary
158
+ if CFG.workspace_path not in current_dir:
159
+ os.chdir(CFG.workspace_path)
160
 
161
+ logger.info(
162
+ f"Executing command '{command_line}' in working directory '{os.getcwd()}'"
163
+ )
164
 
165
  do_not_show_output = subprocess.DEVNULL
166
  process = subprocess.Popen(
autogpt/commands/file_operations.py CHANGED
@@ -9,12 +9,13 @@ import requests
9
  from colorama import Back, Fore
10
  from requests.adapters import HTTPAdapter, Retry
11
 
 
 
 
12
  from autogpt.spinner import Spinner
13
  from autogpt.utils import readable_file_size
14
- from autogpt.workspace import WORKSPACE_PATH, path_in_workspace
15
 
16
- LOG_FILE = "file_logger.txt"
17
- LOG_FILE_PATH = WORKSPACE_PATH / LOG_FILE
18
 
19
 
20
  def check_duplicate_operation(operation: str, filename: str) -> bool:
@@ -27,7 +28,7 @@ def check_duplicate_operation(operation: str, filename: str) -> bool:
27
  Returns:
28
  bool: True if the operation has already been performed on the file
29
  """
30
- log_content = read_file(LOG_FILE)
31
  log_entry = f"{operation}: {filename}\n"
32
  return log_entry in log_content
33
 
@@ -40,13 +41,7 @@ def log_operation(operation: str, filename: str) -> None:
40
  filename (str): The name of the file the operation was performed on
41
  """
42
  log_entry = f"{operation}: {filename}\n"
43
-
44
- # Create the log file if it doesn't exist
45
- if not os.path.exists(LOG_FILE_PATH):
46
- with open(LOG_FILE_PATH, "w", encoding="utf-8") as f:
47
- f.write("File Operation Logger ")
48
-
49
- append_to_file(LOG_FILE, log_entry, shouldLog=False)
50
 
51
 
52
  def split_file(
@@ -81,6 +76,7 @@ def split_file(
81
  start += max_length - overlap
82
 
83
 
 
84
  def read_file(filename: str) -> str:
85
  """Read a file and return the contents
86
 
@@ -91,8 +87,7 @@ def read_file(filename: str) -> str:
91
  str: The contents of the file
92
  """
93
  try:
94
- filepath = path_in_workspace(filename)
95
- with open(filepath, "r", encoding="utf-8") as f:
96
  content = f.read()
97
  return content
98
  except Exception as e:
@@ -112,27 +107,28 @@ def ingest_file(
112
  :param overlap: The number of overlapping characters between chunks, default is 200
113
  """
114
  try:
115
- print(f"Working with file {filename}")
116
  content = read_file(filename)
117
  content_length = len(content)
118
- print(f"File length: {content_length} characters")
119
 
120
  chunks = list(split_file(content, max_length=max_length, overlap=overlap))
121
 
122
  num_chunks = len(chunks)
123
  for i, chunk in enumerate(chunks):
124
- print(f"Ingesting chunk {i + 1} / {num_chunks} into memory")
125
  memory_to_add = (
126
  f"Filename: {filename}\n" f"Content part#{i + 1}/{num_chunks}: {chunk}"
127
  )
128
 
129
  memory.add(memory_to_add)
130
 
131
- print(f"Done ingesting {num_chunks} chunks from {filename}.")
132
  except Exception as e:
133
- print(f"Error while ingesting file '{filename}': {str(e)}")
134
 
135
 
 
136
  def write_to_file(filename: str, text: str) -> str:
137
  """Write text to a file
138
 
@@ -146,11 +142,9 @@ def write_to_file(filename: str, text: str) -> str:
146
  if check_duplicate_operation("write", filename):
147
  return "Error: File has already been updated."
148
  try:
149
- filepath = path_in_workspace(filename)
150
- directory = os.path.dirname(filepath)
151
- if not os.path.exists(directory):
152
- os.makedirs(directory)
153
- with open(filepath, "w", encoding="utf-8") as f:
154
  f.write(text)
155
  log_operation("write", filename)
156
  return "File written to successfully."
@@ -158,22 +152,27 @@ def write_to_file(filename: str, text: str) -> str:
158
  return f"Error: {str(e)}"
159
 
160
 
161
- def append_to_file(filename: str, text: str, shouldLog: bool = True) -> str:
 
 
 
162
  """Append text to a file
163
 
164
  Args:
165
  filename (str): The name of the file to append to
166
  text (str): The text to append to the file
 
167
 
168
  Returns:
169
  str: A message indicating success or failure
170
  """
171
  try:
172
- filepath = path_in_workspace(filename)
173
- with open(filepath, "a") as f:
 
174
  f.write(text)
175
 
176
- if shouldLog:
177
  log_operation("append", filename)
178
 
179
  return "Text appended successfully."
@@ -181,6 +180,7 @@ def append_to_file(filename: str, text: str, shouldLog: bool = True) -> str:
181
  return f"Error: {str(e)}"
182
 
183
 
 
184
  def delete_file(filename: str) -> str:
185
  """Delete a file
186
 
@@ -193,14 +193,14 @@ def delete_file(filename: str) -> str:
193
  if check_duplicate_operation("delete", filename):
194
  return "Error: File has already been deleted."
195
  try:
196
- filepath = path_in_workspace(filename)
197
- os.remove(filepath)
198
  log_operation("delete", filename)
199
  return "File deleted successfully."
200
  except Exception as e:
201
  return f"Error: {str(e)}"
202
 
203
 
 
204
  def search_files(directory: str) -> list[str]:
205
  """Search for files in a directory
206
 
@@ -212,29 +212,34 @@ def search_files(directory: str) -> list[str]:
212
  """
213
  found_files = []
214
 
215
- if directory in {"", "/"}:
216
- search_directory = WORKSPACE_PATH
217
- else:
218
- search_directory = path_in_workspace(directory)
219
-
220
- for root, _, files in os.walk(search_directory):
221
  for file in files:
222
  if file.startswith("."):
223
  continue
224
- relative_path = os.path.relpath(os.path.join(root, file), WORKSPACE_PATH)
 
 
225
  found_files.append(relative_path)
226
 
227
  return found_files
228
 
229
 
 
 
 
 
 
 
 
230
  def download_file(url, filename):
231
  """Downloads a file
232
  Args:
233
  url (str): URL of the file to download
234
  filename (str): Filename to save the file as
235
  """
236
- safe_filename = path_in_workspace(filename)
237
  try:
 
 
238
  message = f"{Fore.YELLOW}Downloading file from {Back.LIGHTBLUE_EX}{url}{Back.RESET}{Fore.RESET}"
239
  with Spinner(message) as spinner:
240
  session = requests.Session()
@@ -251,7 +256,7 @@ def download_file(url, filename):
251
  total_size = int(r.headers.get("Content-Length", 0))
252
  downloaded_size = 0
253
 
254
- with open(safe_filename, "wb") as f:
255
  for chunk in r.iter_content(chunk_size=8192):
256
  f.write(chunk)
257
  downloaded_size += len(chunk)
@@ -260,7 +265,7 @@ def download_file(url, filename):
260
  progress = f"{readable_file_size(downloaded_size)} / {readable_file_size(total_size)}"
261
  spinner.update_message(f"{message} {progress}")
262
 
263
- return f'Successfully downloaded and locally stored file: "{filename}"! (Size: {readable_file_size(total_size)})'
264
  except requests.HTTPError as e:
265
  return f"Got an HTTP Error whilst trying to download file: {e}"
266
  except Exception as e:
 
9
  from colorama import Back, Fore
10
  from requests.adapters import HTTPAdapter, Retry
11
 
12
+ from autogpt.commands.command import command
13
+ from autogpt.config import Config
14
+ from autogpt.logs import logger
15
  from autogpt.spinner import Spinner
16
  from autogpt.utils import readable_file_size
 
17
 
18
+ CFG = Config()
 
19
 
20
 
21
  def check_duplicate_operation(operation: str, filename: str) -> bool:
 
28
  Returns:
29
  bool: True if the operation has already been performed on the file
30
  """
31
+ log_content = read_file(CFG.file_logger_path)
32
  log_entry = f"{operation}: {filename}\n"
33
  return log_entry in log_content
34
 
 
41
  filename (str): The name of the file the operation was performed on
42
  """
43
  log_entry = f"{operation}: {filename}\n"
44
+ append_to_file(CFG.file_logger_path, log_entry, should_log=False)
 
 
 
 
 
 
45
 
46
 
47
  def split_file(
 
76
  start += max_length - overlap
77
 
78
 
79
+ @command("read_file", "Read file", '"filename": "<filename>"')
80
  def read_file(filename: str) -> str:
81
  """Read a file and return the contents
82
 
 
87
  str: The contents of the file
88
  """
89
  try:
90
+ with open(filename, "r", encoding="utf-8") as f:
 
91
  content = f.read()
92
  return content
93
  except Exception as e:
 
107
  :param overlap: The number of overlapping characters between chunks, default is 200
108
  """
109
  try:
110
+ logger.info(f"Working with file {filename}")
111
  content = read_file(filename)
112
  content_length = len(content)
113
+ logger.info(f"File length: {content_length} characters")
114
 
115
  chunks = list(split_file(content, max_length=max_length, overlap=overlap))
116
 
117
  num_chunks = len(chunks)
118
  for i, chunk in enumerate(chunks):
119
+ logger.info(f"Ingesting chunk {i + 1} / {num_chunks} into memory")
120
  memory_to_add = (
121
  f"Filename: {filename}\n" f"Content part#{i + 1}/{num_chunks}: {chunk}"
122
  )
123
 
124
  memory.add(memory_to_add)
125
 
126
+ logger.info(f"Done ingesting {num_chunks} chunks from {filename}.")
127
  except Exception as e:
128
+ logger.info(f"Error while ingesting file '{filename}': {str(e)}")
129
 
130
 
131
+ @command("write_to_file", "Write to file", '"filename": "<filename>", "text": "<text>"')
132
  def write_to_file(filename: str, text: str) -> str:
133
  """Write text to a file
134
 
 
142
  if check_duplicate_operation("write", filename):
143
  return "Error: File has already been updated."
144
  try:
145
+ directory = os.path.dirname(filename)
146
+ os.makedirs(directory, exist_ok=True)
147
+ with open(filename, "w", encoding="utf-8") as f:
 
 
148
  f.write(text)
149
  log_operation("write", filename)
150
  return "File written to successfully."
 
152
  return f"Error: {str(e)}"
153
 
154
 
155
+ @command(
156
+ "append_to_file", "Append to file", '"filename": "<filename>", "text": "<text>"'
157
+ )
158
+ def append_to_file(filename: str, text: str, should_log: bool = True) -> str:
159
  """Append text to a file
160
 
161
  Args:
162
  filename (str): The name of the file to append to
163
  text (str): The text to append to the file
164
+ should_log (bool): Should log output
165
 
166
  Returns:
167
  str: A message indicating success or failure
168
  """
169
  try:
170
+ directory = os.path.dirname(filename)
171
+ os.makedirs(directory, exist_ok=True)
172
+ with open(filename, "a") as f:
173
  f.write(text)
174
 
175
+ if should_log:
176
  log_operation("append", filename)
177
 
178
  return "Text appended successfully."
 
180
  return f"Error: {str(e)}"
181
 
182
 
183
+ @command("delete_file", "Delete file", '"filename": "<filename>"')
184
  def delete_file(filename: str) -> str:
185
  """Delete a file
186
 
 
193
  if check_duplicate_operation("delete", filename):
194
  return "Error: File has already been deleted."
195
  try:
196
+ os.remove(filename)
 
197
  log_operation("delete", filename)
198
  return "File deleted successfully."
199
  except Exception as e:
200
  return f"Error: {str(e)}"
201
 
202
 
203
+ @command("search_files", "Search Files", '"directory": "<directory>"')
204
  def search_files(directory: str) -> list[str]:
205
  """Search for files in a directory
206
 
 
212
  """
213
  found_files = []
214
 
215
+ for root, _, files in os.walk(directory):
 
 
 
 
 
216
  for file in files:
217
  if file.startswith("."):
218
  continue
219
+ relative_path = os.path.relpath(
220
+ os.path.join(root, file), CFG.workspace_path
221
+ )
222
  found_files.append(relative_path)
223
 
224
  return found_files
225
 
226
 
227
+ @command(
228
+ "download_file",
229
+ "Download File",
230
+ '"url": "<url>", "filename": "<filename>"',
231
+ CFG.allow_downloads,
232
+ "Error: You do not have user authorization to download files locally.",
233
+ )
234
  def download_file(url, filename):
235
  """Downloads a file
236
  Args:
237
  url (str): URL of the file to download
238
  filename (str): Filename to save the file as
239
  """
 
240
  try:
241
+ directory = os.path.dirname(filename)
242
+ os.makedirs(directory, exist_ok=True)
243
  message = f"{Fore.YELLOW}Downloading file from {Back.LIGHTBLUE_EX}{url}{Back.RESET}{Fore.RESET}"
244
  with Spinner(message) as spinner:
245
  session = requests.Session()
 
256
  total_size = int(r.headers.get("Content-Length", 0))
257
  downloaded_size = 0
258
 
259
+ with open(filename, "wb") as f:
260
  for chunk in r.iter_content(chunk_size=8192):
261
  f.write(chunk)
262
  downloaded_size += len(chunk)
 
265
  progress = f"{readable_file_size(downloaded_size)} / {readable_file_size(total_size)}"
266
  spinner.update_message(f"{message} {progress}")
267
 
268
+ return f'Successfully downloaded and locally stored file: "{filename}"! (Size: {readable_file_size(downloaded_size)})'
269
  except requests.HTTPError as e:
270
  return f"Got an HTTP Error whilst trying to download file: {e}"
271
  except Exception as e:
plugins/Auto-GPT-Plugins.zip ADDED
Binary file (255 kB). View file
 
plugins/__PUT_PLUGIN_ZIPS_HERE__ ADDED
File without changes
scripts/check_requirements.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import sys
2
 
3
  import pkg_resources
@@ -16,7 +17,7 @@ def main():
16
  for package in required_packages:
17
  if not package: # Skip empty lines
18
  continue
19
- package_name = package.strip().split("==")[0]
20
  if package_name.lower() not in installed_packages:
21
  missing_packages.append(package_name)
22
 
 
1
+ import re
2
  import sys
3
 
4
  import pkg_resources
 
17
  for package in required_packages:
18
  if not package: # Skip empty lines
19
  continue
20
+ package_name = re.split("[<>=@ ]+", package.strip())[0]
21
  if package_name.lower() not in installed_packages:
22
  missing_packages.append(package_name)
23
 
scripts/install_plugin_deps.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import zipfile
5
+ from pathlib import Path
6
+
7
+
8
+ def install_plugin_dependencies():
9
+ """
10
+ Installs dependencies for all plugins in the plugins dir.
11
+
12
+ Args:
13
+ None
14
+
15
+ Returns:
16
+ None
17
+ """
18
+ plugins_dir = Path(os.getenv("PLUGINS_DIR", "plugins"))
19
+ for plugin in plugins_dir.glob("*.zip"):
20
+ with zipfile.ZipFile(str(plugin), "r") as zfile:
21
+ try:
22
+ basedir = zfile.namelist()[0]
23
+ basereqs = os.path.join(basedir, "requirements.txt")
24
+ extracted = zfile.extract(basereqs, path=plugins_dir)
25
+ subprocess.check_call(
26
+ [sys.executable, "-m", "pip", "install", "-r", extracted]
27
+ )
28
+ os.remove(extracted)
29
+ os.rmdir(os.path.join(plugins_dir, basedir))
30
+ except KeyError:
31
+ continue
32
+
33
+
34
+ if __name__ == "__main__":
35
+ install_plugin_dependencies()