Event Driven Pipelines
Overview
Event driven pipelines are a new way of orchestrating workflows in Peach.
peach-tasks on schedule and peach-endpoints on realtime / streaming
Until now, a peach-task would run in isolation from other peach-tasks and peach-endpoints. It would run on a schedule and do some work.
A common pattern is to store the result of the task's computation in redis or influxdb or milvus and use it to have Recommendation APIs (peach-endpoints) serve recommendations.

Event driven pipelines
Now we can let peach-tasks and peach-endpoints work togther and orchestrate more complex event driven workflows.
An example would be creating a webhook for audio file transcription. The webhook would receive the audio file and trigger a task to transcribe it. And the transcribed text could be fed to a recommendation task to generate recommendations.
Use the decorators for this
Pipelines are the case the decorator convention suits
best, and it is the recommended way to build them. A @peach.task is an
ordinary Python function, so one stage calls the next by calling it — a
real function call, with real arguments and a real return value, in the same
file. There is no task name to keep in step with a config file, no payload
dictionary to pack and unpack, and no separate YAML describing a wiring that
the code already expresses.
The rest of this page shows both. Read Decorator tasks and Decorator endpoints first if you have not met them.
syntax for event triggering tasks:
You can use helper function to trigger a peach-task from inside another peach-tasks, peach-endpoints and even from inside peach-lab.
Here is syntax for triggering a task:
from pipe_algorithms_lib.task_helpers import trigger_task_with_event
TASK_NAME = "collaborative_filtering" # one of your real task name as specified in peach.conf
PAYLOAD = {"key1" : "value1", "key2" : "value2"}
trigger_task_with_event(TASK_NAME, PAYLOAD)
example usecase : webhook for audio file transcription.
If a large audio file is uploaded to s3, we can trigger a task to transcribe it. The upload event can be received by an endpoint - which would act as a webhook and then it can trigger the task.
peach.conf
# partial config as example
tasks:
audio_transcription:
method: audio_transcription_task
endpoints:
audio_transcription_webhook:
method: webhook_handler
from pipe_algorithms_lib.task_helpers import trigger_task_with_event
TASK_NAME = "audio_transcription" # one of your real task name as specified in peach.conf
PAYLOAD = {"s3_uri" : "s3://bucket/key", "language" : "en"}
def audio_transcription_task(payload):
s3_uri = payload["s3_uri"]
language = payload["language"]
# do transcription
def webhook_handler(request):
s3_uri = request.json["s3_uri"]
language = request.json["language"]
PAYLOAD = {"s3_uri" : s3_uri, "language" : language}
trigger_task_with_event(TASK_NAME, PAYLOAD)
the same webhook, with decorators
The whole thing becomes one file under peach/, with the wiring expressed as a
call:
from pipe_algorithms_lib.compute import peach
@peach.task(py_env="ml", num_gpus=1)
def audio_transcription(s3_uri: str, language: str = "en"):
text = transcribe(s3_uri, language) # your transcription code
return {"text": text}
@peach.endpoint(name="audio-transcription-webhook", route_prefix="/transcribe")
def webhook_handler(s3_uri: str, language: str = "en"):
return audio_transcription(s3_uri, language)
No peach.conf, no TASK_NAME string, no PAYLOAD dict — and the task keeps a
signature you can call and test directly. audio_transcription has no cron,
because nothing schedules it; it exists to be called.
This call is synchronous
Calling a @peach.task dispatches it and waits for the result, so the
webhook above does not answer until the transcription finishes. That is what
you want for a stage that needs its predecessor's output, and wrong for a
webhook that must acknowledge immediately.
For fire-and-forget, keep trigger_task_with_event: it starts a Prefect flow
run and returns its id straight away, and poll_status(flow_run_id) checks
on it later. A long transcription triggered by an upload belongs on that
path, not on a blocking call.
static multi-stage pipelines (DAGs)
So far we have seen the possibilty to build dynamic event driven pipelines - where one task (or endpoint) can trigger another task to build a dynamic graph of the pipeline.
But in some cases the order of task execution is known before hand. In those case you can build a static pipeline for it. A Dynamic Acyclic Graph (DAG) is a graph that is acyclic (no cycles) and directed (edges have a direction). So it is a graph that is acyclic and directed. The static multi-stage pipelines on peach will be DAGs.
For this we rely on a key downstream in the task config to specify the list of tasks that should be executed after the current task is completed successfully.
downstream is peach.conf only
There is no downstream= on @peach.task yet. A decorator-based pipeline
expresses its order by calling the next stage directly, as above — which
covers most cases, but does not give you the separate, independently
retryable Prefect flow runs per stage that downstream does. Use
peach.conf when you need that.
example syntax for building static DAG pipelines:
peach.conf
tasks:
task1:
method: task1_method
downstream:
- task2
- task3
task2:
method: task2_method
task3:
method: task3_method
passing data from one DAG stage to another DAG stage.
To pass data from an upstream task to a downstream task, we use a helper function pass_to_child_task. And to receive the data in the downstream task, we use a helper function read_from_parent_task.
import json
from pipe_algorithms_lib.task_helpers import pass_data_to_downstream
# This is the data we want to pass from task1 to task2
PAYLOAD = {"key1": "value1", "key2": "value2"}
def task1_method(payload):
"""
This task serializes a Python dictionary to a JSON formatted string
and passes it to its downstream tasks.
"""
# We serialize the dictionary to a JSON string before passing it
data_to_pass = json.dumps(PAYLOAD)
pass_to_child_task(data_to_pass)
def task2_method():
payload = read_from_parent_task()
"""
This task receives a JSON formatted string, deserializes it back
into a Python dictionary, and can then use the data.
"""
# The payload received is a JSON string, so we deserialize it back to a dictionary
received_data = json.loads(payload)
# now you can use the data as a dictionary:
# print(received_data.get("key1"))
Related
- Decorator tasks and Decorator endpoints — the recommended way to build these pipelines.
- Tasks and scheduling — the
peach.conftasks thatdownstreamandtrigger_task_with_eventrefer to. - Build your first feature — the single task-and-endpoint pattern these pipelines extend.
Reach out.
Do reach out to the peach team, we would be happy to help you build these new pipelines.