Skip to content

Commit 539d5c1

Browse files
committed
update production demo with endpoints
1 parent 0def8e1 commit 539d5c1

File tree

11 files changed

+103
-70
lines changed

11 files changed

+103
-70
lines changed

production_demo/README.md

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Restack AI - Production Example
22

3-
This repository contains a simple example project to help you scale with the Restack AI.
3+
This repository contains a simple example project to help you scale with Restack AI.
44
It demonstrates how to scale reliably to millions of workflows on a local machine with a local LLM provider.
55

66
## Walkthrough video
@@ -60,85 +60,93 @@ And for each child workflow, for each step you can see how long the function sta
6060

6161
## Prerequisites
6262

63-
- Python 3.8 or higher
63+
- Python 3.10 or higher
6464
- Poetry (for dependency management)
6565
- Docker (for running the Restack services)
6666
- Local LLM provider (we use LMStudio and a Meta Llama 3.1 8B Instruct 4bit model in this example)
6767

68-
## Usage
68+
## Start LM stduio for local LLM provider
6969

70-
0. Start LM Studio and start local server with Meta Llama 3.1 8B Instruct 4bit model
70+
Start local server with Meta Llama 3.1 8B Instruct 4bit model
7171

7272
https://lmstudio.ai
7373

74-
1. Run Restack local engine with Docker:
74+
## Prerequisites
7575

76-
```bash
77-
docker run -d --pull always --name restack -p 5233:5233 -p 6233:6233 -p 7233:7233 ghcr.io/restackio/restack:main
78-
```
76+
- Docker (for running Restack)
77+
- Python 3.10 or higher
7978

80-
2. Open the web UI to see the workflows:
79+
## Start Restack
8180

82-
```bash
83-
http://localhost:5233
84-
```
81+
To start the Restack, use the following Docker command:
8582

86-
3. Clone this repository:
83+
```bash
84+
docker run -d --pull always --name restack -p 5233:5233 -p 6233:6233 -p 7233:7233 ghcr.io/restackio/restack:main
85+
```
8786

88-
```bash
89-
git clone https://github.com/restackio/examples-python
90-
cd examples-python/examples/production_demo
91-
```
87+
## Start python shell
9288

93-
4. Install dependencies using Poetry:
89+
```bash
90+
poetry env use 3.10 && poetry shell
91+
```
9492

95-
```bash
96-
poetry env use 3.12
97-
```
93+
## Install dependencies
94+
95+
```bash
96+
poetry install
97+
```
98+
99+
```bash
100+
poetry env info # Optional: copy the interpreter path to use in your IDE (e.g. Cursor, VSCode, etc.)
101+
```
102+
103+
```bash
104+
poetry run dev
105+
```
98106

99-
```bash
100-
poetry shell
101-
```
107+
## Run workflows
102108

103-
```bash
104-
poetry install
105-
```
109+
### from UI
106110

107-
```bash
108-
poetry env info # Optional: copy the interpreter path to use in your IDE (e.g. Cursor, VSCode, etc.)
109-
```
111+
You can run workflows from the UI by clicking the "Run" button.
110112

111-
5. Run the services:
113+
![Run workflows from UI](./ui-endpoints.png)
112114

113-
```bash
114-
poetry run dev
115-
```
115+
### from API
116116

117-
This will start the Restack service with the defined workflows and functions.
117+
You can run one workflow from the API by using the generated endpoint:
118118

119-
6. In a new terminal, schedule the workflow:
119+
`POST http://localhost:6233/api/workflows/ChildWorkflow`
120120

121-
```bash
122-
poetry shell
123-
```
121+
or multiple workflows by using the generated endpoint:
124122

125-
```bash
126-
poetry run workflow
127-
```
123+
`POST http://localhost:6233/api/workflows/ExampleWorkflow`
128124

129-
This will schedule the ExampleWorkflow` and print the result.
125+
### from any client
130126

131-
7. Optionally, schedule the workflow to run on a interval:
127+
You can run workflows with any client connected to Restack, for example:
128+
129+
```bash
130+
poetry run schedule
131+
```
132+
133+
executes `schedule_workflow.py` which will connect to Restack and execute the `ChildWorkflow` workflow.
134+
135+
```bash
136+
poetry run scale
137+
```
138+
139+
executes `schedule_scale.py` which will connect to Restack and execute the `ExampleWorkflow` workflow.
140+
141+
```bash
142+
poetry run interval
143+
```
132144

133-
```bash
134-
poetry run interval
135-
```
145+
executes `schedule_interval.py` which will connect to Restack and execute the `ChildWorkflow` workflow every second.
136146

137-
8. Optionally, schedule a parent workflow to run 50 child workflows all at once:
147+
## Deploy on Restack Cloud
138148

139-
```bash
140-
poetry run scale
141-
```
149+
To deploy the application on Restack, you can create an account at [https://console.restack.io](https://console.restack.io)
142150

143151
## Project Structure
144152

@@ -149,7 +157,7 @@ https://lmstudio.ai
149157
- `services.py`: Sets up and runs the Restack services
150158
- `schedule_workflow.py`: Example script to schedule and run a workflow
151159
- `schedule_interval.py`: Example script to schedule and a workflow every second
152-
- `schedule_scale.py`: Example script to schedule and run 100 workflows at once
160+
- `schedule_scale.py`: Example script to schedule and run 50 workflows at once
153161

154162
# Deployment
155163

production_demo/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ packages = [{include = "src"}]
1111

1212
[tool.poetry.dependencies]
1313
python = ">=3.10,<4.0"
14-
restack-ai = "^0.0.48"
14+
restack-ai = "^0.0.52"
1515
watchfiles = "^1.0.0"
1616
openai = "^1.57.2"
17+
pydantic = "^2.10.5"
1718

1819
[tool.poetry.dev-dependencies]
1920
pytest = "6.2" # Optional: Add if you want to include tests in your example

production_demo/schedule_scale.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import time
33
from restack_ai import Restack
44

5+
from src.workflows.workflow import ExampleWorkflowInput
6+
57
async def main():
68

79
client = Restack()
@@ -10,6 +12,7 @@ async def main():
1012
await client.schedule_workflow(
1113
workflow_name="ExampleWorkflow",
1214
workflow_id=workflow_id,
15+
input=ExampleWorkflowInput(amount=50)
1316
)
1417

1518
exit(0)

production_demo/src/functions/evaluate.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from restack_ai.function import function, FunctionFailure, log
22
from openai import OpenAI
3+
from pydantic import BaseModel
4+
5+
class EvaluateInput(BaseModel):
6+
generated_text: str
37

48
@function.defn()
5-
async def llm_evaluate(generated_text: str) -> str:
9+
async def llm_evaluate(input: EvaluateInput) -> str:
610
try:
711
client = OpenAI(base_url="http://192.168.4.142:1234/v1/",api_key="llmstudio")
812
except Exception as e:
@@ -12,7 +16,7 @@ async def llm_evaluate(generated_text: str) -> str:
1216
prompt = (
1317
f"Evaluate the following joke for humor, creativity, and originality. "
1418
f"Provide a score out of 10 for each category for your score.\n\n"
15-
f"Joke: {generated_text}\n\n"
19+
f"Joke: {input.generated_text}\n\n"
1620
f"Response format:\n"
1721
f"Humor: [score]/10"
1822
f"Creativity: [score]/10"

production_demo/src/functions/function.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
tries = 0
44

5+
from pydantic import BaseModel
6+
7+
class ExampleFunctionInput(BaseModel):
8+
name: str
9+
510
@function.defn()
6-
async def example_function(input: str) -> str:
11+
async def example_function(input: ExampleFunctionInput) -> str:
712
try:
813
global tries
914

@@ -12,7 +17,7 @@ async def example_function(input: str) -> str:
1217
raise FunctionFailure(message="Simulated failure", non_retryable=False)
1318

1419
log.info("example function started", input=input)
15-
return f"Hello, {input}!"
20+
return f"Hello, {input.name}!"
1621
except Exception as e:
1722
log.error("example function failed", error=e)
1823
raise e

production_demo/src/functions/generate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from restack_ai.function import function, FunctionFailure, log
22
from openai import OpenAI
33

4+
from pydantic import BaseModel
5+
6+
class GenerateInput(BaseModel):
7+
prompt: str
8+
49
@function.defn()
5-
async def llm_generate(prompt: str) -> str:
10+
async def llm_generate(input: GenerateInput) -> str:
611

712
try:
813
client = OpenAI(base_url="http://192.168.4.142:1234/v1/",api_key="llmstudio")
@@ -16,7 +21,7 @@ async def llm_generate(prompt: str) -> str:
1621
messages=[
1722
{
1823
"role": "user",
19-
"content": prompt
24+
"content": input.prompt
2025
}
2126
],
2227
temperature=0.5,

production_demo/src/services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from src.functions.evaluate import llm_evaluate
1111

1212
from src.workflows.workflow import ExampleWorkflow, ChildWorkflow
13-
13+
import webbrowser
1414

1515

1616
async def main():
@@ -43,5 +43,6 @@ def run_services():
4343
def watch_services():
4444
watch_path = os.getcwd()
4545
print(f"Watching {watch_path} and its subdirectories for changes...")
46+
webbrowser.open("http://localhost:5233")
4647
run_process(watch_path, recursive=True, target=run_services)
4748

production_demo/src/workflows/child.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
from datetime import timedelta
2-
2+
from pydantic import BaseModel, Field
33
from restack_ai.workflow import workflow, import_functions, log
44

55
with import_functions():
66
from src.functions.function import example_function
77
from src.functions.generate import llm_generate
88
from src.functions.evaluate import llm_evaluate
99

10+
class ChildWorkflowInput(BaseModel):
11+
name: str = Field(default='John Doe')
12+
1013
@workflow.defn()
1114
class ChildWorkflow:
1215
@workflow.run
13-
async def run(self):
16+
async def run(self, input: ChildWorkflowInput):
1417
log.info("ChildWorkflow started")
15-
await workflow.step(example_function, input="first", start_to_close_timeout=timedelta(minutes=2))
18+
await workflow.step(example_function, input=input, start_to_close_timeout=timedelta(minutes=2))
1619

1720
await workflow.sleep(1)
1821

production_demo/src/workflows/workflow.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
import asyncio
22
from datetime import timedelta
3-
3+
from pydantic import BaseModel, Field
44
from restack_ai.workflow import workflow, log, workflow_info, import_functions
5-
from .child import ChildWorkflow
5+
from .child import ChildWorkflow, ChildWorkflowInput
66

77
with import_functions():
8-
from src.functions.generate import llm_generate
8+
from src.functions.generate import llm_generate
9+
10+
class ExampleWorkflowInput(BaseModel):
11+
amount: int = Field(default=50)
912

1013
@workflow.defn()
1114
class ExampleWorkflow:
1215
@workflow.run
13-
async def run(self):
16+
async def run(self, input: ExampleWorkflowInput):
1417
# use the parent run id to create child workflow ids
1518
parent_workflow_id = workflow_info().workflow_id
1619

1720
tasks = []
18-
for i in range(50):
21+
for i in range(input.amount):
1922
log.info(f"Queue ChildWorkflow {i+1} for execution")
2023
task = workflow.child_execute(
2124
ChildWorkflow,
22-
workflow_id=f"{parent_workflow_id}-child-execute-{i+1}"
25+
workflow_id=f"{parent_workflow_id}-child-execute-{i+1}",
26+
input=ChildWorkflowInput(name=f"child workflow {i+1}")
2327
)
2428
tasks.append(task)
2529

production_demo/ui-endpoints.png

32.6 KB
Loading

0 commit comments

Comments
 (0)