Skip to content

Commit 138c103

Browse files
authored
Merge pull request #99 from alexhraber/main
add docs/PocketFlow
2 parents 7cf7d62 + cc2fed5 commit 138c103

9 files changed

+1985
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ This is a tutorial project of [Pocket Flow](https://github.com/The-Pocket/Pocket
6262

6363
- [OpenManus](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/OpenManus) - Build AI agents with digital brains that think, learn, and use tools just like humans do!
6464

65+
- [PocketFlow](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/PocketFlow) - Pocket Flow: 100-line LLM framework. Let Agents build Agents!
66+
6567
- [Pydantic Core](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Pydantic%20Core) - Validate data at rocket speed with just Python type hints!
6668

6769
- [Requests](https://the-pocket.github.io/PocketFlow-Tutorial-Codebase-Knowledge/Requests) - Talk to the internet in Python with code so simple it feels like cheating!
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Chapter 1: Shared State (`shared` dictionary)
2+
3+
Welcome to your first step into the world of PocketFlow! Building powerful AI applications often involves breaking down complex tasks into smaller, manageable steps. But how do these steps communicate with each other? How does one part of your AI know what another part has done or figured out? That's where the **`shared` dictionary** comes into play.
4+
5+
Imagine you're building a simple AI assistant.
6+
1. First, it needs to get your question (e.g., "What's the weather like in London?").
7+
2. Then, it might need to search the web for "weather in London."
8+
3. Finally, it uses your original question and the search results to give you an answer.
9+
10+
For this to work, the "question understanding" step needs to pass the question to the "web searching" step. Then, both the original question and the search results need to be available to the "answering" step. The `shared` dictionary is the magic message board that lets all these steps share information.
11+
12+
## What is the `shared` Dictionary?
13+
14+
At its heart, the `shared` dictionary is a standard Python dictionary (`dict`). Think of it like a **communal backpack** or a **shared whiteboard**.
15+
As your PocketFlow application (which we call a [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md)) runs, different components (which we call [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)) can:
16+
* **Put things into it** (write data).
17+
* **Look at what's inside** (read data).
18+
* **Update things** that are already there.
19+
20+
This `shared` dictionary becomes the primary way for different parts of your workflow to pass data, intermediate results, and context to each other. It's available throughout the entire lifecycle of a single execution of a [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md).
21+
22+
## How to Use the `shared` Dictionary
23+
24+
Let's see how this works with a few simple examples.
25+
26+
**1. Initializing `shared` with Starting Data**
27+
28+
Before your [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) even starts, you usually prepare some initial data. This data is placed into the `shared` dictionary.
29+
30+
Consider this snippet from one of our example projects (`cookbook/pocketflow-node/main.py`):
31+
```python
32+
# This is how we can start with some data
33+
text_to_summarize = """
34+
PocketFlow is a minimalist LLM framework...
35+
"""
36+
shared = {"data": text_to_summarize}
37+
38+
# Later, this 'shared' dictionary is passed when running the flow:
39+
# flow.run(shared)
40+
```
41+
In this code:
42+
* We have some `text_to_summarize`.
43+
* We create a Python dictionary named `shared`.
44+
* We add an entry to this dictionary: the key is `"data"` and its value is our `text_to_summarize`.
45+
When the [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) starts, this `shared` dictionary will be its starting point.
46+
47+
Here's another example from `cookbook/pocketflow-a2a/main.py` where a question is put into `shared`:
48+
```python
49+
# Default question or one from command line
50+
question = "Who won the Nobel Prize in Physics 2024?"
51+
52+
# Process the question
53+
shared = {"question": question}
54+
# agent_flow.run(shared)
55+
```
56+
Here, the `shared` dictionary is initialized with the `question` under the key `"question"`.
57+
58+
**2. A [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) Reading from `shared`**
59+
60+
[Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) are the workers in your [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md). They often need to read data from the `shared` dictionary to know what to do. This usually happens in a Node's `prep` method.
61+
62+
Let's look at the `Summarize` [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) from `cookbook/pocketflow-node/flow.py`:
63+
```python
64+
# Inside the Summarize Node class
65+
# def prep(self, shared):
66+
# """Read and preprocess data from shared store."""
67+
# return shared["data"] # Accesses the 'data' we set earlier
68+
```
69+
When this `Summarize` [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) is about to run, its `prep` method is called. PocketFlow automatically passes the current `shared` dictionary to this method.
70+
The line `shared["data"]` retrieves the value associated with the key `"data"` – which is the text we want to summarize.
71+
72+
Another example from `cookbook/pocketflow-a2a/nodes.py`, in the `DecideAction` [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md):
73+
```python
74+
# Inside the DecideAction Node's prep method
75+
# def prep(self, shared):
76+
# Get the current context (default if none exists)
77+
context = shared.get("context", "No previous search")
78+
# Get the question from the shared store
79+
question = shared["question"]
80+
return question, context
81+
```
82+
This `prep` method reads two items:
83+
* `shared.get("context", "No previous search")`: This tries to get the value for the key `"context"`. If `"context"` isn't found (maybe it's the first time this runs), it defaults to `"No previous search"`. Using `.get()` is a safe way to read, as it prevents errors if a key might be missing.
84+
* `shared["question"]`: This directly retrieves the value for the key `"question"`, assuming it will always be there.
85+
86+
**3. A [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) Writing Results Back to `shared`**
87+
88+
After a [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) does its work (e.g., summarizes text, gets search results), it often needs to save its findings back into the `shared` dictionary. This typically happens in a Node's `post` method.
89+
90+
Continuing with our `Summarize` [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) (`cookbook/pocketflow-node/flow.py`):
91+
```python
92+
# Inside the Summarize Node class
93+
# 'exec_res' below is the result from the Node's main task
94+
# def post(self, shared, prep_res, exec_res):
95+
# """Store the summary in shared store."""
96+
# shared["summary"] = exec_res # Stores the result
97+
```
98+
Here, `exec_res` holds the summary generated by the [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md). The line `shared["summary"] = exec_res` creates a new key `"summary"` in the `shared` dictionary (or updates it if it already exists) and stores the summary there. Now, subsequent [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) can access this summary!
99+
100+
Similarly, in `DecideAction`'s `post` method (`cookbook/pocketflow-a2a/nodes.py`):
101+
```python
102+
# Inside DecideAction Node's post method
103+
# def post(self, shared, prep_res, exec_res):
104+
# 'exec_res' contains the decision made by an LLM
105+
if exec_res["action"] == "search":
106+
shared["search_query"] = exec_res["search_query"]
107+
# ...
108+
else:
109+
shared["context"] = exec_res["answer"]
110+
# ...
111+
# ...
112+
```
113+
Depending on the `action` decided, this `post` method writes either a `"search_query"` or an updated `"context"` (which is the answer) into the `shared` dictionary.
114+
115+
**4. Modifying Existing Data in `shared`**
116+
117+
Sometimes, a [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) needs to update or add to existing information in `shared`. For example, in a chat application, you maintain a history of messages.
118+
119+
From `cookbook/pocketflow-chat/main.py`, the `ChatNode`'s `prep` method does this:
120+
```python
121+
# Inside ChatNode's prep method
122+
# def prep(self, shared):
123+
# Initialize messages if this is the first run
124+
if "messages" not in shared:
125+
shared["messages"] = [] # Create an empty list if no history
126+
127+
# ... user_input is obtained ...
128+
129+
# Add user message to history
130+
shared["messages"].append({"role": "user", "content": user_input})
131+
# ...
132+
```
133+
Here:
134+
1. It checks if `"messages"` (our chat history) exists in `shared`. If not, it initializes `shared["messages"]` as an empty list.
135+
2. It then appends the new user message to this list. The `shared["messages"]` list grows with each turn of the conversation.
136+
137+
**5. Accessing Final Results from `shared`**
138+
139+
Once your [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) has completed all its steps, the `shared` dictionary will contain the final outputs and any important intermediate data you chose to store. You can then access these results from your main script.
140+
141+
Back to `cookbook/pocketflow-node/main.py`:
142+
```python
143+
# After the flow.run(shared) call:
144+
# The 'shared' dictionary now contains the summary
145+
146+
print("\nSummary:", shared["summary"])
147+
```
148+
This line simply prints the value associated with the key `"summary"` from the `shared` dictionary, which was put there by the `Summarize` [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md).
149+
150+
## Key Characteristics of `shared`
151+
152+
* **It's a Python Dictionary:** This makes it incredibly flexible and easy to use. If you know how to use dictionaries in Python (e.g., `my_dict['key'] = value`, `value = my_dict['key']`, `my_dict.get('key', default_value)`), you already know how to interact with `shared`.
153+
* **Scoped to a Single Flow Execution:** Each time you run a [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) (e.g., by calling `flow.run(shared_input)`), it operates on its own instance of the `shared` dictionary. If you run the same [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) twice, even simultaneously for different requests, they will have completely separate `shared` dictionaries. They won't interfere with each other. Think of it like two people filling out their own copies of the same form.
154+
* **Persistent Throughout One Flow Execution:** The `shared` dictionary is created (or you provide an initial one) when a [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md) starts. The *exact same* dictionary object is then passed from one [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) to the next. Any modifications made by one [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) are visible to all subsequent [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md).
155+
156+
## What Happens Under the Hood? (A Simplified View)
157+
158+
You don't need to manage the passing of the `shared` dictionary yourself; PocketFlow handles it for you. Here's a simplified step-by-step:
159+
160+
1. **You start a Flow:** You call something like `my_flow.run(initial_shared_data)`. `initial_shared_data` is the dictionary you've prepared.
161+
2. **PocketFlow takes over:** It takes your `initial_shared_data` and passes it to the first [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) in your [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md).
162+
3. **Node executes:**
163+
* The [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)'s `prep` method is called with the `shared` dictionary. It can read from it.
164+
* The [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)'s `exec` method (the main workhorse) is called.
165+
* The [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)'s `post` method is called with the `shared` dictionary. It can write results back to it.
166+
4. **Pass it on:** PocketFlow determines the next [Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) to run and passes the *same, possibly modified*, `shared` dictionary to it.
167+
5. **Repeat:** Steps 3 and 4 repeat until there are no more [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) to run in the [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md).
168+
6. **Flow ends:** The `run` method finishes, and the `shared` dictionary you originally passed in now contains all the updates made by the [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md).
169+
170+
Here's a visual way to think about it:
171+
172+
```mermaid
173+
sequenceDiagram
174+
participant You
175+
participant PocketFlowEngine as PocketFlow Engine
176+
participant NodeA as First Node
177+
participant NodeB as Second Node
178+
participant SharedDict as Shared Dictionary
179+
180+
You->>PocketFlowEngine: my_flow.run(initial_shared)
181+
PocketFlowEngine->>SharedDict: Initialize with initial_shared
182+
PocketFlowEngine->>NodeA: process(SharedDict)
183+
NodeA->>SharedDict: Reads input (e.g., shared['question'])
184+
NodeA->>SharedDict: Writes output (e.g., shared['data_from_A'] = ...)
185+
PocketFlowEngine->>NodeB: process(SharedDict)
186+
NodeB->>SharedDict: Reads input (e.g., shared['data_from_A'])
187+
NodeB->>SharedDict: Writes output (e.g., shared['final_answer'] = ...)
188+
PocketFlowEngine->>You: Flow complete (initial_shared is now updated)
189+
```
190+
191+
## Analogy Time!
192+
193+
Think of the `shared` dictionary as:
194+
195+
* **A Relay Race Baton (but smarter!):** Each runner ([Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)) takes the baton (`shared` dictionary), maybe adds a small note or a sticker to it, and then passes it to the next runner. By the end of the race, the baton has collected contributions from everyone.
196+
* **A Project's Shared Folder:** Imagine a team working on a project. They have a shared folder (`shared` dictionary) on a server. The first person creates a document (initial data). The next person opens it, adds their part, and saves it. The next person does the same. Everyone works on the same set of files in that folder.
197+
198+
## Conclusion
199+
200+
You've now learned about the `shared` dictionary, the backbone of communication within a PocketFlow [Flow (`Flow`, `AsyncFlow`)](04_flow___flow____asyncflow___.md). It's a simple yet powerful Python dictionary that allows different [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) to share data and context seamlessly. By reading from and writing to `shared`, your [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md) can collaborate to achieve complex tasks.
201+
202+
Now that you understand how data is passed around, you're probably wondering about the "workers" themselves – the [Nodes (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md). What are they, and how do you build them? Let's dive into that in the next chapter!
203+
204+
Next up: [Chapter 2: Node (`BaseNode`, `Node`, `AsyncNode`)](02_node___basenode____node____asyncnode___.md)
205+
206+
---
207+
208+
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge)

0 commit comments

Comments
 (0)