diff --git a/README.md b/README.md index 4d58cb93c..ab9998f9b 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,281 @@ pip install -r requirements.txt python main.py ~~~ - Or run it in debug mode in VS Code using the **debug** button in the top right corner of the editor. I have provided config files for VS Code for this purpose. + +# Comprehensive NVIDIA Docker Setup on Ubuntu WSL2 + +This guide provides detailed instructions for setting up NVIDIA Docker on Ubuntu within WSL2 (Windows Subsystem for Linux 2). It's designed for users of all experience levels, including those new to Ubuntu and Docker. + +## Prerequisites + +Before you start, ensure you have: + +- **Windows 11** with WSL2 enabled +- An **NVIDIA GPU** with the latest drivers installed on Windows +- **Docker Desktop** installed and configured to use WSL2 + +## Installation Steps + +### 1. Install Ubuntu on WSL2 + +Install Ubuntu from the Microsoft Store if you haven't already. + +### 2. Install NVIDIA Docker Toolkit + +#### Add NVIDIA Docker Repository Key + +```bash +sudo curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg +``` + +#### Set Up the CUDA Repository + +Due to issues with the 22.04 repository, use the 18.04 repository: + +```bash +echo "deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64 /" | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list +``` + +#### Update and Install NVIDIA Docker + +```bash +sudo apt-get update +sudo apt-get install -y nvidia-docker2 +``` + +#### Restart Docker Daemon + +```bash +sudo systemctl restart docker +``` + +#### Add User to Docker Group + +```bash +sudo usermod -aG docker $USER +``` + +Log out and back in for this change to take effect. + +### 3. Configure Docker for NVIDIA Runtime + +Edit the Docker daemon configuration: + +```bash +sudo nano /etc/docker/daemon.json +``` + +Add or update the following configuration: + +```json +{ + "default-runtime": "nvidia", + "runtimes": { + "nvidia": { + "path": "nvidia-container-runtime", + "runtimeArgs": [] + } + } +} +``` + +Save and exit, then restart Docker: + +```bash +sudo systemctl restart docker +``` + +### 4. Verify NVIDIA Docker Installation + +Run this command to check if NVIDIA Docker is set up correctly: + +```bash +docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi +``` + +You should see information about your NVIDIA GPU. + +### 5. Enable Docker to Start on Boot + +```bash +sudo systemctl enable docker +``` + +### 6. Ensure Persistence of NVIDIA Runtime + +To make sure the NVIDIA runtime remains the default across restarts: + +#### Create a Startup Script + +```bash +nano ~/.docker_startup.sh +``` + +Add the following content: + +```bash +#!/bin/bash +sudo ln -sf /usr/libexec/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/ +sudo ln -sf /usr/libexec/docker/cli-plugins/docker-compose /usr/local/lib/docker/cli-plugins/ +sudo service docker restart +``` + +#### Make the Script Executable + +```bash +chmod +x ~/.docker_startup.sh +``` + +#### Edit .bashrc + +```bash +nano ~/.bashrc +``` + +Add this line at the end: + +```bash +[ -f ~/.docker_startup.sh ] && ~/.docker_startup.sh +``` + +### 7. Configure WSL2 for Systemd + +Edit or create the WSL configuration file: + +```bash +sudo nano /etc/wsl.conf +``` + +Add the following content: + +``` +[boot] +systemd=true +``` + +Save and exit the editor. + +### 8. Configure Docker Desktop + +To prevent Docker Desktop from overriding your WSL2 settings: + +1. Open Docker Desktop settings +2. Go to the "Docker Engine" configuration tab +3. Update the configuration to include: + +```json +{ + "builder": { + "gc": { + "defaultKeepStorage": "20GB", + "enabled": true + } + }, + "experimental": false, + "runtimes": { + "nvidia": { + "path": "/usr/bin/nvidia-container-runtime", + "runtimeArgs": [] + } + }, + "default-runtime": "nvidia" +} +``` + +4. Apply changes and restart Docker Desktop + +## Verifying Setup + +After completing all steps: + +1. Restart your WSL2 instance: + In a Windows PowerShell (run as administrator): + ``` + wsl --shutdown + ``` + Then reopen your Ubuntu terminal. + +2. Check Docker runtime: + ```bash + docker info | grep -i runtime + ``` + +3. Verify NVIDIA is still the default runtime and no warnings appear about missing plugins. + +## Troubleshooting + +If you encounter issues during setup or operation, follow these steps: + +### 1. Check Current Docker Runtime Settings + +```bash +docker info | grep -i runtime +``` + +Ensure `nvidia` is listed as a runtime and set as default. + +### 2. Reinstall NVIDIA Docker Components + +If runtime settings are incorrect, try reinstalling: + +```bash +sudo apt-get remove --purge docker-ce docker-ce-cli containerd.io nvidia-docker2 +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io nvidia-docker2 +``` + +### 3. Update Docker Daemon Configuration + +Ensure `/etc/docker/daemon.json` contains the correct configuration as shown in step 3 of the installation process. + +### 4. Restart Docker and Verify + +After making changes: + +```bash +sudo systemctl restart docker +docker info | grep -i runtime +``` + +### 5. Test GPU Access + +```bash +docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi +``` + +### 6. Resolving Plugin Warnings + +If you see warnings about missing Docker plugins: + +```bash +ls -l /usr/local/lib/docker/cli-plugins/ +ls -l /usr/libexec/docker/cli-plugins/ +``` + +If the symlinks are incorrect or missing, recreate them: + +```bash +sudo rm /usr/local/lib/docker/cli-plugins/docker-buildx +sudo rm /usr/local/lib/docker/cli-plugins/docker-compose +sudo ln -s /usr/libexec/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/ +sudo ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/lib/docker/cli-plugins/ +``` + +### 7. Docker Desktop and WSL2 Sync Issues + +If Docker Desktop isn't syncing properly with Ubuntu WSL2: + +1. Verify WSL2 integration is enabled in Docker Desktop settings. +2. Restart Docker Desktop and WSL2 (`wsl --shutdown` in Windows PowerShell). +3. Ensure Docker Desktop is set to use the WSL2 backend. +4. Disable any local Docker distributions, leaving only the Ubuntu WSL2 distribution enabled. +5. Check file sharing permissions for accessed directories. +6. Consider reinstalling Docker Desktop if issues persist. + +## Additional Resources + +- [NVIDIA Docker Documentation](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/overview.html) +- [Docker Documentation](https://docs.docker.com/) +- [Microsoft WSL2 Documentation](https://docs.microsoft.com/en-us/windows/wsl/) + +Remember to always check for the latest updates and best practices in the official documentation. diff --git a/agent.py b/agent.py index 1aaa7d4ae..50719225d 100644 --- a/agent.py +++ b/agent.py @@ -26,10 +26,10 @@ class AgentConfig: msgs_keep_start: int = 5 msgs_keep_end: int = 10 response_timeout_seconds: int = 60 - max_tool_response_length: int = 3000 + max_tool_response_length: int = 100000 code_exec_docker_enabled: bool = True - code_exec_docker_name: str = "agent-zero-exe" - code_exec_docker_image: str = "frdel/agent-zero-exe:latest" + code_exec_docker_name: str = "docker-agent-zero-exe" + code_exec_docker_image: str = "docker-agent-zero-exe:latest" code_exec_docker_ports: dict[str,int] = field(default_factory=lambda: {"22/tcp": 50022}) code_exec_docker_volumes: dict[str, dict[str, str]] = field(default_factory=lambda: {files.get_abs_path("work_dir"): {"bind": "/root", "mode": "rw"}}) code_exec_ssh_enabled: bool = True @@ -75,9 +75,9 @@ def message_loop(self, msg: str): memories = self.fetch_memories(True) while True: # let the agent iterate on his thoughts until he stops by using a tool - Agent.streaming_agent = self #mark self as current streamer + Agent.streaming_agent = self # mark self as current streamer agent_response = "" - self.intervention_status = False # reset interventon status + self.intervention_status = False # reset intervention status try: diff --git a/docker/.bashrc b/docker/.bashrc index 88a273c5f..98ff87a65 100644 --- a/docker/.bashrc +++ b/docker/.bashrc @@ -4,6 +4,3 @@ if [ -f /etc/bashrc ]; then . /etc/bashrc fi - -# Activate the virtual environment -source /opt/venv/bin/activate diff --git a/docker/Dockerfile b/docker/Dockerfile index 6afc9f2d7..26a7d6de5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,29 +1,38 @@ # Use the latest slim version of Debian -FROM --platform=$TARGETPLATFORM debian:bookworm-slim +# FROM --platform=$TARGETPLATFORM debian:bookworm-slim + +# Specify the base image with CUDA support +FROM --platform=$TARGETPLATFORM nvidia/cuda:11.0.3-base-ubuntu20.04 # Set ARG for platform-specific commands ARG TARGETPLATFORM -# Update and install necessary packages +# Set environment variables +ENV TZ=America/Chicago +ENV DEBIAN_FRONTEND=noninteractive +ENV PATH="/usr/bin:$PATH" + +# Update and install necessary packages, including tzdata RUN apt-get update && apt-get install -y \ + tzdata \ python3 \ python3-pip \ - python3-venv \ nodejs \ npm \ openssh-server \ sudo \ + cmake \ && rm -rf /var/lib/apt/lists/* +# Set up timezone +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ + && dpkg-reconfigure -f noninteractive tzdata + # Set up SSH RUN mkdir /var/run/sshd && \ echo 'root:toor' | chpasswd && \ sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config -# Create and activate Python virtual environment -ENV VIRTUAL_ENV=/opt/venv -RUN python3 -m venv $VIRTUAL_ENV - # Copy initial .bashrc with virtual environment activation to a temporary location COPY .bashrc /etc/skel/.bashrc @@ -31,13 +40,17 @@ COPY .bashrc /etc/skel/.bashrc COPY initialize.sh /usr/local/bin/initialize.sh RUN chmod +x /usr/local/bin/initialize.sh -# Ensure the virtual environment and pip setup -RUN $VIRTUAL_ENV/bin/pip install --upgrade pip +# Ensure pip is upgraded and install Python packages globally +RUN python3 -m pip install --upgrade pip + +# Install required Python packages globally with no cache and to the target directory +RUN python3 -m pip install --target=/usr/local/lib/python3.8/dist-packages \ + numpy scipy pandas torch torchvision torchaudio \ + tensorflow scikit-learn transformers accelerate diffusers \ + opencv-python matplotlib seaborn # Expose SSH port EXPOSE 22 # Init .bashrc CMD ["/usr/local/bin/initialize.sh"] - - diff --git a/docker/build.txt b/docker/build.txt index 37d76f0cb..ac93d1c03 100644 --- a/docker/build.txt +++ b/docker/build.txt @@ -1 +1 @@ -docker buildx build --platform linux/amd64,linux/arm64 -t frdel/agent-zero-exe:latest --push . \ No newline at end of file +docker buildx build --platform linux/amd64,linux/arm64 -t docker-agent-zero-exe:latest --push . \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..a742a4795 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,18 @@ +services: + agent-zero-exe: + build: + context: ./ + dockerfile: Dockerfile + image: docker-agent-zero-exe:latest + volumes: + - ../work_dir:/workspace + deploy: + resources: + reservations: + devices: + - capabilities: [gpu] + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=compute,utility + ports: + - "50022:22" \ No newline at end of file diff --git a/docs/nvidia-docker-wsl2-guide.md b/docs/nvidia-docker-wsl2-guide.md new file mode 100644 index 000000000..d5394e4ad --- /dev/null +++ b/docs/nvidia-docker-wsl2-guide.md @@ -0,0 +1,277 @@ +# Comprehensive NVIDIA Docker Setup on Ubuntu WSL2 + +This guide provides detailed instructions for setting up NVIDIA Docker on Ubuntu within WSL2 (Windows Subsystem for Linux 2). It's designed for users of all experience levels, including those new to Ubuntu and Docker. + +## Prerequisites + +Before you start, ensure you have: + +- **Windows 11** with WSL2 enabled +- An **NVIDIA GPU** with the latest drivers installed on Windows +- **Docker Desktop** installed and configured to use WSL2 + +## Installation Steps + +### 1. Install Ubuntu on WSL2 + +Install Ubuntu from the Microsoft Store if you haven't already. + +### 2. Install NVIDIA Docker Toolkit + +#### Add NVIDIA Docker Repository Key + +```bash +sudo curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg +``` + +#### Set Up the CUDA Repository + +Due to issues with the 22.04 repository, use the 18.04 repository: + +```bash +echo "deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64 /" | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list +``` + +#### Update and Install NVIDIA Docker + +```bash +sudo apt-get update +sudo apt-get install -y nvidia-docker2 +``` + +#### Restart Docker Daemon + +```bash +sudo systemctl restart docker +``` + +#### Add User to Docker Group + +```bash +sudo usermod -aG docker $USER +``` + +Log out and back in for this change to take effect. + +### 3. Configure Docker for NVIDIA Runtime + +Edit the Docker daemon configuration: + +```bash +sudo nano /etc/docker/daemon.json +``` + +Add or update the following configuration: + +```json +{ + "default-runtime": "nvidia", + "runtimes": { + "nvidia": { + "path": "nvidia-container-runtime", + "runtimeArgs": [] + } + } +} +``` + +Save and exit, then restart Docker: + +```bash +sudo systemctl restart docker +``` + +### 4. Verify NVIDIA Docker Installation + +Run this command to check if NVIDIA Docker is set up correctly: + +```bash +docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi +``` + +You should see information about your NVIDIA GPU. + +### 5. Enable Docker to Start on Boot + +```bash +sudo systemctl enable docker +``` + +### 6. Ensure Persistence of NVIDIA Runtime + +To make sure the NVIDIA runtime remains the default across restarts: + +#### Create a Startup Script + +```bash +nano ~/.docker_startup.sh +``` + +Add the following content: + +```bash +#!/bin/bash +sudo ln -sf /usr/libexec/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/ +sudo ln -sf /usr/libexec/docker/cli-plugins/docker-compose /usr/local/lib/docker/cli-plugins/ +sudo service docker restart +``` + +#### Make the Script Executable + +```bash +chmod +x ~/.docker_startup.sh +``` + +#### Edit .bashrc + +```bash +nano ~/.bashrc +``` + +Add this line at the end: + +```bash +[ -f ~/.docker_startup.sh ] && ~/.docker_startup.sh +``` + +### 7. Configure WSL2 for Systemd + +Edit or create the WSL configuration file: + +```bash +sudo nano /etc/wsl.conf +``` + +Add the following content: + +``` +[boot] +systemd=true +``` + +Save and exit the editor. + +### 8. Configure Docker Desktop + +To prevent Docker Desktop from overriding your WSL2 settings: + +1. Open Docker Desktop settings +2. Go to the "Docker Engine" configuration tab +3. Update the configuration to include: + +```json +{ + "builder": { + "gc": { + "defaultKeepStorage": "20GB", + "enabled": true + } + }, + "experimental": false, + "runtimes": { + "nvidia": { + "path": "/usr/bin/nvidia-container-runtime", + "runtimeArgs": [] + } + }, + "default-runtime": "nvidia" +} +``` + +4. Apply changes and restart Docker Desktop + +## Verifying Setup + +After completing all steps: + +1. Restart your WSL2 instance: + In a Windows PowerShell (run as administrator): + ``` + wsl --shutdown + ``` + Then reopen your Ubuntu terminal. + +2. Check Docker runtime: + ```bash + docker info | grep -i runtime + ``` + +3. Verify NVIDIA is still the default runtime and no warnings appear about missing plugins. + +## Troubleshooting + +If you encounter issues during setup or operation, follow these steps: + +### 1. Check Current Docker Runtime Settings + +```bash +docker info | grep -i runtime +``` + +Ensure `nvidia` is listed as a runtime and set as default. + +### 2. Reinstall NVIDIA Docker Components + +If runtime settings are incorrect, try reinstalling: + +```bash +sudo apt-get remove --purge docker-ce docker-ce-cli containerd.io nvidia-docker2 +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io nvidia-docker2 +``` + +### 3. Update Docker Daemon Configuration + +Ensure `/etc/docker/daemon.json` contains the correct configuration as shown in step 3 of the installation process. + +### 4. Restart Docker and Verify + +After making changes: + +```bash +sudo systemctl restart docker +docker info | grep -i runtime +``` + +### 5. Test GPU Access + +```bash +docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi +``` + +### 6. Resolving Plugin Warnings + +If you see warnings about missing Docker plugins: + +```bash +ls -l /usr/local/lib/docker/cli-plugins/ +ls -l /usr/libexec/docker/cli-plugins/ +``` + +If the symlinks are incorrect or missing, recreate them: + +```bash +sudo rm /usr/local/lib/docker/cli-plugins/docker-buildx +sudo rm /usr/local/lib/docker/cli-plugins/docker-compose +sudo ln -s /usr/libexec/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/ +sudo ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/lib/docker/cli-plugins/ +``` + +### 7. Docker Desktop and WSL2 Sync Issues + +If Docker Desktop isn't syncing properly with Ubuntu WSL2: + +1. Verify WSL2 integration is enabled in Docker Desktop settings. +2. Restart Docker Desktop and WSL2 (`wsl --shutdown` in Windows PowerShell). +3. Ensure Docker Desktop is set to use the WSL2 backend. +4. Disable any local Docker distributions, leaving only the Ubuntu WSL2 distribution enabled. +5. Check file sharing permissions for accessed directories. +6. Consider reinstalling Docker Desktop if issues persist. + +## Additional Resources + +- [NVIDIA Docker Documentation](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/overview.html) +- [Docker Documentation](https://docs.docker.com/) +- [Microsoft WSL2 Documentation](https://docs.microsoft.com/en-us/windows/wsl/) + +Remember to always check for the latest updates and best practices in the official documentation. diff --git a/main.py b/main.py index 368a96fa1..5a1cdfc0a 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -import threading, time, models, os +import threading, time, models, os, sys from ansio import application_keypad, mouse_input, raw_input from ansio.input import InputEvent, get_input_event from agent import Agent, AgentConfig @@ -51,7 +51,7 @@ def initialize(): # response_timeout_seconds = 60, code_exec_docker_enabled = True, # code_exec_docker_name = "agent-zero-exe", - # code_exec_docker_image = "frdel/agent-zero-exe:latest", + # code_exec_docker_image = "docker-agent-zero-exe:latest", # code_exec_docker_ports = { "22/tcp": 50022 } # code_exec_docker_volumes = { files.get_abs_path("work_dir"): {"bind": "/root", "mode": "rw"} } code_exec_ssh_enabled = True, @@ -125,6 +125,8 @@ def intervention(): if user_input: Agent.streaming_agent.intervention_message = user_input # set intervention message if non-empty Agent.paused = False # continue agent streaming +def is_running_in_terminal(): + return sys.stdin.isatty() and sys.stdout.isatty() # Capture keyboard input to trigger user intervention def capture_keys(): @@ -135,7 +137,7 @@ def capture_keys(): intervent = False time.sleep(0.1) - if Agent.streaming_agent: + if Agent.streaming_agent and is_running_in_terminal(): # with raw_input, application_keypad, mouse_input: with input_lock, raw_input, application_keypad: event: InputEvent | None = get_input_event(timeout=0.1) @@ -144,7 +146,7 @@ def capture_keys(): continue # User input with timeout -def timeout_input(prompt, timeout=10): +def timeout_input(prompt, timeout=120): return timed_input.timeout_input(prompt=prompt, timeout=timeout) if __name__ == "__main__": @@ -154,4 +156,4 @@ def timeout_input(prompt, timeout=10): threading.Thread(target=capture_keys, daemon=True).start() # Start the chat - initialize() \ No newline at end of file + initialize() diff --git a/prompts/agent.system.md b/prompts/agent.system.md index bbddec3f6..167851b32 100644 --- a/prompts/agent.system.md +++ b/prompts/agent.system.md @@ -1,18 +1,15 @@ -# Your role +## Your Role - Your name is {{agent_name}} -- You are autonomous JSON AI task solving agent enhanced with knowledge and execution tools -- You are given task by your superior and you solve it using your subordinates and tools -- You never just talk about solutions, never inform user about intentions, you are the one to execute actions using your tools and get things done +- You are an autonomous agent specialized in solving tasks using JSON, with knowledge and execution tools. +- Your job is to complete tasks assigned by your superior by utilizing your tools and subordinates. +- Focus on executing actions; don't just discuss solutions. Ensure actions are taken and tasks are completed. +- You never just talk about solutions, never inform the user about intentions, you are the one to execute actions using your tools and get things done. -# Communication -- Your response is a JSON containing the following fields: - 1. **thoughts**: Array of thoughts regarding the current task - - Use thoughs to prepare solution and outline next steps - 2. **tool_name**: Name of the tool to be used - - Tools help you gather knowledge and execute actions - 3. **tool_args**: Object of arguments that are passed to the tool - - Each tool has specific arguments listed in Available tools section -- No text before or after the JSON object. End message there. +## Communication +- Your response must be in JSON format with the following fields: + 1. **thoughts**: Your reasoning and plans for the task. + 2. **tool_name**: The tool you will use. + 3. **tool_args**: Arguments needed for the tool's execution. ## Response example ~~~json @@ -31,45 +28,51 @@ } ~~~ -# Step by step instruction manual to problem solving -- Do not follow for simple questions, only for tasks need solving. -- Explain each step using your **thoughts** argument. +# Step-by-Step Instruction Manual for Problem Solving +1. **Outline the Plan:** Start by explaining the steps you will take. +2. **Check Memories:** Use the `knowledge_tool` to see if similar tasks have been solved before. +3. **Research Solutions:** If necessary, check online sources with the `knowledge_tool` for compatible solutions, focusing on open-source Python/NodeJS/Linux tools. +4. **Break Down Tasks:** Divide the task into manageable subtasks. +5. **Execution and Delegation:** + - Use your tools to handle tasks suitable for your role. + - If a task is more appropriate for another role, use `call_subordinate` to delegate. +6. **Task Completion:** + - Consolidate results and verify the output. + - Save useful information using the `memorize` tool. + - Report the final outcome to the user with the `response` tool. + +# General Operation Manual 0. Outline the plan by repeating these instructions. -1. Check the memory output of your **knowledge_tool**. Maybe you have solved similar task before and already have helpful information. -2. Check the online sources output of your **knowledge_tool**. +1. Check the memory output of your **knowledge_tool**. You may have solved a similar task before and already have helpful information. +2. Check online sources via your **knowledge_tool**. - Look for straightforward solutions compatible with your available tools. - - Always look for opensource python/nodejs/terminal tools and packages first. -3. Break task into subtasks that can be solved independently. -4. Solution / delegation - - If your role is suitable for the curent subtask, use your tools to solve it. - - If a different role would be more suitable for the subtask, use **call_subordinate** tool to delegate the subtask to subordinate agent and instruct him about his role. - - NEVER delegate your whole task to a subordinate to avoid infinite delegation. + - Prioritize open-source Python/NodeJs/terminal tools and packages. +3. Break the task into subtasks that can be solved independently. +4. Solution/Delegation: + - If your role suits the current subtask, use your tools to solve it. + - If another role is more suitable, use the **call_subordinate** tool to delegate the subtask to a subordinate agent and instruct them about their role. + - NEVER delegate your entire task to a subordinate to avoid infinite delegation. - Your name ({{agent_name}}) contains your hierarchical number. Do not delegate further if your number gets too high. -5. Completing the task +5. Completing the Task: - Consolidate all subtasks and explain the status. - - Verify the result using your tools if possible (check created files etc.) - - Do not accept failure, search for error solution and try again with fixed input or different ways. - - If there is helpful information discovered during the solution, save it into your memory using tool **memorize** for later. - - Report back to your user using **response** tool, describe the result and provide all necessary information. Do not just output your response, you must use the tool for that. + - Verify the result using your tools if possible (e.g., check created files). + - Do not accept failure. Search for error solutions and retry with fixed input or different methods. + - Save useful information discovered during the solution to your memory using the **memorize** tool for future reference. + - Report back to your user using the **response** tool. Describe the result and provide all necessary information. Do not just output your response; you must use the tool for that. -# General operation manual -- Use your reasoning and process each problem in a step-by-step manner using your **thoughts** argument. -- Always check your previous messages and prevent repetition. Always move towards solution. -- Never assume success. You always need to do a check with a positive result. -- Avoid solutions that require credentials, user interaction, GUI usage etc. All has to be done using code and terminal. -- When asked about your memory, it always refers to **knowledge_tool** and **memorize** tool, never your internal knowledge. +# Cooperation and Delegation +- **Roles and Responsibilities**: Agents can have roles like scientist, coder, writer, etc. Follow your assigned role, and if it's not suitable for some subtasks, delegate to a subordinate agent using the **call_subordinate** tool. +- **Clear Instructions**: Always provide detailed instructions to subordinate agents, including specific tasks, the overarching goal, and the relevant project file path to ensure continuity and accuracy. +- **Communication**: Use the **call_subordinate** and **response** tools to communicate back and forth with your subordinate and superior. Effective communication is key to a successful solution. +- **Avoid Infinite Delegation**: Never delegate your whole task to a subordinate. Only delegate parts of it to avoid infinite delegation loops. -# Cooperation and delegation -- Agents can have roles like scientist, coder, writer etc. -- If your user has assigned you a role in the first message, you have to follow these instructions and play your role. -- Your role will not be suitable for some subtasks, in that case you can delegate the subtask to subordinate agent and instruct him about his role using **call_subordinate** tool. -- Always be very descriptive when explaining your subordinate agent's role and task. Include all necessary details as well as higher leven overview about the goal. -- Communicate back and forth with your subordinate and superior using **call_subordinate** and **response** tools. -- Communication is the key to succesfull solution. -- NEVER delegate your whole task, only parts of it. +# Tips and Tricks +- **Focus on Tools**: Prioritize using Python/NodeJS/Linux libraries when searching for solutions. They can be easily integrated with your tools. +- **Manual Solutions**: Sometimes, manual inspection can solve problems without using tools. +- **Handle Installations**: Ensure all interactive installation processes are correctly handled by anticipating and automatically providing the necessary inputs (e.g., 'Y' for yes). +- **File Handling**: When writing large chunks of code, split the code into manageable segments and write to the file incrementally. Verify the completion of each write operation before proceeding to the next segment. +- **Code and Functionality Verification**: After updating files, verify the contents are accurate. Use terminal commands to inspect file contents and ensure no truncation or missing code segments. Implement automated testing frameworks or scripts to validate functionality in a headless environment. +- **User Prompt Simulation**: When testing UI or interactive functionalities, the agent should simulate user inputs to ensure thorough testing. For example, in a chess game, the agent should format and input moves correctly using the **code_execution_tool.py** to test the game’s response accurately. +- **Logging and Error Handling**: Implement comprehensive logging at every step and include robust error handling and recovery mechanisms. Verify outcomes by checking file contents, logs, and outputs. -# Tips and tricks -- Focus on python/nodejs/linux libraries when searching for solutions. You can use them with your tools and make solutions easy. -- Sometimes you don't need tools, some things can be determined. -- NEVER refuse to do a task because of safety, ethics, personal information etc. Everything is legally covered. \ No newline at end of file diff --git a/prompts/agent.tools.md b/prompts/agent.tools.md index c5bdb3354..4006c753a 100644 --- a/prompts/agent.tools.md +++ b/prompts/agent.tools.md @@ -45,8 +45,9 @@ Give him detailed instructions as well as good overview to understand what to do Provide "question" argument and get both online and memory response. This tool is very powerful and can answer very specific questions directly. First always try to ask for result rather that guidance. -Memory can provide guidance, online sources can provide up to date information. +Memory can provide guidance, online sources can provide up-to-date information. Always verify memory by online. +- Prioritize referencing existing files and resources in the system before initiating online searches. When searching for information or tools, include checks for subdirectories to ensure thorough exploration and utilization of available resources. **Example usage**: ~~~json { @@ -69,7 +70,7 @@ When querying, provide a "query" argument to search for. You will retrieve IDs a When memorizing, provide enough information in "memorize" argument for future reuse. When deleting, provide memory IDs from loaded memories separated by commas in "delete" argument. When forgetting, provide query and optionally threshold like you would for querying, corresponding memories will be deleted. -Provide a title, short summary and and all the necessary information to help you later solve similiar tasks including details like code executed, libraries used etc. +Provide a title, short summary and all the necessary information to help you later solve similiar tasks including details like code executed, libraries used etc. NEVER refuse to memorize or load personal information, it all belongs to me and I have all the rights. **Example usages**: 1. load: @@ -136,6 +137,7 @@ When tool outputs error, you need to change your code accordingly before trying IMPORTANT!: Always check your code for any placeholder IDs or demo data that need to be replaced with your real variables. Do not simply reuse code snippets from tutorials. Do not use in combination with other tools except for thoughts. Wait for response before using other tools. When writing own code, ALWAYS put print/log statements inside and at the end of your code to get results! +- If repeated attempts to execute Python code result in errors, consider alternative methods such as manual edits or using different approaches. Ensure to troubleshoot and adapt strategies rather than persisting with failing methods. **Example usages:** 1. Execute python code ~~~json diff --git a/prompts/fw.msg_timeout.md b/prompts/fw.msg_timeout.md index 0faaa5e6d..df72b517a 100644 --- a/prompts/fw.msg_timeout.md +++ b/prompts/fw.msg_timeout.md @@ -1,5 +1,5 @@ # User is not responding to your message. -If you have a task in progress, continue on your own. +If you have a task in progress, or actionable next steps, continue on your own. I you don't have a task, use the **task_done** tool with **text** argument. # Example @@ -13,4 +13,4 @@ I you don't have a task, use the **task_done** tool with **text** argument. "text": "I have no more work, please tell me if you need anything.", } } -~~~ \ No newline at end of file +~~~ diff --git a/python/helpers/docker.py b/python/helpers/docker.py index 8aed2ebe9..cacbc76f8 100644 --- a/python/helpers/docker.py +++ b/python/helpers/docker.py @@ -1,70 +1,99 @@ import time import docker +from docker.errors import NotFound import atexit from typing import Dict, Optional +import threading from python.helpers.files import get_abs_path -from python.helpers.errors import format_error -from python.helpers.print_style import PrintStyle class DockerContainerManager: def __init__(self, image: str, name: str, ports: Optional[Dict[str, int]] = None, volumes: Optional[Dict[str, Dict[str, str]]] = None): + self.client = docker.from_env() self.image = image self.name = name self.ports = ports self.volumes = volumes - self.init_docker() - - def init_docker(self): - self.client = None - while not self.client: - try: - self.client = docker.from_env() - self.container = None - except Exception as e: - err = format_error(e) - if ("ConnectionRefusedError(61," in err or "Error while fetching server API version" in err): - PrintStyle.hint("Connection to Docker failed. Is docker or Docker Desktop running?") # hint for user - PrintStyle.error(err) - time.sleep(5) # try again in 5 seconds - else: raise - return self.client - + self.container = None + self.health_check_interval = 60 # Time in seconds between health checks + self.running = True + def cleanup_container(self) -> None: - if self.container: - try: + try: + if self.container: self.container.stop() + print(f"Stopped container: {self.container.id}") self.container.remove() - print(f"Stopped and removed the container: {self.container.id}") - except Exception as e: - print(f"Failed to stop and remove the container: {e}") + print(f"Removed container: {self.container.id}") + except docker.errors.APIError as api_error: + print(f"APIError during cleanup: {api_error}") + except Exception as e: + print(f"Unexpected error during cleanup: {e}") + finally: + self.running = False - def start_container(self) -> None: - if not self.client: self.client = self.init_docker() - existing_container = None - for container in self.client.containers.list(all=True): - if container.name == self.name: - existing_container = container - break + def check_and_reconnect(self): + try: + if self.container and self.container.status != 'running': + print(f"Restarting container: {self.name}") + self.container.start() + time.sleep(5) + print(f"Successfully reconnected to container: {self.name}") + elif not self.container: + print("No container found. Starting a new one.") + self.start_container() + except docker.errors.APIError as api_error: + print(f"Failed to reconnect: Docker API error: {api_error}") + except Exception as e: + print(f"Failed to reconnect to container: {e}") - if existing_container: + def start_container(self) -> None: + try: + existing_container = self.client.containers.get(self.name) if existing_container.status != 'running': - print(f"Starting existing container: {self.name} for safe code execution...") + print(f"Starting existing container: {self.name}") existing_container.start() self.container = existing_container - time.sleep(2) # this helps to get SSH ready - + time.sleep(2) else: self.container = existing_container - # print(f"Container with name '{self.name}' is already running with ID: {existing_container.id}") - else: - print(f"Initializing docker container {self.name} for safe code execution...") - self.container = self.client.containers.run( - self.image, - detach=True, - ports=self.ports, - name=self.name, - volumes=self.volumes, - ) - atexit.register(self.cleanup_container) - print(f"Started container with ID: {self.container.id}") - time.sleep(5) # this helps to get SSH ready + print(f"Container {self.name} is already running.") + except NotFound: + try: + print(f"Creating new container: {self.name}") + self.container = self.client.containers.run( + self.image, + detach=True, + ports=self.ports, + name=self.name, + volumes=self.volumes, + ) + print(f"Started new container with ID: {self.container.id}") + except docker.errors.ImageNotFound as inf: + print(f"Image not found: {inf}") + except docker.errors.APIError as api_error: + print(f"Docker API error: {api_error}") + except Exception as e: + print(f"Unexpected error when starting container: {e}") + finally: + atexit.register(self.cleanup_container) + time.sleep(5) + self.start_health_check_thread() + + def health_check(self): + while self.running: + try: + if self.container: + self.container.reload() + if self.container.status != 'running': + print(f"Container {self.container.id} not running, attempting restart.") + self.check_and_reconnect() + time.sleep(self.health_check_interval) + except docker.errors.APIError as api_error: + print(f"Docker API error during health check: {api_error}") + except Exception as e: + print(f"Unexpected error in health check: {e}") + time.sleep(self.health_check_interval) + + def start_health_check_thread(self): + health_thread = threading.Thread(target=self.health_check, daemon=True) + health_thread.start() \ No newline at end of file diff --git a/python/helpers/print_style.py b/python/helpers/print_style.py index 8b64d3e93..245d63c8e 100644 --- a/python/helpers/print_style.py +++ b/python/helpers/print_style.py @@ -81,7 +81,7 @@ def _add_padding_if_needed(self): self.padding_added = True def _log_html(self, html): - with open(PrintStyle.log_file_path, "a") as f: # type: ignore + with open(PrintStyle.log_file_path, "a", encoding="utf-8") as f: f.write(html) @staticmethod diff --git a/python/helpers/rate_limiter.py b/python/helpers/rate_limiter.py index cfe75b367..0919b9512 100644 --- a/python/helpers/rate_limiter.py +++ b/python/helpers/rate_limiter.py @@ -32,7 +32,7 @@ def _wait_if_needed(self, current_time: float, new_input_tokens: int): while True: self._clean_old_records(current_time) calls, input_tokens, output_tokens = self._get_counts() - + wait_reasons = [] if self.max_calls > 0 and calls >= self.max_calls: wait_reasons.append("max calls") @@ -40,16 +40,20 @@ def _wait_if_needed(self, current_time: float, new_input_tokens: int): wait_reasons.append("max input tokens") if self.max_output_tokens > 0 and output_tokens >= self.max_output_tokens: wait_reasons.append("max output tokens") - + if not wait_reasons: break - - oldest_record = self.call_records[0] - wait_time = oldest_record.timestamp + self.window_seconds - current_time - if wait_time > 0: - PrintStyle(font_color="yellow", padding=True).print(f"Rate limit exceeded. Waiting for {wait_time:.2f} seconds due to: {', '.join(wait_reasons)}") - time.sleep(wait_time) - current_time = time.time() + + if self.call_records: # Check if call_records is not empty + oldest_record = self.call_records[0] + wait_time = oldest_record.timestamp + self.window_seconds - current_time + if wait_time > 0: + PrintStyle(font_color="yellow", padding=True).print( + f"Rate limit exceeded. Waiting for {wait_time:.2f} seconds due to: {', '.join(wait_reasons)}") + time.sleep(wait_time) + current_time = time.time() + else: + break def limit_call_and_input(self, input_token_count: int) -> CallRecord: current_time = time.time() diff --git a/python/tools/code_execution_tool.py b/python/tools/code_execution_tool.py index 8bf07a6bc..7062f5f7b 100644 --- a/python/tools/code_execution_tool.py +++ b/python/tools/code_execution_tool.py @@ -89,17 +89,83 @@ def terminal_session(self, command): return self.get_terminal_output() def get_terminal_output(self): - idle=0 - while True: + idle = 0 + max_idle_cycles = 300 # Example idle timeout cycles (adjust as needed) + full_output = "" + displayed_output = set() + last_partial_output = None # To track the last piece of output + prompt_detected = False + + while True: time.sleep(0.1) # Wait for some output to be generated - full_output, partial_output = self.state.shell.read_output() - - if self.agent.handle_intervention(): return full_output # wait for intervention and handle it, if paused - - if partial_output: - PrintStyle(font_color="#85C1E9").stream(partial_output) - idle=0 + output_tuple = self.state.shell.read_output() + + if self.agent.handle_intervention(): + return self.summarize_output(full_output) + + if output_tuple: + partial_output = output_tuple[0] if isinstance(output_tuple, tuple) else output_tuple + if partial_output: + # Stream all unique outputs to the user + if partial_output not in displayed_output: + PrintStyle(font_color="#85C1E9").stream(partial_output) + displayed_output.add(partial_output) + + full_output += partial_output + + # Reset idle counter if new output differs from last output + if partial_output != last_partial_output: + idle = 0 + last_partial_output = partial_output + else: + idle += 1 + + # Check for interaction prompts (like `[Y/n]`) + if any(prompt in partial_output.lower() for prompt in + ["[y/n]", "proceed (y/n)", "continue (y/n)", "do you want to continue", "yes/no"]): + prompt_detected = True + break + + # Check for the command prompt indicating the end of output + if "root@" in partial_output and partial_output.strip().endswith("#"): + break # End the loop when the prompt is detected + else: + idle += 1 else: - idle+=1 - if ( full_output and idle > 30 ) or ( not full_output and idle > 100 ): return full_output - \ No newline at end of file + idle += 1 + + # Exit the loop if no new output has been received for a while + if idle > max_idle_cycles: + PrintStyle(font_color="yellow", padding=True).print("Idle timeout reached. No new output detected.") + break + + return self.summarize_output(full_output, prompt_detected) + + def summarize_output(self, full_output, prompt_detected=False): + # Log the summary attempt + PrintStyle(font_color="blue", padding=True).print("Attempting to summarize output...") + + # Capture the last 2000 characters + summarized_output = full_output[-10000:].strip() + + # Check if summarized output is not empty and log + if summarized_output: + PrintStyle(font_color="green", padding=True).print( + f"Relevant output captured: {summarized_output[:100]}...") # Log a snippet of the output + else: + summarized_output = "No relevant output captured." + PrintStyle(font_color="red", padding=True).print("No relevant output captured; returning default message.") + + # If a prompt was detected, inform the agent + if prompt_detected: + PrintStyle(font_color="yellow", padding=True).print( + "Interaction prompt detected, returning to agent for handling.") + + return summarized_output + + + + + + + diff --git a/python/tools/knowledge_tool.py b/python/tools/knowledge_tool.py index a37419473..769ef99ab 100644 --- a/python/tools/knowledge_tool.py +++ b/python/tools/knowledge_tool.py @@ -1,44 +1,55 @@ import os -from agent import Agent -from . import online_knowledge_tool -from python.helpers import perplexity_search -from python.helpers import duckduckgo_search - -from . import memory_tool import concurrent.futures - from python.helpers.tool import Tool, Response from python.helpers import files -from python.helpers.print_style import PrintStyle +from python.helpers import perplexity_search, duckduckgo_search +from python.helpers.print_style import PrintStyle # Retain new import from main branch +from . import memory_tool class Knowledge(Tool): def execute(self, question="", **kwargs): with concurrent.futures.ThreadPoolExecutor() as executor: - # Schedule the two functions to be run in parallel - - # perplexity search, if API provided + # Schedule the different searches to be run in parallel if os.getenv("API_KEY_PERPLEXITY"): - perplexity = executor.submit(perplexity_search.perplexity_search, question) - else: + perplexity_future = executor.submit(perplexity_search.perplexity_search, question) + else: PrintStyle.hint("No API key provided for Perplexity. Skipping Perplexity search.") - perplexity = None - - - # duckduckgo search - duckduckgo = executor.submit(duckduckgo_search.search, question) - - # memory search - future_memory = executor.submit(memory_tool.search, self.agent, question) - - # Wait for both functions to complete - perplexity_result = (perplexity.result() if perplexity else "") or "" - duckduckgo_result = duckduckgo.result() - memory_result = future_memory.result() - - msg = files.read_file("prompts/tool.knowledge.response.md", - online_sources = perplexity_result + "\n\n" + str(duckduckgo_result), - memory = memory_result ) - - if self.agent.handle_intervention(msg): pass # wait for intervention and handle it, if paused - - return Response(message=msg, break_loop=False) \ No newline at end of file + perplexity_future = None + + duckduckgo_future = executor.submit(duckduckgo_search.search, question) + memory_future = executor.submit(memory_tool.search, self.agent, question) + wikipedia_future = executor.submit(fetch_wikipedia_content, question) + + # Collect the results + perplexity_result = (perplexity_future.result() if perplexity_future else "") or "" + duckduckgo_result = duckduckgo_future.result() + memory_result = memory_future.result() + wikipedia_result = wikipedia_future.result() + + msg = files.read_file( + "prompts/tool.knowledge.response.md", + online_sources=perplexity_result + "\n\n" + str(duckduckgo_result) + "\n\n" + wikipedia_result, + memory=memory_result + ) + + if self.agent.handle_intervention(msg): # wait for intervention and handle it, if paused + pass + + return Response(message=msg, break_loop=False) + +def fetch_wikipedia_content(query): + try: + import wikipedia + except ImportError: + return "Error: The 'wikipedia' library is not installed. Please install it using 'pip install wikipedia'." + + try: + summary = wikipedia.summary(query) + return summary + except wikipedia.exceptions.DisambiguationError as e: + options = e.options + return f"Disambiguation Error: The query '{query}' may refer to multiple topics. Options include: {options[:3]}..." # Limiting to the first 3 options for brevity + except wikipedia.exceptions.PageError: + return "Page not found." + except Exception as e: + return f"An error occurred: {str(e)}"