Docker + SSH, AgentConfig class
frdel committed
Jul 5, 2024 at 22:58 UTC
b74d05d6c2bf278537bd1388f399d4075fcdf5a6
27 files changed
+422
-146
agent.py
+54
-48
@@ -1,58 +1,60 @@
1
+from dataclasses import dataclass, field
2
import time, importlib, inspect, os, json
3
import traceback
3
-from typing import Optional, Dict, TypedDict
4
-from tools.helpers import extract_tools, rate_limiter, files, errors
5
-from tools.helpers.print_style import PrintStyle
4
+from typing import Any, Optional, Dict, TypedDict
5
+from python.helpers import extract_tools, rate_limiter, files, errors
6
+from python.helpers.print_style import PrintStyle
7
from langchain.schema import AIMessage
8
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
9
from langchain_core.messages import HumanMessage, SystemMessage
10
from langchain_core.language_models.chat_models import BaseChatModel
11
from langchain_core.embeddings import Embeddings
11
-from tools.helpers.rate_limiter import RateLimiter
12
+from python.helpers.rate_limiter import RateLimiter
13
14
# rate_limit = rate_limiter.rate_limiter(30,160000) #TODO! implement properly
15
16
+@dataclass
17
+class AgentConfig:
18
+ agent_number: int
19
+ chat_model:BaseChatModel
20
+ embeddings_model:Embeddings
21
+ memory_subdir: str = ""
22
+ auto_memory_count: int = 3
23
+ auto_memory_skip: int = 2
24
+ rate_limit_seconds: int = 60
25
+ rate_limit_requests: int = 30
26
+ rate_limit_input_tokens: int = 0
27
+ rate_limit_output_tokens: int = 0
28
+ msgs_keep_max: int = 25
29
+ msgs_keep_start: int = 5
30
+ msgs_keep_end: int = 10
31
+ max_tool_response_length: int = 3000
32
+ code_exec_docker_enabled: bool = True
33
+ code_exec_docker_name: str = "agent-zero-exe"
34
+ code_exec_docker_image: str = "frdel/agent-zero-exe:latest"
35
+ code_exec_docker_ports: dict[str,int] = field(default_factory=lambda: {"22/tcp": 50022})
36
+ code_exec_docker_volumes: dict[str, dict[str, str]] = field(default_factory=lambda: {files.get_abs_path("work_dir"): {"bind": "/root", "mode": "rw"}})
37
+ code_exec_ssh_enabled: bool = True
38
+ code_exec_ssh_addr: str = "localhost"
39
+ code_exec_ssh_port: int = 50022
40
+ code_exec_ssh_user: str = "root"
41
+ code_exec_ssh_pass: str = "toor"
42
+ additional: Dict[str, Any] = field(default_factory=dict)
43
+
44
45
class Agent:
46
47
paused=False
48
streaming_agent=None
49
21
- def __init__(self,
22
- agent_number: int,
23
- chat_model:BaseChatModel,
24
- embeddings_model:Embeddings,
25
- memory_subdir: str = "",
26
- auto_memory_count: int = 3,
27
- auto_memory_skip: int = 2,
28
- rate_limit_seconds: int = 60,
29
- rate_limit_requests: int = 30,
30
- rate_limit_input_tokens: int = 0,
31
- rate_limit_output_tokens: int = 0,
32
- msgs_keep_max: int = 25,
33
- msgs_keep_start: int = 5,
34
- msgs_keep_end: int = 10,
35
- max_tool_response_length: int = 3000,
36
- **kwargs):
37
-
38
- # agent config
39
- self.agent_number = agent_number
40
- self.chat_model = chat_model
41
- self.embeddings_model = embeddings_model
42
- self.memory_subdir = memory_subdir
43
- self.auto_memory_count = auto_memory_count
44
- self.auto_memory_skip = auto_memory_skip
45
- self.rate_limit_seconds = rate_limit_seconds
46
- self.rate_limit_requests = rate_limit_requests
47
- self.rate_limit_input_tokens = rate_limit_input_tokens
48
- self.rate_limit_output_tokens = rate_limit_output_tokens
49
- self.msgs_keep_max = msgs_keep_max
50
- self.msgs_keep_start = msgs_keep_start
51
- self.msgs_keep_end = msgs_keep_end
52
- self.max_tool_response_length = max_tool_response_length
50
+ def __init__(self, number:int, config: AgentConfig):
51
+
52
+ # agent config
53
+ self.config = config
54
55
# non-config vars
55
- self.agent_name = f"Agent {self.agent_number}"
56
+ self.number = number
57
+ self.agent_name = f"Agent {self.config.agent_number}"
58
59
self.system_prompt = files.read_file("./prompts/agent.system.md").replace("{", "{{").replace("}", "}}")
60
self.tools_prompt = files.read_file("./prompts/agent.tools.md").replace("{", "{{").replace("}", "}}")
@@ -61,7 +63,7 @@ class Agent:
63
self.last_message = ""
64
self.intervention_message = ""
65
self.intervention_status = False
64
- self.rate_limiter = RateLimiter(max_calls=rate_limit_requests,max_input_tokens=rate_limit_input_tokens,max_output_tokens=rate_limit_output_tokens,window_seconds=rate_limit_seconds)
66
+ self.rate_limiter = RateLimiter(max_calls=self.config.rate_limit_requests,max_input_tokens=self.config.rate_limit_input_tokens,max_output_tokens=self.config.rate_limit_output_tokens,window_seconds=self.config.rate_limit_seconds)
67
self.data = {} # free data object all the tools can use
68
69
os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
@@ -90,7 +92,7 @@ class Agent:
92
MessagesPlaceholder(variable_name="messages") ])
93
94
inputs = {"messages": self.history}
93
- chain = prompt | self.chat_model
95
+ chain = prompt | self.config.chat_model
96
97
formatted_inputs = prompt.format(messages=self.history)
98
tokens = int(len(formatted_inputs)/4)
@@ -147,7 +149,7 @@ class Agent:
149
else:
150
new_message = HumanMessage(content=msg) if human else AIMessage(content=msg)
151
self.history.append(new_message)
150
- self.cleanup_history(self.msgs_keep_max, self.msgs_keep_start, self.msgs_keep_end)
152
+ self.cleanup_history(self.config.msgs_keep_max, self.config.msgs_keep_start, self.config.msgs_keep_end)
153
if message_type=="ai":
154
self.last_message = msg
155
@@ -159,7 +161,7 @@ class Agent:
161
SystemMessage(content=system),
162
HumanMessage(content=msg)])
163
162
- chain = prompt | self.chat_model
164
+ chain = prompt | self.config.chat_model
165
response = ""
166
printer = None
167
@@ -251,12 +253,12 @@ class Agent:
253
254
255
def get_tool(self, name: str, args: dict, message: str, **kwargs):
254
- from tools.unknown import Unknown
255
- from tools.helpers.tool import Tool
256
+ from python.tools.unknown import Unknown
257
+ from python.helpers.tool import Tool
258
259
tool_class = Unknown
258
- if files.exists("tools",f"{name}.py"):
259
- module = importlib.import_module("tools." + name) # Import the module
260
+ if files.exists("python/tools",f"{name}.py"):
261
+ module = importlib.import_module("python.tools." + name) # Import the module
262
class_list = inspect.getmembers(module, inspect.isclass) # Get all functions in the module
263
264
for cls in class_list:
@@ -267,14 +269,15 @@ class Agent:
269
return tool_class(agent=self, name=name, args=args, message=message, **kwargs)
270
271
def fetch_memories(self,reset_skip=False):
272
+ if self.config.auto_memory_count<=0: return ""
273
if reset_skip: self.memory_skip_counter = 0
274
275
if self.memory_skip_counter > 0:
276
self.memory_skip_counter-=1
277
return ""
278
else:
276
- self.memory_skip_counter = self.auto_memory_skip
277
- from tools import memory_tool
279
+ self.memory_skip_counter = self.config.auto_memory_skip
280
+ from python.tools import memory_tool
281
messages = self.concat_messages(self.history)
282
memories = memory_tool.process_query(self,messages,"load")
283
input = {
@@ -283,4 +286,7 @@ class Agent:
286
}
287
cleanup_prompt = files.read_file("./prompts/msg.memory_cleanup.md").replace("{", "{{")
288
clean_memories = self.send_adhoc_message(cleanup_prompt,json.dumps(input), output_label="Memory injection")
286
- return clean_memories
\ No newline at end of file
289
+ return clean_memories
290
+
291
+ def call_extension(self, name: str, **kwargs) -> Any:
292
+ pass
\ No newline at end of file
main.py
+48
-27
@@ -1,21 +1,20 @@
1
-import threading, sys, time, readline, models, os
1
+import threading, time, models, os
2
from ansio import application_keypad, mouse_input, raw_input
3
from ansio.input import InputEvent, get_input_event
4
-from agent import Agent
5
-from tools.helpers.print_style import PrintStyle
6
-from tools.helpers.files import read_file
4
+from agent import Agent, AgentConfig
5
+from python.helpers.print_style import PrintStyle
6
+from python.helpers.files import read_file
7
from pytimedinput import timedInput as timed_input
8
-from tools.helpers import files
8
+from python.helpers import files
9
10
11
input_lock = threading.Lock()
12
13
os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
14
15
-# Main conversation loop
16
-def chat():
15
18
- # chat model used for agents
16
+def initialize():
17
+ # chat model used by agents
18
# chat_llm = models.get_groq_llama70b(temperature=0.2)
19
# chat_llm = models.get_groq_llama70b_json(temperature=0.2)
20
# chat_llm = models.get_groq_llama8b(temperature=0.2)
@@ -30,29 +29,51 @@ def chat():
29
# embedding model used for memory
30
# embedding_llm = models.get_embedding_openai()
31
embedding_llm = models.get_embedding_hf()
32
+
33
+ # agent configuration
34
+ config = AgentConfig(
35
+ agent_number = 0,
36
+ chat_model = chat_llm,
37
+ embeddings_model = embedding_llm,
38
+ # memory_subdir = "",
39
+ auto_memory_count = 0,
40
+ # auto_memory_skip = 2,
41
+ # rate_limit_seconds = 60,
42
+ # rate_limit_requests = 30,
43
+ # rate_limit_input_tokens = 0,
44
+ # rate_limit_output_tokens = 0,
45
+ # msgs_keep_max = 25,
46
+ # msgs_keep_start = 5,
47
+ # msgs_keep_end = 10,
48
+ # max_tool_response_length = 3000,
49
+ code_exec_docker_enabled = True,
50
+ # code_exec_docker_name = "agent-zero-exe",
51
+ # code_exec_docker_image = "frdel/agent-zero-exe:latest",
52
+ # code_exec_docker_ports = { "22/tcp": 50022 }
53
+ # code_exec_docker_volumes = { files.get_abs_path("work_dir"): {"bind": "/root", "mode": "rw"} }
54
+ code_exec_ssh_enabled = True,
55
+ # code_exec_ssh_addr = "localhost",
56
+ # code_exec_ssh_port = 50022,
57
+ # code_exec_ssh_user = "root",
58
+ # code_exec_ssh_pass = "toor",
59
+ # additional = {},
60
+ )
61
62
# create the first agent
35
- agent0 = Agent( agent_number=0,
36
- chat_model=chat_llm,
37
- embeddings_model=embedding_llm,
38
- # memory_subdir = "",
39
- # auto_memory_count = 3,
40
- # auto_memory_skip = 2,
41
- # rate_limit_seconds = 60,
42
- rate_limit_requests = 30,
43
- rate_limit_input_tokens = 160000,
44
- rate_limit_output_tokens = 8000,
45
- # msgs_keep_max = 25,
46
- # msgs_keep_start = 5,
47
- # msgs_keep_end = 10,
48
- # max_tool_response_length = 3000,
49
- )
63
+ agent0 = Agent( number = 0, config = config )
64
+
65
+ # start the chat loop
66
+ chat(agent0)
67
68
+
69
+# Main conversation loop
70
+def chat(agent:Agent):
71
+
72
# start the conversation loop
73
while True:
74
# ask user for message
75
with input_lock:
55
- timeout = agent0.get_data("timeout") # how long the agent is willing to wait
76
+ timeout = agent.get_data("timeout") # how long the agent is willing to wait
77
if not timeout: # if agent wants to wait for user input forever
78
PrintStyle(background_color="#6C3483", font_color="white", bold=True, padding=True).print(f"User message ('exit' to leave):")
79
user_input = input("> ")
@@ -77,10 +98,10 @@ def chat():
98
if user_input.lower() == 'exit': break
99
100
# send message to agent0,
80
- assistant_response = agent0.message_loop(user_input)
101
+ assistant_response = agent.message_loop(user_input)
102
103
# print agent0 response
83
- PrintStyle(font_color="white",background_color="#1D8348", bold=True, padding=True).print(f"{agent0.agent_name}: reponse:")
104
+ PrintStyle(font_color="white",background_color="#1D8348", bold=True, padding=True).print(f"{agent.agent_name}: reponse:")
105
PrintStyle(font_color="white").print(f"{assistant_response}")
106
107
@@ -123,4 +144,4 @@ if __name__ == "__main__":
144
threading.Thread(target=capture_keys, daemon=True).start()
145
146
# Start the chat
126
- chat()
\ No newline at end of file
147
+ initialize()
\ No newline at end of file
python/__init__.py
renamed
python/helpers/dirty_json.py
renamed
python/helpers/docker.py
new
+49
@@ -0,0 +1,49 @@
1
+import time
2
+import docker
3
+import atexit
4
+from typing import Dict, Optional
5
+from python.helpers.files import get_abs_path
6
+
7
+class DockerContainerManager:
8
+ def __init__(self, image:str, name:str, ports: Optional[Dict[str, int]] = None, volumes: Optional[Dict[str, Dict[str, str]]] = None):
9
+ self.client = docker.from_env()
10
+ self.image = image
11
+ self.name = name
12
+ self.ports = ports
13
+ self.volumes = volumes
14
+ self.container = None
15
+
16
+
17
+
18
+ def cleanup_container(self) -> None:
19
+ if self.container:
20
+ try:
21
+ self.container.stop()
22
+ self.container.remove()
23
+ print(f"Stopped and removed the container: {self.container.id}")
24
+ except Exception as e:
25
+ print(f"Failed to stop and remove the container: {e}")
26
+
27
+ def start_container(self) -> None:
28
+ existing_container = None
29
+ for container in self.client.containers.list():
30
+ if container.name == self.name:
31
+ existing_container = container
32
+ break
33
+
34
+ if existing_container:
35
+ #print(f"Container with name '{self.name}' is already running with ID: {existing_container.id}")
36
+ pass
37
+ else:
38
+ print(f"Initializing docker container {self.name}...")
39
+ self.container = self.client.containers.run(
40
+ self.image,
41
+ detach=True,
42
+ ports=self.ports,
43
+ name=self.name,
44
+ volumes=self.volumes,
45
+ )
46
+ atexit.register(self.cleanup_container)
47
+ print(f"Started container with ID: {self.container.id}")
48
+ time.sleep(1) # this helps to get SSH ready
49
+
python/helpers/errors.py
renamed
python/helpers/extract_tools.py
renamed
python/helpers/files.py
renamed
python/helpers/messages.py
renamed
python/helpers/perplexity_search.py
renamed
python/helpers/print_style.py
renamed
+5
@@ -1,4 +1,5 @@
1
import os, webcolors, html
2
+import sys
3
from datetime import datetime
4
from . import files
5
@@ -112,6 +113,10 @@ class PrintStyle:
113
self._log_html(html_text)
114
PrintStyle.last_endline = False
115
116
+ def is_last_line_empty(self):
117
+ lines = sys.stdin.readlines()
118
+ return bool(lines) and not lines[-1].strip()
119
+
120
# Ensure HTML file is closed properly when the program exits
121
import atexit
122
atexit.register(PrintStyle._close_html_log)
python/helpers/rate_limiter.py
renamed
python/helpers/shell_local.py
new
+68
@@ -0,0 +1,68 @@
1
+import select
2
+import subprocess
3
+import time
4
+import sys
5
+from typing import Optional, Tuple
6
+
7
+class LocalInteractiveSession:
8
+ def __init__(self):
9
+ self.process = None
10
+ self.full_output = ''
11
+
12
+ def connect(self):
13
+ # Start a new subprocess with the appropriate shell for the OS
14
+ if sys.platform.startswith('win'):
15
+ # Windows
16
+ self.process = subprocess.Popen(
17
+ ['cmd.exe'],
18
+ stdin=subprocess.PIPE,
19
+ stdout=subprocess.PIPE,
20
+ stderr=subprocess.PIPE,
21
+ text=True,
22
+ bufsize=1
23
+ )
24
+ else:
25
+ # macOS and Linux
26
+ self.process = subprocess.Popen(
27
+ ['/bin/bash'],
28
+ stdin=subprocess.PIPE,
29
+ stdout=subprocess.PIPE,
30
+ stderr=subprocess.PIPE,
31
+ text=True,
32
+ bufsize=1
33
+ )
34
+
35
+ def close(self):
36
+ if self.process:
37
+ self.process.terminate()
38
+ self.process.wait()
39
+
40
+ def send_command(self, command: str):
41
+ if not self.process:
42
+ raise Exception("Shell not connected")
43
+ self.full_output = ""
44
+ self.process.stdin.write(command + '\n') # type: ignore
45
+ self.process.stdin.flush() # type: ignore
46
+
47
+ def read_output(self) -> Tuple[str, Optional[str]]:
48
+ if not self.process:
49
+ raise Exception("Shell not connected")
50
+
51
+ partial_output = ''
52
+ while True:
53
+ rlist, _, _ = select.select([self.process.stdout], [], [], 0.1)
54
+ if rlist:
55
+ line = self.process.stdout.readline() # type: ignore
56
+ if line:
57
+ partial_output += line
58
+ self.full_output += line
59
+ time.sleep(0.1)
60
+ else:
61
+ break # No more output
62
+ else:
63
+ break # No data available
64
+
65
+ if not partial_output:
66
+ return self.full_output, None
67
+
68
+ return self.full_output, partial_output
\ No newline at end of file
python/helpers/shell_ssh.py
new
+71
@@ -0,0 +1,71 @@
1
+import paramiko
2
+import time
3
+import re
4
+from typing import Optional, Tuple
5
+
6
+class SSHInteractiveSession:
7
+ def __init__(self, hostname: str, port: int, username: str, password: str):
8
+ self.hostname = hostname
9
+ self.port = port
10
+ self.username = username
11
+ self.password = password
12
+ self.client = paramiko.SSHClient()
13
+ self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
14
+ self.shell = None
15
+ self.full_output = ''
16
+
17
+ def connect(self):
18
+ # try 3 times with wait and then except
19
+ errors = 0
20
+ while True:
21
+ try:
22
+ self.client.connect(self.hostname, self.port, self.username, self.password)
23
+ self.shell = self.client.invoke_shell()
24
+ while True: # wait for end of initial output
25
+ full, part = self.read_output()
26
+ if full and not part: return
27
+ time.sleep(0.1)
28
+ except Exception as e:
29
+ errors += 1
30
+ if errors < 3:
31
+ print(f"SSH Connection attempt {errors}...")
32
+ time.sleep(5)
33
+ else:
34
+ raise e
35
+
36
+ def close(self):
37
+ if self.shell:
38
+ self.shell.close()
39
+ if self.client:
40
+ self.client.close()
41
+
42
+ def send_command(self, command: str):
43
+ if not self.shell:
44
+ raise Exception("Shell not connected")
45
+ self.full_output = ""
46
+ self.shell.send((command + '\n').encode())
47
+
48
+ def read_output(self) -> Tuple[str, str]:
49
+ if not self.shell:
50
+ raise Exception("Shell not connected")
51
+
52
+ partial_output = ''
53
+ while self.shell.recv_ready():
54
+ data = self.shell.recv(1024).decode('utf-8')
55
+ data = self.clean_string(data)
56
+ partial_output += data
57
+ self.full_output += data
58
+ time.sleep(0.1) # Prevent busy waiting
59
+
60
+ self.full_output = self.clean_string(self.full_output)
61
+ return self.full_output, partial_output
62
+
63
+ def clean_string(self, input_string):
64
+ # Remove ANSI escape codes
65
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
66
+ cleaned = ansi_escape.sub('', input_string)
67
+
68
+ # Replace '\r\n' with '\n'
69
+ cleaned = cleaned.replace('\r\n', '\n')
70
+
71
+ return cleaned
\ No newline at end of file
python/helpers/tool.py
renamed
+3
-3
@@ -1,8 +1,8 @@
1
from abc import abstractmethod
2
from typing import TypedDict
3
from agent import Agent
4
-from tools.helpers.print_style import PrintStyle
5
-from tools.helpers import files, messages
4
+from python.helpers.print_style import PrintStyle
5
+from python.helpers import files, messages
6
7
class Response:
8
def __init__(self, message: str, break_loop: bool) -> None:
@@ -30,7 +30,7 @@ class Tool:
30
PrintStyle().print()
31
32
def after_execution(self, response: Response, **kwargs):
33
- text = messages.truncate_text(response.message.strip(), self.agent.max_tool_response_length)
33
+ text = messages.truncate_text(response.message.strip(), self.agent.config.max_tool_response_length)
34
msg_response = files.read_file("./prompts/fw.tool_response.md", tool_name=self.name, tool_response=text)
35
self.agent.append_message(msg_response, human=True)
36
PrintStyle(font_color="#1B4F72", background_color="white", padding=True, bold=True).print(f"{self.agent.agent_name}: Response from tool '{self.name}':")
python/helpers/vector_db.py
renamed
python/tools/call_subordinate.py
renamed
+4
-4
@@ -1,7 +1,7 @@
1
from agent import Agent
2
-from tools.helpers.tool import Tool, Response
3
-from tools.helpers import files
4
-from tools.helpers.print_style import PrintStyle
2
+from python.helpers.tool import Tool, Response
3
+from python.helpers import files
4
+from python.helpers.print_style import PrintStyle
5
6
class Delegation(Tool):
7
@@ -10,7 +10,7 @@ class Delegation(Tool):
10
if self.agent.get_data("subordinate") is None or str(reset).lower().strip() == "true":
11
# subordinate = Agent(system_prompt=self.agent.system_prompt, tools_prompt=self.agent.tools_prompt, number=self.agent.number+1)
12
config = self.agent.__dict__.copy()
13
- config["agent_number"] = self.agent.agent_number+1
13
+ config["agent_number"] = self.agent.number+1
14
subordinate = Agent(**config)
15
subordinate.set_data("superior", self.agent)
16
self.agent.set_data("subordinate", subordinate)
python/tools/code_execution_tool.py
new
+93
@@ -0,0 +1,93 @@
1
+from dataclasses import dataclass
2
+import os, json, contextlib, subprocess, ast, shlex
3
+from io import StringIO
4
+import time
5
+from typing import Literal
6
+from python.helpers import files, messages
7
+from agent import Agent
8
+from python.helpers.tool import Tool, Response
9
+from python.helpers import files
10
+from python.helpers.print_style import PrintStyle
11
+from python.helpers.shell_local import LocalInteractiveSession
12
+from python.helpers.shell_ssh import SSHInteractiveSession
13
+from python.helpers.docker import DockerContainerManager
14
+
15
+@dataclass
16
+class State:
17
+ shell: LocalInteractiveSession | SSHInteractiveSession
18
+ docker: DockerContainerManager | None
19
+
20
+
21
+class CodeExecution(Tool):
22
+
23
+ def execute(self,**kwargs):
24
+ self.prepare_state()
25
+
26
+ # os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
27
+
28
+ runtime = self.args["runtime"].lower().strip()
29
+ if runtime == "python":
30
+ response = self.execute_python_code(self.args["code"])
31
+ elif runtime == "nodejs":
32
+ response = self.execute_nodejs_code(self.args["code"])
33
+ elif runtime == "terminal":
34
+ response = self.execute_terminal_command(self.args["code"])
35
+ else:
36
+ response = files.read_file("./prompts/fw.code_runtime_wrong.md", runtime=runtime)
37
+
38
+ if not response: response = files.read_file("./prompts/fw.code_no_output.md")
39
+ return Response(message=response, break_loop=False)
40
+
41
+ def after_execution(self, response, **kwargs):
42
+ msg_response = files.read_file("./prompts/fw.tool_response.md", tool_name=self.name, tool_response=response.message)
43
+ self.agent.append_message(msg_response, human=True)
44
+
45
+ def prepare_state(self):
46
+ self.state = self.agent.get_data("cot_state")
47
+ if not self.state:
48
+
49
+ #initialize docker container if execution in docker is configured
50
+ if self.agent.config.code_exec_docker_enabled:
51
+ docker = DockerContainerManager(name=self.agent.config.code_exec_docker_name, image=self.agent.config.code_exec_docker_image, ports=self.agent.config.code_exec_docker_ports, volumes=self.agent.config.code_exec_docker_volumes)
52
+ docker.start_container()
53
+ else: docker = None
54
+
55
+ #initialize local or remote interactive shell insterface
56
+ if self.agent.config.code_exec_ssh_enabled:
57
+ shell = SSHInteractiveSession(self.agent.config.code_exec_ssh_addr,self.agent.config.code_exec_ssh_port,self.agent.config.code_exec_ssh_user,self.agent.config.code_exec_ssh_pass)
58
+ else: shell = LocalInteractiveSession()
59
+
60
+ self.state = State(shell=shell,docker=docker)
61
+ shell.connect()
62
+ self.agent.set_data("cot_state", self.state)
63
+
64
+ def execute_python_code(self, code):
65
+ escaped_code = shlex.quote(code)
66
+ command = f'python3 -c {escaped_code}'
67
+ return self.terminal_session(command)
68
+
69
+ def execute_nodejs_code(self, code):
70
+ escaped_code = shlex.quote(code)
71
+ command = f'node -e {escaped_code}'
72
+ return self.terminal_session(command)
73
+
74
+ def execute_terminal_command(self, command):
75
+ return self.terminal_session(command)
76
+
77
+ def terminal_session(self, command):
78
+ self.state.shell.send_command(command)
79
+
80
+ PrintStyle(background_color="white",font_color="#85C1E9",bold=True).print(f"{self.agent.agent_name} code execution output:")
81
+
82
+ idle=0
83
+ while True:
84
+ time.sleep(0.1) # Wait for some output to be generated
85
+ full_output, partial_output = self.state.shell.read_output()
86
+
87
+ if partial_output:
88
+ PrintStyle(font_color="#85C1E9").stream(partial_output)
89
+ idle=0
90
+ else:
91
+ idle+=1
92
+ if ( full_output and idle > 30 ) or ( not full_output and idle > 100 ): return full_output
93
+
\ No newline at end of file
python/tools/knowledge_tool.py
renamed
+2
-2
@@ -5,8 +5,8 @@ import concurrent.futures
5
6
7
8
-from tools.helpers.tool import Tool, Response
9
-from tools.helpers import files
8
+from python.helpers.tool import Tool, Response
9
+from python.helpers import files
10
11
class Knowledge(Tool):
12
def execute(self, question="", **kwargs):
python/tools/memorize.py
renamed
+3
-3
@@ -1,7 +1,7 @@
1
from agent import Agent
2
-from tools.helpers import files
3
-from tools.helpers.tool import Tool, Response
4
-from tools import memory_tool
2
+from python.helpers import files
3
+from python.helpers.tool import Tool, Response
4
+import memory_tool
5
6
class Memorize(Tool):
7
def execute(self,**kwargs):
python/tools/memory_tool.py
renamed
+6
-7
@@ -1,16 +1,15 @@
1
from agent import Agent
2
-from tools.helpers.vector_db import VectorDB, Document
3
-from tools.helpers import files
2
+from python.helpers.vector_db import VectorDB, Document
3
+from python.helpers import files
4
import os, json
5
-from tools.helpers.tool import Tool, Response
6
-from tools.helpers.print_style import PrintStyle
5
+from python.helpers.tool import Tool, Response
6
+from python.helpers.print_style import PrintStyle
7
8
db: VectorDB | None = None
9
10
class Memory(Tool):
11
def execute(self,**kwargs):
12
- #TODO separate param for memory tool result count
13
- result = process_query(self.agent, self.args["memory"],self.args["action"], result_count=self.agent.auto_memory_count)
12
+ result = process_query(self.agent, self.args["memory"],self.args["action"], result_count=self.agent.config.auto_memory_count)
13
return Response(message="\n\n".join(result), break_loop=False)
14
15
@@ -21,7 +20,7 @@ def initialize(embeddings_model, subdir=""):
20
21
22
def process_query(agent:Agent, message: str, action: str = "load", result_count: int = 3, **kwargs):
24
- if not db: initialize(agent.embeddings_model, subdir=agent.memory_subdir)
23
+ if not db: initialize(agent.config.embeddings_model, subdir=agent.config.memory_subdir)
24
25
if action.strip().lower() == "save":
26
id = db.insert_document(str(message)) # type: ignore
python/tools/online_knowledge_tool.py
renamed
+2
-2
@@ -1,6 +1,6 @@
1
from agent import Agent
2
-from tools.helpers import perplexity_search
3
-from tools.helpers.tool import Tool, Response
2
+from python.helpers import perplexity_search
3
+from python.helpers.tool import Tool, Response
4
5
class OnlineKnowledge(Tool):
6
def execute(self,**kwargs):
python/tools/response.py
renamed
+5
-5
@@ -1,11 +1,11 @@
1
from agent import Agent
2
-from tools.helpers import files
3
-from tools.helpers.print_style import PrintStyle
2
+from python.helpers import files
3
+from python.helpers.print_style import PrintStyle
4
5
from agent import Agent
6
-from tools.helpers.tool import Tool, Response
7
-from tools.helpers import files
8
-from tools.helpers.print_style import PrintStyle
6
+from python.helpers.tool import Tool, Response
7
+from python.helpers import files
8
+from python.helpers.print_style import PrintStyle
9
10
class ResponseTool(Tool):
11
python/tools/task_done.py
renamed
+5
-5
@@ -1,11 +1,11 @@
1
from agent import Agent
2
-from tools.helpers import files
3
-from tools.helpers.print_style import PrintStyle
2
+from python.helpers import files
3
+from python.helpers.print_style import PrintStyle
4
5
from agent import Agent
6
-from tools.helpers.tool import Tool, Response
7
-from tools.helpers import files
8
-from tools.helpers.print_style import PrintStyle
6
+from python.helpers.tool import Tool, Response
7
+from python.helpers import files
8
+from python.helpers.print_style import PrintStyle
9
10
class TaskDone(Tool):
11
python/tools/unknown.py
renamed
+2
-2
@@ -1,5 +1,5 @@
1
-from tools.helpers.tool import Tool, Response
2
-from tools.helpers import files
1
+from python.helpers.tool import Tool, Response
2
+from python.helpers import files
3
4
class Unknown(Tool):
5
def execute(self, **kwargs):
requirements.txt
+2
@@ -9,3 +9,5 @@ langchain-chroma==0.1.1
9
webcolors==24.6.0
10
sentence-transformers==3.0.1
11
pytimedinput==2.0.1
12
+docker==7.1.0
13
+paramiko==3.4.0
tools/code_execution_tool.py
deleted
-38
@@ -1,38 +0,0 @@
1
-import os, json, contextlib, subprocess, ast, shlex
2
-from io import StringIO
3
-from tools.helpers import files, messages
4
-from agent import Agent
5
-from tools.helpers.tool import Tool, Response
6
-from tools.helpers import files
7
-from tools.helpers.print_style import PrintStyle
8
-
9
-class CodeExecution(Tool):
10
-
11
- def execute(self,**kwargs):
12
-
13
- # os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
14
-
15
- runtime = self.args["runtime"].lower().strip()
16
- if runtime == "python":
17
- response = self.execute_python_code(self.args["code"])
18
- elif runtime == "nodejs":
19
- response = self.execute_nodejs_code(self.args["code"])
20
- elif runtime == "terminal":
21
- response = self.execute_terminal_command(self.args["code"])
22
- else:
23
- response = files.read_file("./prompts/fw.code_runtime_wrong.md", runtime=runtime)
24
-
25
- if not response: response = files.read_file("./prompts/fw.code_no_output.md")
26
- return Response(message=response, break_loop=False)
27
-
28
- def execute_python_code(self, code, input_data="y\n"):
29
- result = subprocess.run(['python', '-c', code], capture_output=True, text=True, input=input_data)
30
- return result.stdout + result.stderr
31
-
32
- def execute_nodejs_code(self, code, input_data="y\n"):
33
- result = subprocess.run(['node', '-e', code], capture_output=True, text=True, input=input_data)
34
- return result.stdout + result.stderr
35
-
36
- def execute_terminal_command(self, command, input_data="y\n"):
37
- result = subprocess.run(command, shell=True, capture_output=True, text=True, input=input_data)
38
- return result.stdout + result.stderr
\ No newline at end of file