diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..36200e3 --- /dev/null +++ b/.example.env @@ -0,0 +1 @@ +JUPYTER_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4a3021b..74a453f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ uploads downloads toolkilt/.venv/ toolkilt/__pycache__/ -toolkilt/tools/__pycache__/ \ No newline at end of file +toolkilt/tools/__pycache__/ +**/notebooks/ +**/.env* +**/__pycache__/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index aecb4f5..6ebd684 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,10 @@ RUN pip install fastapi uvicorn python-multipart # Copy the API script to the root directory COPY api.py /home/jovyan/api.py +COPY README.md /home/jovyan/README.md # Expose port 8888 for the Jupyter Notebook EXPOSE 8888 # Expose an additional port for the FastAPI server -EXPOSE 8000 \ No newline at end of file +EXPOSE 8100 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..49d3c69 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: build run test + +build: + docker build --push -t ghcr.io/enso-labs/interpreter:latest . + +docker_run: + docker run -d --name interpreter ghcr.io/enso-labs/interpreter:latest + +test: + python -m unittest discover \ No newline at end of file diff --git a/README.md b/README.md index e26f4ce..d1d803e 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,20 @@ Unlock the potential of executing Python code safely and efficiently with the Pr Our sandbox is designed with security as a top priority. By isolating execution environments, we mitigate risks associated with running untrusted code. This makes it an ideal solution for developers and organizations who need a safe and controlled way to execute scripts, test code snippets, or provide coding functionalities within their applications. Key Features: -- đŸ›Ąī¸ **Secure Execution:** Run Python code in a sandboxed environment to ensure the applications remains secure and unaffected. -- 📁 **File Management:** Upload and manage files within isolated sessions to maintain data integrity and security. -- đŸ“Ļ **Package Installation:** Install required Python packages per session without affecting the global environment. -- đŸ•šī¸ **Session Management:** Efficiently create, manage, and terminate sessions to maintain clean and organized execution spaces. + +- đŸ›Ąī¸ **Secure Execution:** Run Python code in a sandboxed environment to ensure the applications remains secure and unaffected. +- 📁 **File Management:** Upload and manage files within isolated sessions to maintain data integrity and security. +- đŸ“Ļ **Package Installation:** Install required Python packages per session without affecting the global environment. +- đŸ•šī¸ **Session Management:** Efficiently create, manage, and terminate sessions to maintain clean and organized execution spaces. ### Start Interpreter + ```bash docker-compose up --build ``` ### Test the Langchain Toolkit + ```bash ## Change Directory cd toolkit @@ -43,7 +46,7 @@ python interpreter.py Simple Example ```bash -curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/execute -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "code": "print(\"Hello from Interpreter!\")" }' @@ -55,7 +58,7 @@ curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" - Example curl Request (Verify will error when numpy is not installed): ```bash -curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/execute -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "code": "import os\nimport numpy as np\na = int(os.getenv(\"VAR_A\"))\nb = int(os.getenv(\"VAR_B\"))\nc = int(os.getenv(\"VAR_C\"))\narray = np.array([a, b, c])\nresult = np.sum(array)\nprint(f\"Result of summing [{a}, {b}, {c}] is: {result}\")", "env": { @@ -72,7 +75,7 @@ curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" - Example curl Request (Install numpy and execute the code): ```bash -curl -X POST http://localhost:8001/install -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/install -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "packages": ["numpy"] }' @@ -84,7 +87,7 @@ curl -X POST http://localhost:8001/install -H "Content-Type: application/json" - Execute numpy with Env vars ```bash -curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/execute -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "code": "import os\nimport numpy as np\na = int(os.getenv(\"VAR_A\"))\nb = int(os.getenv(\"VAR_B\"))\nc = int(os.getenv(\"VAR_C\"))\narray = np.array([a, b, c])\nresult = np.sum(array)\nprint(f\"Result of summing [{a}, {b}, {c}] is: {result}\")", "env": { @@ -101,14 +104,14 @@ curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" - Example curl Request (Verify numpy has been uninstalled): ```bash -curl -X POST http://localhost:8001/terminate -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/terminate -H "Content-Type: application/json" -d '{ "session_id": "your_session_id" }' ## Result # {"status":"success","message":"Session your_session_id terminated successfully."} -curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/execute -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "code": "import os\nimport numpy as np\na = int(os.getenv(\"VAR_A\"))\nb = int(os.getenv(\"VAR_B\"))\nc = int(os.getenv(\"VAR_C\"))\narray = np.array([a, b, c])\nresult = np.sum(array)\nprint(f\"Result of summing [{a}, {b}, {c}] is: {result}\")", "env": { @@ -127,7 +130,7 @@ curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" - Upload a file for a specific session. ```bash -curl -X POST "http://localhost:8001/upload" \ +curl -X POST "http://localhost:8100/upload" \ -H "accept: application/json" \ -F "session_id=your_session_id" \ -F "file=@data/AAPL.csv" @@ -139,7 +142,7 @@ curl -X POST "http://localhost:8001/upload" \ Install pandas pacakage for created session to interact with csv. ```bash -curl -X POST http://localhost:8001/install -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/install -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "packages": ["pandas"] }' @@ -151,7 +154,7 @@ curl -X POST http://localhost:8001/install -H "Content-Type: application/json" - Execute to interact with csv ```bash -curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/execute -H "Content-Type: application/json" -d '{ "session_id": "your_session_id", "code": "import pandas as pd\nfile_path = \"/tmp/your_session_id/AAPL.csv\"\ndf = pd.read_csv(file_path)\nfirst_row = df.iloc[0]\nprint(first_row.to_json())" }' @@ -163,7 +166,7 @@ curl -X POST http://localhost:8001/execute -H "Content-Type: application/json" - Download file from session ```bash -curl -X GET "http://localhost:8001/download?session_id=your_session_id&filename=AAPL.csv" -o AAPL_downloaded.csv +curl -X GET "http://localhost:8100/download?session_id=your_session_id&filename=AAPL.csv" -o AAPL_downloaded.csv ## Result # File downloaded to workspace @@ -172,10 +175,21 @@ curl -X GET "http://localhost:8001/download?session_id=your_session_id&filename= Terminate session to uninstall pacakges and remove files. ```bash -curl -X POST http://localhost:8001/terminate -H "Content-Type: application/json" -d '{ +curl -X POST http://localhost:8100/terminate -H "Content-Type: application/json" -d '{ "session_id": "your_session_id" }' ## Result # {"status":"success","message":"Session your_session_id terminated successfully."} -``` \ No newline at end of file +``` + +```bash +docker run -d \ + --name jupyter \ + --restart always \ + --env-file .env.jupyter \ + -p 8888:8888 \ + -v "$PWD/notebooks:/home/jovyan/work" \ + -v jupyter_data:/home/jovyan/.jupyter \ + jupyter/base-notebook:latest +``` diff --git a/api.py b/api.py index c74bbaa..3cd1616 100644 --- a/api.py +++ b/api.py @@ -6,7 +6,7 @@ import shutil from typing import List, Dict -app = FastAPI() +app = FastAPI(name="Enso Labs Interpreter", docs_url="/") class CodeExecutionRequest(BaseModel): session_id: str @@ -141,4 +141,4 @@ def download_file(session_id: str, filename: str): if __name__ == '__main__': import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) + uvicorn.run(app, host=os.getenv("HOST", "0.0.0.0"), port=int(os.getenv("PORT", 8100))) diff --git a/docker-compose.yml b/docker-compose.yml index 2c1ed33..bad81da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,17 @@ -version: '3' - services: interpreter: container_name: interpreter - image: promptengineers/interpreter:latest + # image: ghcr.io/enso-labs/interpreter:latest build: . restart: always ports: - "8888:8888" - - "8001:8000" + - "8100:8100" environment: - JUPYTER_ENABLE_LAB=yes + - JUPYTER_TOKEN=${JUPYTER_TOKEN} - PYTHONPATH=/home/jovyan - entrypoint: ["sh", "-c", "start-notebook.sh --NotebookApp.token='' & uvicorn api:app --host 0.0.0.0 --port 8000"] + entrypoint: ["sh", "-c", "start-notebook.sh --NotebookApp.token=${JUPYTER_TOKEN} & uvicorn api:app --host 0.0.0.0 --port 8100"] volumes: - ./uploads:/tmp + - ./notebooks:/home/jovyan/.ipynb_checkpoints \ No newline at end of file diff --git a/toolkit/interpreter.py b/toolkit/interpreter.py index e33b4c0..2ae1cb7 100644 --- a/toolkit/interpreter.py +++ b/toolkit/interpreter.py @@ -1,28 +1,30 @@ +import os from typing import List +from pydantic import Field from langchain_core.tools import BaseToolkit -from langchain_core.pydantic_v1 import Field -from langchain_community.tools import BaseTool +from langchain_community.tools import StructuredTool from tools import Interpreter class InterpreterToolkit(BaseToolkit): - """Toolkit for the interpreter.""" + """Toolkit for the interpreter.""" - api_url: str = Field(default="http://localhost:8001") + api_url: str = Field(default=os.getenv("INTERPRETER_URL", "http://localhost:8100")) - class Config: - """Pydantic config.""" - arbitrary_types_allowed = True + class Config: + """Pydantic config.""" + arbitrary_types_allowed = True - def get_tools(self) -> List[BaseTool]: - """Get the tools in the toolkit.""" - toolkit = Interpreter(api_url=self.api_url).toolkit() - return toolkit + def get_tools(self) -> List[StructuredTool]: + """Get the tools in the toolkit.""" + toolkit = Interpreter(api_url=self.api_url).toolkit() + return toolkit if __name__ == "__main__": - toolkit = InterpreterToolkit(api_url="http://localhost:8001") + toolkit = InterpreterToolkit(api_url=os.getenv("INTERPRETER_URL", "http://localhost:8100")) tools = toolkit.get_tools() result = tools[0].run({"session_id": "test", "code": "print('Hello, World!')"}) print(">>> Tools: ", tools) + print("\n\n") print(">>> Result: ", result) \ No newline at end of file diff --git a/toolkit/tools/__init__.py b/toolkit/tools/__init__.py index 4badc0e..1f64473 100644 --- a/toolkit/tools/__init__.py +++ b/toolkit/tools/__init__.py @@ -1,8 +1,10 @@ +import os import httpx import logging from typing import List +from pydantic import BaseModel, Field + from langchain.tools import StructuredTool -from langchain_core.pydantic_v1 import BaseModel, Field from langchain_core.tools import ToolException ######################################################## @@ -32,7 +34,7 @@ class DownloadSchema(BaseModel): ## Class ######################################################## class Interpreter: - def __init__(self, api_url: str = "http://localhost:8000"): + def __init__(self, api_url: str = os.getenv("INTERPRETER_URL", "http://localhost:8100")): self.api_url = api_url def install(self, session_id: str, packages: List[str]): @@ -256,8 +258,5 @@ def toolkit(self) -> List[StructuredTool]: ## Test ######################################################## if __name__ == "__main__": - result = Interpreter(api_url="http://localhost:8001").execute().run({ - "session_id": "test", - "code": "print('Hello, World!')" - }) + result = Interpreter().execute(session_id="test", code="print('Hello, World!')") print(result) \ No newline at end of file