Skip to content

Commit 2b7dbf3

Browse files
authored
docs: update resources docs (#18991)
1 parent 3093dab commit 2b7dbf3

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

docs/docs/module_guides/workflow/index.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -632,11 +632,12 @@ result = await handler
632632
handler = w.run(ctx=handler.ctx)
633633
result = await handler
634634
```
635+
635636
## Resources
636637

637638
Resources are external dependencies you can inject into the steps of a workflow.
638639

639-
A simple example can be:
640+
As a simple example, look at `memory` in the following workflow:
640641

641642
```python
642643
from llama_index.core.workflow.resource import Resource
@@ -675,9 +676,16 @@ class WorkflowWithResource(Workflow):
675676
return StopEvent(result="Messages put into memory")
676677
```
677678

678-
The `Resource` wrapper acts as both a type declaration and an executor. At definition time, it specifies the expected type using `Annotated` - for example, a `Memory` object. At runtime, it invokes the associated factory function, such as `get_memory`, to produce the actual instance. The return type of this function must match the declared type, ensuring consistency between what’s expected and what’s provided during execution.
679+
To inject a resource into a workflow step, you have to add a parameter to the step signature and define its type,
680+
using `Annotated` and invoke the `Resource()` wrapper passing a function or callable returning the actual Resource
681+
object. The return type of the wrapped function must match the declared type, ensuring consistency between what’s
682+
expected and what’s provided during execution. In the example above, `memory: Annotated[Memory, Resource(get_memory)`
683+
defines a resource of type `Memory` that will be provided by the `get_memory()` function and passed to the step in the
684+
`memory` parameter when the workflow runs.
679685

680-
Resources are shared among steps of a workflow, and `Resource` will invoke the factory function only once. In case this is not the desired behavior, passing `cache=False` to `Resource` will inject different resource objects in different steps, invoking the factory function as many times.
686+
Resources are shared among steps of a workflow, and the `Resource()` wrapper will invoke the factory function only once.
687+
In case this is not the desired behavior, passing `cache=False` to `Resource()` will inject different resource objects
688+
in different steps, invoking the factory function as many times.
681689

682690
## Checkpointing Workflows
683691

docs/docs/understanding/workflows/resources.md

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
# Resources
22

3-
Resources are a component of workflows that allow us to equip our steps with external dependencies such as memory, LLMs, query engines or chat history.
3+
Resources are external dependencies such as memory, LLMs, query engines or chat history instances that will be injected
4+
into workflow steps at runtime.
45

5-
Resources are a powerful way of binding components to our steps that we otherwise would need to specify by hand every time and, most importantly, resources are **stateful**, meaning that they maintain their state across different steps, unless otherwise specified.
6+
Resources are a powerful way of binding workflow steps to Python objects that we otherwise would need to create by hand
7+
every time. For performance reasons, by default resources are cached for a workflow, meaning the same resource instance
8+
is passed to every step where it's injected. It's important to master this concept because cached and non-cached
9+
resources can lead to unexpected behaviour, let's see it in detail.
610

7-
## Using Stateful Resources
11+
## Resources are cached by default
812

9-
In order to use them within our code, we need to import them from the `resource` submodule:
13+
First of all, to use resources within our code, we need to import `Resource` from the `resource` submodule:
1014

1115
```python
1216
from llama_index.core.workflow.resource import Resource
@@ -19,7 +23,8 @@ from llama_index.core.workflow import (
1923
)
2024
```
2125

22-
The `Resource` function works as a wrapper for another function that, when executed, returns an object of a specified type. This is the usage pattern:
26+
`Resource` wraps a function or callable that must return an object of the same type as the one in the resource
27+
definition, let's see an example:
2328

2429
```python
2530
from typing import Annotated
@@ -33,7 +38,9 @@ def get_memory(*args, **kwargs) -> Memory:
3338
resource = Annotated[Memory, Resource(get_memory)]
3439
```
3540

36-
When a step of our workflow will be equipped with this resource, the variable in the step to which the resource is assigned would behave as a memory component:
41+
In the example above, `Annotated[Memory, Resource(get_memory)` defines a resource of type `Memory` that will be provided
42+
at runtime by the `get_memory()` function. A resource defined like this can be injected into a step by passing it as
43+
a method parameter:
3744

3845
```python
3946
import random
@@ -98,7 +105,9 @@ class WorkflowWithMemory(Workflow):
98105
return StopEvent(result=messages)
99106
```
100107

101-
As you can see, each step has access to memory and writes to it - the memory is shared among them and we can see it by running the workflow:
108+
As you can see, each step has access to the `memory` resource and can write to it. It's important to note that
109+
`get_memory()` will be called only once, and the same memory instance will be injected into the different steps. We can
110+
see this is the case by running the workflow:
102111

103112
```python
104113
wf = WorkflowWithMemory(disable_validation=True)
@@ -128,7 +137,9 @@ Third step: Hello World!
128137

129138
This shows that each step added its message to a global memory, which is exactly what we were expecting!
130139

131-
It is important to note, though, the resources are preserved across steps of the same workflow instance, but not across different workflows. If we were to run two `WorkflowWithMemory` instances, their memories would be separate and independent:
140+
Note that resources are preserved across steps of the same workflow instance, but not across different workflows. If we
141+
were to run two `WorkflowWithMemory` instances, `get_memory` would be called one time for each workflow and as a result
142+
their memories would be separate and independent:
132143

133144
```python
134145
wf1 = WorkflowWithMemory(disable_validation=True)
@@ -165,11 +176,11 @@ First step: Happy New Year!
165176
Second step: Python is awesome!
166177
```
167178

168-
## Using Steteless Resources
179+
## Disable resource caching
169180

170-
Resources can also be stateless, meaning that we can configure them *not* to be preserved across steps in the same run.
171-
172-
In order to do so, we just need to specify `cache=False` when instantiating `Resource` - let's see this in a simple example, using a custom `Counter` class:
181+
If we pass `cache=False` to `Resource` when defining a resource, the wrapped function is called every time the resource
182+
is injected into a step. This behaviour can be desirable at times, let's see a simple example using a custom
183+
`Counter` class:
173184

174185
```python
175186
from pydantic import BaseModel, Field
@@ -219,4 +230,22 @@ Counter at first step: 1
219230
Counter at second step: 1
220231
```
221232

233+
## A note about stateful and stateless resources
234+
235+
As we have seen, cached resources are expected to be **stateful**, meaning that they can maintain their state across
236+
different workflow runs and different steps, unless otherwise specified. But this doesn't mean we can consider a
237+
resource **stateless** only because we disable caching. Let's see an example:
238+
239+
```python
240+
global_mem = Memory.from_defaults("global_id", token_limit=60000)
241+
242+
243+
def get_memory(*args, **kwargs) -> Memory:
244+
return global_mem
245+
```
246+
247+
If we disable caching with `Annotated[Memory, Resource(get_memory, cache=False)]`, the function `get_memory` is going
248+
to be called multiple times but the resource instance will be always the same. Such a resource should be considered
249+
stateful not regarding its caching behaviour.
250+
222251
Now that we've mastered resources, let's take a look at [observability and debugging](./observability.md) in workflows.

0 commit comments

Comments
 (0)