Skip to content

Commit 754837d

Browse files
committed
e2b scaling example, simple
1 parent cbf588e commit 754837d

12 files changed

+157
-112
lines changed

community/e2b/README.md

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,88 @@
1-
# Restack AI - Quickstart
1+
# E2B Code Execution with Restack AI
22

3-
This repository contains a quickstart for Restack.
4-
It demonstrates how to set up a basic workflow and functions.
3+
This repository demonstrates how to use [E2B](https://e2b.dev) for code execution at scale with [Restack](https://docs.restack.io/). E2B provides secure sandboxed environments for code execution, while Restack handles workflow orchestration and scaling.
54

65
## Prerequisites
76

87
- Docker (for running Restack)
98
- Python 3.10 or higher
9+
- E2B API key ([Get it here](https://e2b.dev/docs/getting-started/api-key))
10+
- OpenAI API key (for AI functionalities)
11+
12+
## Configuration
13+
14+
1. Copy `.env.Example` to `.env`
15+
2. Add your API keys:
16+
```
17+
E2B_API_KEY=<your-e2b-api-key>
18+
OPENAI_API_KEY=<your-openai-api-key>
19+
```
1020

1121
## Start Restack
1222

13-
To start the Restack, use the following Docker command:
23+
To start Restack locally, use the following Docker command:
1424

1525
```bash
1626
docker run -d --pull always --name restack -p 5233:5233 -p 6233:6233 -p 7233:7233 ghcr.io/restackio/restack:main
1727
```
1828

19-
## Start python shell
29+
## Setup Python Environment
2030

2131
```bash
2232
poetry env use 3.10 && poetry shell
2333
```
2434

25-
## Install dependencies
35+
## Install Dependencies
2636

2737
```bash
2838
poetry install
2939
```
3040

41+
For IDE setup (optional):
3142
```bash
32-
poetry env info # Optional: copy the interpreter path to use in your IDE (e.g. Cursor, VSCode, etc.)
43+
poetry env info # Copy the interpreter path to use in your IDE (e.g. Cursor, VSCode)
3344
```
3445

46+
Start the development server:
3547
```bash
3648
poetry run dev
3749
```
3850

39-
## Run workflows
51+
## Running Workflows
4052

41-
### from UI
53+
### Via UI
4254

43-
You can run workflows from the UI by clicking the "Run" button.
55+
Access the Restack UI to run and monitor workflows:
4456

45-
![Run workflows from UI](./screenshot-quickstart.png)
57+
![Restack Workflow UI](./ui-e2b-restack-code-execution.png)
4658

47-
### from API
59+
### Via API
4860

49-
You can run workflows from the API by using the generated endpoint:
61+
Execute workflows through the Restack API endpoint:
5062

5163
`POST http://localhost:6233/api/workflows/GreetingWorkflow`
5264

53-
### from any client
65+
### Via Client
5466

55-
You can run workflows with any client connected to Restack, for example:
67+
Run workflows programmatically using the Python client:
5668

5769
```bash
5870
poetry run schedule
5971
```
6072

61-
executes `schedule_workflow.py` which will connect to Restack and execute the `GreetingWorkflow` workflow.
73+
This executes `schedule_workflow.py` which connects to Restack and runs the `GreetingWorkflow`.
74+
75+
## Cloud Deployment
76+
77+
### Restack Cloud
78+
Deploy your workflows on Restack Cloud:
79+
1. Create an account at [Restack Console](https://console.restack.io)
80+
2. Create a stack and import the cloned repository
81+
3. Reference the Dockerfile path `community/e2b/Dockerfile` and the build context `community/e2b`
82+
4. Set the environment variables for E2B and OpenAI. Restack environment variables are set automatically.
83+
6284

63-
## Deploy on Restack Cloud
85+
## Learn More
86+
- [Restack Documentation](https://docs.restack.io)
87+
- [E2B Documentation](https://e2b.dev/docs)
6488

65-
To deploy the application on Restack, you can create an account at [https://console.restack.io](https://console.restack.io)

community/e2b/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Project metadata
22
[tool.poetry]
3-
name = "quickstart"
3+
name = "e2b-restack"
44
version = "0.0.1"
5-
description = "A quickstart for Restack"
5+
description = "A simple example for e2b with Restack"
66
authors = [
77
"Restack Team <[email protected]>",
88
]

community/e2b/schedule_calendar.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

community/e2b/schedule_interval.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

community/e2b/schedule_workflow.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import asyncio
22
import time
33
from restack_ai import Restack
4-
from src.workflows.workflow import GreetingWorkflowInput
5-
async def main():
4+
from src.workflows.many_code_executions import ManyCodeExecutionWorkflow, ManyCodeExecutionWorkflowInput
65

6+
async def main():
77
client = Restack()
88

9-
workflow_id = f"{int(time.time() * 1000)}-GreetingWorkflow"
9+
workflow_id = f"{int(time.time() * 1000)}-ManyCodeExecutionWorkflow"
1010
run_id = await client.schedule_workflow(
11-
workflow_name="GreetingWorkflow",
11+
workflow_name="ManyCodeExecutionWorkflow",
1212
workflow_id=workflow_id,
13-
input=GreetingWorkflowInput(name="Bob")
13+
input=ManyCodeExecutionWorkflowInput()
1414
)
1515

1616
await client.get_workflow_result(

community/e2b/src/functions/e2b_execute_python.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ async def e2b_execute_python(input: ExecutePythonInput) -> str:
1515
# Create a new sandbox instance with a 60 second timeout
1616
sandbox = Sandbox(timeout=60)
1717
execution = sandbox.run_code(input.code)
18-
return execution.logs
18+
result = execution.text
19+
return result
1920
except Exception as e:
2021
log.error("e2b_execute_python function failed", error=e)
2122
raise e

community/e2b/src/functions/openai_tool_call.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
from openai import OpenAI
55

66
class OpenaiToolCallInput(BaseModel):
7-
user_content: str
8-
system_content: str = "You are an expert in Python. You can execute Python code and return the result."
9-
tools: list[str]
7+
user_content: str | None = None
8+
system_content: str | None = None
9+
tools: list[dict] = []
1010
model: str = "gpt-4"
1111
messages: list[dict] = []
1212

@@ -20,12 +20,14 @@ async def openai_tool_call(input: OpenaiToolCallInput) -> dict:
2020
messages = input.messages.copy() if input.messages else [
2121
{"role": "system", "content": input.system_content}
2222
]
23-
messages.append({"role": "user", "content": input.user_content})
23+
24+
if input.user_content:
25+
messages.append({"role": "user", "content": input.user_content})
2426

2527
response = client.chat.completions.create(
2628
model=input.model,
2729
messages=messages,
28-
tools=input.tools,
30+
**({"tools": input.tools} if input.tools else {})
2931
)
3032

3133
response_message = response.choices[0].message

community/e2b/src/services.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from src.client import client
77
from src.functions.e2b_execute_python import e2b_execute_python
88
from src.functions.openai_tool_call import openai_tool_call
9-
from src.workflows.workflow import E2BWorkflow
10-
9+
from src.workflows.code_execution import CodeExecutionWorkflow
10+
from src.workflows.many_code_executions import ManyCodeExecutionWorkflow
1111

1212
async def main():
1313

1414
await client.start_service(
15-
workflows=[E2BWorkflow],
15+
workflows=[CodeExecutionWorkflow, ManyCodeExecutionWorkflow],
1616
functions=[e2b_execute_python, openai_tool_call]
1717
)
1818

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from pydantic import BaseModel, Field
2+
from restack_ai.workflow import workflow, import_functions
3+
import json
4+
5+
with import_functions():
6+
from src.functions.e2b_execute_python import e2b_execute_python, ExecutePythonInput
7+
from src.functions.openai_tool_call import openai_tool_call, OpenaiToolCallInput
8+
9+
class CodeExecutionWorkflowInput(BaseModel):
10+
user_content: str = Field(default='Calculate how many r\'s are in the word \'strawberry\'')
11+
system_content: str = Field(default='You are an expert in Python. You can execute Python code and return the result.')
12+
13+
class CodeExecutionWorkflowOutput(BaseModel):
14+
content: str
15+
16+
@workflow.defn()
17+
class CodeExecutionWorkflow:
18+
@workflow.run
19+
async def run(self, input: CodeExecutionWorkflowInput) -> CodeExecutionWorkflowOutput:
20+
21+
messages = []
22+
while True:
23+
llm_response = await workflow.step(openai_tool_call, input=OpenaiToolCallInput(
24+
model="gpt-4o-mini",
25+
user_content=input.user_content if not messages else None,
26+
system_content=input.system_content if not messages else None,
27+
messages=messages,
28+
tools=[{
29+
"type": "function",
30+
"function": {
31+
"name": "execute_python",
32+
"description": "Execute python code in a Jupyter notebook cell and return result",
33+
"parameters": {
34+
"type": "object",
35+
"properties": {
36+
"code": {"type": "string", "description": "The python code to execute"}
37+
},
38+
"required": ["code"]
39+
}
40+
}
41+
}] if not messages else []
42+
))
43+
44+
messages = llm_response["messages"]
45+
46+
if not llm_response["response"].get("tool_calls"):
47+
return CodeExecutionWorkflowOutput(content=llm_response["response"].get("content"))
48+
49+
tool_call = llm_response["response"]["tool_calls"][0]
50+
code = json.loads(tool_call["function"]["arguments"])["code"]
51+
code_execution_output = await workflow.step(e2b_execute_python, input=ExecutePythonInput(code=code))
52+
53+
messages.append({
54+
"role": "tool",
55+
"name": "execute_python",
56+
"content": str(code_execution_output),
57+
"tool_call_id": tool_call["id"]
58+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pydantic import BaseModel, Field
2+
from restack_ai.workflow import workflow
3+
from typing import List, Dict
4+
from src.workflows.code_execution import CodeExecutionWorkflow, CodeExecutionWorkflowInput
5+
import asyncio
6+
7+
8+
class ManyCodeExecutionWorkflowInput(BaseModel):
9+
tasks: List[str] = Field(default=[
10+
'Calculate how many r\'s are in the word \'strawberry\'',
11+
'Find the sum of numbers from 1 to 100',
12+
'Calculate the factorial of 5',
13+
'Count the vowels in the word \'elephant\'',
14+
'Generate a list of first 10 Fibonacci numbers',
15+
'Check if 17 is a prime number',
16+
'Convert 25 Celsius to Fahrenheit',
17+
'Calculate the area of a circle with radius 5',
18+
'Find all even numbers between 1 and 20',
19+
'Calculate the length of the hypotenuse for a right triangle with sides 3 and 4'
20+
])
21+
22+
class ManyCodeExecutionWorkflowOutput(BaseModel):
23+
results: List[str] = Field(default=[])
24+
25+
26+
@workflow.defn()
27+
class ManyCodeExecutionWorkflow:
28+
@workflow.run
29+
async def run(self, input: ManyCodeExecutionWorkflowInput) -> ManyCodeExecutionWorkflowOutput:
30+
tasks = [
31+
workflow.child_execute(
32+
CodeExecutionWorkflow,
33+
workflow_id=f"code_execution_{i}",
34+
input=CodeExecutionWorkflowInput(user_content=task)
35+
)
36+
for i, task in enumerate(input.tasks)
37+
]
38+
results = await asyncio.gather(*tasks)
39+
return ManyCodeExecutionWorkflowOutput(results=[r.content for r in results])

community/e2b/src/workflows/workflow.py

Lines changed: 0 additions & 20 deletions
This file was deleted.
Loading

0 commit comments

Comments
 (0)