feat: add new examples from pocketflow-academy
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# PocketFlow Async Basic Example
|
||||
|
||||
This example demonstrates async operations using a simple Recipe Finder that:
|
||||
1. Fetches recipes from an API (async HTTP)
|
||||
2. Processes them with an LLM (async LLM)
|
||||
3. Waits for user confirmation (async input)
|
||||
|
||||
## What this Example Does
|
||||
|
||||
When you run the example:
|
||||
1. You enter an ingredient (e.g., "chicken")
|
||||
2. It searches for recipes (async API call)
|
||||
3. It suggests a recipe (async LLM call)
|
||||
4. You approve or reject the suggestion
|
||||
5. If rejected, it tries again with a different recipe
|
||||
|
||||
## How it Works
|
||||
|
||||
1. **FetchRecipes (AsyncNode)**
|
||||
```python
|
||||
async def prep_async(self, shared):
|
||||
ingredient = input("Enter ingredient: ")
|
||||
return ingredient
|
||||
|
||||
async def exec_async(self, ingredient):
|
||||
# Async API call
|
||||
recipes = await fetch_recipes(ingredient)
|
||||
return recipes
|
||||
```
|
||||
|
||||
2. **SuggestRecipe (AsyncNode)**
|
||||
```python
|
||||
async def exec_async(self, recipes):
|
||||
# Async LLM call
|
||||
suggestion = await call_llm_async(
|
||||
f"Choose best recipe from: {recipes}"
|
||||
)
|
||||
return suggestion
|
||||
```
|
||||
|
||||
3. **GetApproval (AsyncNode)**
|
||||
```python
|
||||
async def post_async(self, shared, prep_res, suggestion):
|
||||
# Async user input
|
||||
answer = await get_user_input(
|
||||
f"Accept {suggestion}? (y/n): "
|
||||
)
|
||||
return "accept" if answer == "y" else "retry"
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Sample Interaction
|
||||
|
||||
```
|
||||
Enter ingredient: chicken
|
||||
Fetching recipes...
|
||||
Found 3 recipes.
|
||||
|
||||
Suggesting best recipe...
|
||||
How about: Grilled Chicken with Herbs
|
||||
|
||||
Accept this recipe? (y/n): n
|
||||
Suggesting another recipe...
|
||||
How about: Chicken Stir Fry
|
||||
|
||||
Accept this recipe? (y/n): y
|
||||
Great choice! Here's your recipe...
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
1. **Async Operations**: Using `async/await` for:
|
||||
- API calls (non-blocking I/O)
|
||||
- LLM calls (potentially slow)
|
||||
- User input (waiting for response)
|
||||
|
||||
2. **AsyncNode Methods**:
|
||||
- `prep_async`: Setup and data gathering
|
||||
- `exec_async`: Main async processing
|
||||
- `post_async`: Post-processing and decisions
|
||||
|
||||
3. **Flow Control**:
|
||||
- Actions ("accept"/"retry") control flow
|
||||
- Retry loop for rejected suggestions
|
||||
@@ -0,0 +1,27 @@
|
||||
"""AsyncFlow implementation for recipe finder."""
|
||||
|
||||
from pocketflow import AsyncFlow, Node
|
||||
from nodes import FetchRecipes, SuggestRecipe, GetApproval
|
||||
|
||||
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
|
||||
fetch = FetchRecipes()
|
||||
suggest = SuggestRecipe()
|
||||
approve = GetApproval()
|
||||
end = NoOp()
|
||||
|
||||
# Connect nodes
|
||||
fetch - "suggest" >> suggest
|
||||
suggest - "approve" >> approve
|
||||
approve - "retry" >> suggest # Loop back for another suggestion
|
||||
approve - "accept" >> end # Properly end the flow
|
||||
|
||||
# Create flow starting with fetch
|
||||
flow = AsyncFlow(start=fetch)
|
||||
return flow
|
||||
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
from flow import create_flow
|
||||
|
||||
async def main():
|
||||
"""Run the recipe finder flow."""
|
||||
# Create flow
|
||||
flow = create_flow()
|
||||
|
||||
# Create shared store
|
||||
shared = {}
|
||||
|
||||
# Run flow
|
||||
print("\nWelcome to Recipe Finder!")
|
||||
print("------------------------")
|
||||
await flow.run_async(shared)
|
||||
print("\nThanks for using Recipe Finder!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,63 @@
|
||||
from pocketflow import AsyncNode
|
||||
from utils import fetch_recipes, call_llm_async, get_user_input
|
||||
|
||||
class FetchRecipes(AsyncNode):
|
||||
"""AsyncNode that fetches recipes."""
|
||||
|
||||
async def prep_async(self, shared):
|
||||
"""Get ingredient from user."""
|
||||
ingredient = await get_user_input("Enter ingredient: ")
|
||||
return ingredient
|
||||
|
||||
async def exec_async(self, ingredient):
|
||||
"""Fetch recipes asynchronously."""
|
||||
recipes = await fetch_recipes(ingredient)
|
||||
return recipes
|
||||
|
||||
async def post_async(self, shared, prep_res, recipes):
|
||||
"""Store recipes and continue."""
|
||||
shared["recipes"] = recipes
|
||||
shared["ingredient"] = prep_res
|
||||
return "suggest"
|
||||
|
||||
class SuggestRecipe(AsyncNode):
|
||||
"""AsyncNode that suggests a recipe using LLM."""
|
||||
|
||||
async def prep_async(self, shared):
|
||||
"""Get recipes from shared store."""
|
||||
return shared["recipes"]
|
||||
|
||||
async def exec_async(self, recipes):
|
||||
"""Get suggestion from LLM."""
|
||||
suggestion = await call_llm_async(
|
||||
f"Choose best recipe from: {', '.join(recipes)}"
|
||||
)
|
||||
return suggestion
|
||||
|
||||
async def post_async(self, shared, prep_res, suggestion):
|
||||
"""Store suggestion and continue."""
|
||||
shared["suggestion"] = suggestion
|
||||
return "approve"
|
||||
|
||||
class GetApproval(AsyncNode):
|
||||
"""AsyncNode that gets user approval."""
|
||||
|
||||
async def prep_async(self, shared):
|
||||
"""Get current suggestion."""
|
||||
return shared["suggestion"]
|
||||
|
||||
async def exec_async(self, suggestion):
|
||||
"""Ask for user approval."""
|
||||
answer = await get_user_input(f"\nAccept this recipe? (y/n): ")
|
||||
return answer
|
||||
|
||||
async def post_async(self, shared, prep_res, answer):
|
||||
"""Handle user's decision."""
|
||||
if answer == "y":
|
||||
print("\nGreat choice! Here's your recipe...")
|
||||
print(f"Recipe: {shared['suggestion']}")
|
||||
print(f"Ingredient: {shared['ingredient']}")
|
||||
return "accept"
|
||||
else:
|
||||
print("\nLet's try another recipe...")
|
||||
return "retry"
|
||||
@@ -0,0 +1,3 @@
|
||||
pocketflow
|
||||
aiohttp>=3.8.0 # For async HTTP requests
|
||||
openai>=1.0.0 # For async LLM calls
|
||||
@@ -0,0 +1,45 @@
|
||||
import asyncio
|
||||
import aiohttp
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
async def fetch_recipes(ingredient):
|
||||
"""Fetch recipes from an API asynchronously."""
|
||||
print(f"Fetching recipes for {ingredient}...")
|
||||
|
||||
# Simulate API call with delay
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Mock recipes (in real app, would fetch from API)
|
||||
recipes = [
|
||||
f"{ingredient} Stir Fry",
|
||||
f"Grilled {ingredient} with Herbs",
|
||||
f"Baked {ingredient} with Vegetables"
|
||||
]
|
||||
|
||||
print(f"Found {len(recipes)} recipes.")
|
||||
|
||||
return recipes
|
||||
|
||||
async def call_llm_async(prompt):
|
||||
"""Make async LLM call."""
|
||||
print("\nSuggesting best recipe...")
|
||||
|
||||
# Simulate LLM call with delay
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Mock LLM response (in real app, would call OpenAI)
|
||||
recipes = prompt.split(": ")[1].split(", ")
|
||||
suggestion = recipes[1] # Always suggest second recipe
|
||||
|
||||
print(f"How about: {suggestion}")
|
||||
return suggestion
|
||||
|
||||
async def get_user_input(prompt):
|
||||
"""Get user input asynchronously."""
|
||||
# Create event loop to handle async input
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# Get input in a non-blocking way
|
||||
answer = await loop.run_in_executor(None, input, prompt)
|
||||
|
||||
return answer.lower()
|
||||
Reference in New Issue
Block a user