feat: add new examples from pocketflow-academy
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# PocketFlow Parallel Batch Node Example
|
||||
|
||||
This example demonstrates parallel processing using AsyncParallelBatchNode to summarize multiple news articles concurrently. It shows how to:
|
||||
1. Process multiple items in parallel
|
||||
2. Handle I/O-bound tasks efficiently
|
||||
3. Manage rate limits with throttling
|
||||
|
||||
## What this Example Does
|
||||
|
||||
When you run the example:
|
||||
1. It loads multiple news articles from a data directory
|
||||
2. Processes them in parallel using AsyncParallelBatchNode
|
||||
3. For each article:
|
||||
- Extracts key information
|
||||
- Generates a summary using an LLM
|
||||
- Saves the results
|
||||
4. Combines all summaries into a final report
|
||||
|
||||
## How it Works
|
||||
|
||||
The example uses AsyncParallelBatchNode to process articles in parallel:
|
||||
|
||||
```python
|
||||
class ParallelSummarizer(AsyncParallelBatchNode):
|
||||
async def prep_async(self, shared):
|
||||
# Return list of articles to process
|
||||
return shared["articles"]
|
||||
|
||||
async def exec_async(self, article):
|
||||
# Process single article (called in parallel)
|
||||
summary = await call_llm_async(f"Summarize: {article}")
|
||||
return summary
|
||||
|
||||
async def post_async(self, shared, prep_res, summaries):
|
||||
# Combine all summaries
|
||||
shared["summaries"] = summaries
|
||||
return "default"
|
||||
```
|
||||
|
||||
Key features demonstrated:
|
||||
- Parallel execution of `exec_async`
|
||||
- Rate limiting with semaphores
|
||||
- Error handling for failed requests
|
||||
- Progress tracking for parallel tasks
|
||||
|
||||
## Project Structure
|
||||
```
|
||||
pocketflow-parallel-batch-node/
|
||||
├── README.md
|
||||
├── requirements.txt
|
||||
├── data/
|
||||
│ ├── article1.txt
|
||||
│ ├── article2.txt
|
||||
│ └── article3.txt
|
||||
├── main.py
|
||||
├── flow.py
|
||||
├── nodes.py
|
||||
└── utils.py
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run the example
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Sample Output
|
||||
```
|
||||
Loading articles...
|
||||
Found 3 articles to process
|
||||
|
||||
Processing in parallel...
|
||||
[1/3] Processing article1.txt...
|
||||
[2/3] Processing article2.txt...
|
||||
[3/3] Processing article3.txt...
|
||||
|
||||
Summaries generated:
|
||||
1. First article summary...
|
||||
2. Second article summary...
|
||||
3. Third article summary...
|
||||
|
||||
Final report saved to: summaries.txt
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
1. **Parallel Processing**
|
||||
- Using AsyncParallelBatchNode for concurrent execution
|
||||
- Managing parallel tasks efficiently
|
||||
|
||||
2. **Rate Limiting**
|
||||
- Using semaphores to control concurrent requests
|
||||
- Avoiding API rate limits
|
||||
|
||||
3. **Error Handling**
|
||||
- Graceful handling of failed requests
|
||||
- Retrying failed tasks
|
||||
|
||||
4. **Progress Tracking**
|
||||
- Monitoring parallel task progress
|
||||
- Providing user feedback
|
||||
@@ -0,0 +1 @@
|
||||
Article 1: AI advances in 2024...
|
||||
@@ -0,0 +1 @@
|
||||
Article 2: New quantum computing breakthrough...
|
||||
@@ -0,0 +1 @@
|
||||
Article 3: Latest developments in robotics...
|
||||
@@ -0,0 +1,3 @@
|
||||
1. Summary of: Article 1: AI advances in 2024...
|
||||
2. Summary of: Article 2: New quantum computi...
|
||||
3. Summary of: Article 3: Latest developments...
|
||||
@@ -0,0 +1,24 @@
|
||||
"""AsyncFlow implementation for parallel article processing."""
|
||||
|
||||
from pocketflow import AsyncFlow, Node
|
||||
from nodes import LoadArticles, ParallelSummarizer
|
||||
|
||||
class NoOp(Node):
|
||||
"""Node that does nothing, used to properly end the flow."""
|
||||
pass
|
||||
|
||||
def create_flow():
|
||||
"""Create and connect nodes into a flow."""
|
||||
|
||||
# Create nodes
|
||||
loader = LoadArticles()
|
||||
summarizer = ParallelSummarizer()
|
||||
end = NoOp()
|
||||
|
||||
# Connect nodes
|
||||
loader - "process" >> summarizer
|
||||
summarizer - "default" >> end # Properly end the flow
|
||||
|
||||
# Create flow starting with loader
|
||||
flow = AsyncFlow(start=loader)
|
||||
return flow
|
||||
@@ -0,0 +1,19 @@
|
||||
import asyncio
|
||||
from flow import create_flow
|
||||
|
||||
async def main():
|
||||
"""Run the parallel processing flow."""
|
||||
# Create flow
|
||||
flow = create_flow()
|
||||
|
||||
# Create shared store
|
||||
shared = {}
|
||||
|
||||
# Run flow
|
||||
print("\nParallel Article Summarizer")
|
||||
print("-------------------------")
|
||||
await flow.run_async(shared)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,48 @@
|
||||
"""AsyncParallelBatchNode implementation for article summarization."""
|
||||
|
||||
from pocketflow import AsyncParallelBatchNode, AsyncNode
|
||||
from utils import call_llm_async, load_articles, save_summaries
|
||||
|
||||
class LoadArticles(AsyncNode):
|
||||
"""Node that loads articles to process."""
|
||||
|
||||
async def prep_async(self, shared):
|
||||
"""Load articles from data directory."""
|
||||
print("\nLoading articles...")
|
||||
articles = await load_articles()
|
||||
return articles
|
||||
|
||||
async def exec_async(self, articles):
|
||||
"""No processing needed."""
|
||||
return articles
|
||||
|
||||
async def post_async(self, shared, prep_res, exec_res):
|
||||
"""Store articles in shared store."""
|
||||
shared["articles"] = exec_res
|
||||
print(f"Found {len(exec_res)} articles to process")
|
||||
return "process"
|
||||
|
||||
class ParallelSummarizer(AsyncParallelBatchNode):
|
||||
"""Node that summarizes articles in parallel."""
|
||||
|
||||
async def prep_async(self, shared):
|
||||
"""Get articles from shared store."""
|
||||
print("\nProcessing in parallel...")
|
||||
return shared["articles"]
|
||||
|
||||
async def exec_async(self, article):
|
||||
"""Summarize a single article (called in parallel)."""
|
||||
summary = await call_llm_async(article)
|
||||
return summary
|
||||
|
||||
async def post_async(self, shared, prep_res, summaries):
|
||||
"""Store summaries and save to file."""
|
||||
shared["summaries"] = summaries
|
||||
|
||||
print("\nSummaries generated:")
|
||||
for i, summary in enumerate(summaries, 1):
|
||||
print(f"{i}. {summary}")
|
||||
|
||||
save_summaries(summaries)
|
||||
print("\nFinal report saved to: summaries.txt")
|
||||
return "default"
|
||||
@@ -0,0 +1,4 @@
|
||||
pocketflow
|
||||
aiohttp>=3.8.0 # For async HTTP requests
|
||||
openai>=1.0.0 # For async LLM calls
|
||||
tqdm>=4.65.0 # For progress bars
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Utility functions for parallel processing."""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
from openai import AsyncOpenAI
|
||||
from tqdm import tqdm
|
||||
|
||||
# Semaphore to limit concurrent API calls
|
||||
MAX_CONCURRENT_CALLS = 3
|
||||
semaphore = asyncio.Semaphore(MAX_CONCURRENT_CALLS)
|
||||
|
||||
async def call_llm_async(prompt):
|
||||
"""Make async LLM call with rate limiting."""
|
||||
async with semaphore: # Limit concurrent calls
|
||||
print(f"\nProcessing: {prompt[:50]}...")
|
||||
|
||||
# Simulate API call with delay
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Mock LLM response (in real app, would call OpenAI)
|
||||
summary = f"Summary of: {prompt[:30]}..."
|
||||
return summary
|
||||
|
||||
async def load_articles():
|
||||
"""Load articles from data directory."""
|
||||
# For demo, generate mock articles
|
||||
articles = [
|
||||
"Article 1: AI advances in 2024...",
|
||||
"Article 2: New quantum computing breakthrough...",
|
||||
"Article 3: Latest developments in robotics..."
|
||||
]
|
||||
|
||||
# Create data directory if it doesn't exist
|
||||
data_dir = "data"
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
# Save mock articles to files
|
||||
for i, content in enumerate(articles, 1):
|
||||
with open(os.path.join(data_dir, f"article{i}.txt"), "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return articles
|
||||
|
||||
def save_summaries(summaries):
|
||||
"""Save summaries to output file."""
|
||||
# Create data directory if it doesn't exist
|
||||
data_dir = "data"
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
with open(os.path.join(data_dir, "summaries.txt"), "w") as f:
|
||||
for i, summary in enumerate(summaries, 1):
|
||||
f.write(f"{i}. {summary}\n")
|
||||
Reference in New Issue
Block a user