Merge pull request #1 from ErikKaum/example/alternative-docker-interface

Example/alternative docker interface
This commit is contained in:
Aymeric Roucher 2024-12-18 12:07:30 +01:00 committed by GitHub
commit 20c6397341
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 331 additions and 2 deletions

138
.gitignore vendored
View File

@ -13,13 +13,147 @@ outputs
# VS Code
.vscode
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# .python-version
# pipenv
#Pipfile.lock
# UV
#uv.lock
# poetry
#poetry.lock
# pdm
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Jupyter Notebook
.ipynb_checkpoints
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
#.idea/

28
Dockerfile Normal file
View File

@ -0,0 +1,28 @@
# Base Python image
FROM python:3.12-slim
# Set working directory
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
gcc \
g++ \
zlib1g-dev \
libjpeg-dev \
libpng-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy package files
COPY . /app/
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Install the package
RUN pip install -e .
COPY server.py /app/server.py
CMD ["python", "/app/server.py"]

View File

@ -0,0 +1,39 @@
from agents.search import DuckDuckGoSearchTool
from agents.docker_alternative import DockerPythonInterpreter
test = """
from agents.tools import Tool
class DummyTool(Tool):
name = "echo"
description = '''Perform a web search based on your query (think a Google search) then returns the top search results as a list of dict elements.
Each result has keys 'title', 'href' and 'body'.'''
inputs = {
"cmd": {"type": "string", "description": "The search query to perform."}
}
output_type = "any"
def forward(self, cmd: str) -> str:
return cmd
"""
container = DockerPythonInterpreter()
output = container.execute("x = 5")
print(f"first output: {output}")
output = container.execute("print(x)")
print(f"second output: {output}")
breakpoint()
print("---------")
output = container.execute(test)
print(output)
output = container.execute("res = DummyTool(cmd='echo this'); print(res)")
print(output)
container.stop()

14
examples/dummytool.py Normal file
View File

@ -0,0 +1,14 @@
from agents.tools import Tool
class DummyTool(Tool):
name = "echo"
description = """Perform a web search based on your query (think a Google search) then returns the top search results as a list of dict elements.
Each result has keys 'title', 'href' and 'body'."""
inputs = {
"cmd": {"type": "string", "description": "The search query to perform."}
}
output_type = "any"
def forward(self, cmd: str) -> str:
return cmd

46
server.py Normal file
View File

@ -0,0 +1,46 @@
import socket
import sys
import traceback
import io
exec_globals = {}
exec_locals = {}
def execute_code(code):
stdout = io.StringIO()
stderr = io.StringIO()
sys.stdout = stdout
sys.stderr = stderr
try:
exec(code, exec_globals, exec_locals)
except Exception as e:
traceback.print_exc(file=stderr)
output = stdout.getvalue()
error = stderr.getvalue()
# Restore original stdout and stderr
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
return output + error
def start_server(host='0.0.0.0', port=65432):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((host, port))
s.listen()
print(f"Server listening on {host}:{port}")
while True:
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
data = conn.recv(1024)
if not data:
break
code = data.decode('utf-8')
output = execute_code(code)
conn.sendall(output.encode('utf-8'))
if __name__ == "__main__":
start_server()

View File

@ -0,0 +1,68 @@
import docker
from typing import List, Optional
import warnings
import socket
from agents.tools import Tool
class DockerPythonInterpreter:
def __init__(self):
self.container = None
try:
self.client = docker.from_env()
self.client.ping()
except docker.errors.DockerException:
raise RuntimeError(
"Could not connect to Docker daemon. Please ensure Docker is installed and running."
)
try:
self.container = self.client.containers.run(
"pyrunner:latest",
ports={'65432/tcp': 65432},
detach=True,
remove=True,
)
except docker.errors.DockerException as e:
raise RuntimeError(f"Failed to create Docker container: {e}")
def stop(self):
"""Cleanup: Stop and remove container when object is destroyed"""
if self.container:
try:
self.container.kill() # can consider .stop(), but this is faster
except Exception as e:
warnings.warn(f"Failed to stop Docker container: {e}")
def execute(self, code: str, tools: Optional[List[Tool]] = None) -> str:
"""
Execute Python code in the container and return stdout and stderr
"""
if tools != None:
tool_instance = tools[0]()
import_code = f"""
module_path = '{tool_instance.__class__.__module__}'
class_name = '{tool_instance.__class__.__name__}'
import importlib
module = importlib.import_module(module_path)
web_search = getattr(module, class_name)()
"""
code = import_code + "\n" + code
try:
# Connect to the server running inside the container
with socket.create_connection(('localhost', 65432)) as sock:
sock.sendall(code.encode('utf-8'))
output = sock.recv(4096)
return output.decode('utf-8')
except Exception as e:
return f"Error executing code: {str(e)}"
__all__ = ["DockerPythonInterpreter"]