Durable website summarization app | Part 1
How to build a durable website summarization app with Resonate, Flask, and Ollama
In this tutorial, you’ll build a website summarization application using the Resonate Python SDK, Flask, and Ollama.
By doing so, you’ll gain experience with Resonate’s implementation of the Distributed Async Await programming model and the core features of the Python SDK.
This tutorial follows the philosophy of progressive disclosure and is broken into several parts, starting with a simple example and building on it step by step. Each part introduces new concepts. You can choose to stop at the end of any part of the tutorial and still have a working application.
You can find the resulting application in the website summarization app repository.
Parts
In this first part you will start with a single Application Node and gain experience triggering the SDK's ability to automatically Retry application-level failures (failed function executions).
In subsequent parts, you will:
connect your application to the Resonate Server, enabling the Application Node to recover from platform-level failures, such as a process crash (Durable Execution).
separate your application into an HTTP Server and individual Application Nodes to see how Resonate facilitates fan-out/fan-in use cases.
add a step to your workflow that blocks the execution on input from a human-in-the-loop and add functionality to unblock it from another process.
integrate a web-scraper, such as Beautiful Soup, and an LLM, such as Ollama, to bring your application to life.
By the end of this tutorial you'll have a good understanding of the Resonate Python SDK and how to build Distributed Async Await applications with it.
Prerequisites
This tutorial assumes that you have Python 3 and a package manager installed, however this tutorial recommends using uv.
This tutorial was written and tested with Resonate Server v0.7.6 and Resonate Python SDK v0.4.12.
Part 1
In this part of the tutorial you'll create an application that is error prone to see how Resonate automatically Retries failed function executions.
Scaffold your project
Start by installing the CLI:
Navigate to the directory you want to scaffold your project in and run the following command:
You should now have a directory called “summarization-app” with the following structure:
The app.py
file should contain the following code:
In the template app, foo()
takes a string argument, greeting,
and passes it first to bar()
and then to baz()
, each step concatenating to the string. The greeting is then returned back to the caller and printed.
The @resonate.register
decorator registers foo()
with the Resonate instance enabling it to be invoked with a the .run()
method.
The handle
variable is a promise. Calling handle.result()
blocks progress of main()
until foo()
returns a result.
Both bar()
and baz()
are invoked using the Resonate Context’s LFC API. LFC stands for Local Function Call, and gives Resonate control over the invocation and execution of those functions.
Move into the root of the project and run the following commands to execute the app as-is:
You should see the following output:
Now, lets add some code to the step functions, bar()
and baz()
, so they fail 50% of the time.
Simulate errors
First, import the random
package into the app.
Then, in each of the step functions, bar() and baz()
, directly after the print
statements, randomly generate a number between 0 and 100 and raise an exception if the number is greater than 50.
Now, each time you run the app, both bar()
and baz()
have a 50% chance of raising an exception.
Without Resonate, if an exception is raised the execution would stop and no more progress would be made.
Consider if the app was written without Resonate, like this:
If either bar()
or baz()
raise an exception, the execution of foo()
stops where the exception is raised, main()
prints the error, and that’s that.
With Resonate however, you will notice that even if an exception is raised, the failed function will be tried again, until it succeeds, enabling foo()
to complete and return the greeting.
Run your app several times until you encounter an error, and then wait and watch.
You will notice that if an exception is thrown, Resonate automatically Retries executing the function. By default, this will happen forever until the execution succeeds.
We say, “by default”, because this behaviour is configurable. You can adjust the backoff interval (time between Retries), maximum attempts, and which errors cause Retries and which do not. For example, here we adjust the Retry Policy of the invocation of bar to be “linear”:
The Retry Policy in the previous code example sets the initial delay to 1 second, with a linear increase of the delay up to maximum of 10 attempts.
Later in the tutorial we will customize our Retry Policy to ignore certain application-level errors that we know shouldn’t be retried.
Next
This part of the tutorial showcases Resonate’s ability to automatically Retry function executions when an exception is raised, and runs the top-level function to completion.
But what if the process crashed altogether while the application was running?
In the next part of the tutorial, we will connect the application to a Resonate Server to enable Recovery!
Curious about how we define the difference between Retry and Recovery? Check out the Scattered Thought, “Difference between Retry and Recovery”.