add multi-agent tutorial
This commit is contained in:
parent
eb1c721e00
commit
7473d4a017
|
|
@ -70,11 +70,13 @@ From there, it's easy to implement popular design patterns like ([Multi-](https:
|
||||||
| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ☆☆☆ <br> *Dummy* | A resume qualification processor using map-reduce pattern for batch evaluation |
|
| [Map-Reduce](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-map-reduce) | ☆☆☆ <br> *Dummy* | A resume qualification processor using map-reduce pattern for batch evaluation |
|
||||||
| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Dummy* | A research agent that can search the web and answer questions |
|
| [Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-agent) | ☆☆☆ <br> *Dummy* | A research agent that can search the web and answer questions |
|
||||||
| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Dummy* | A real-time LLM streaming demo with user interrupt capability |
|
| [Streaming](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-llm-streaming) | ☆☆☆ <br> *Dummy* | A real-time LLM streaming demo with user interrupt capability |
|
||||||
| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Beginner* | A parallel execution demo that shows 3x speedup |
|
| [Multi-Agent](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-multi-agent) | ★☆☆ <br> *Beginner* | A Taboo word game for asynchronous communication between two agents |
|
||||||
| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Beginner* | Research agent is getting unreliable... Let's build a supervision process|
|
| [Supervisor](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-supervisor) | ★☆☆ <br> *Beginner* | Research agent is getting unreliable... Let's build a supervision process|
|
||||||
|
| [Parallel](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-parallel-batch) | ★☆☆ <br> *Beginner* | A parallel execution demo that shows 3x speedup |
|
||||||
| [Thinking](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Beginner* | Solve complex reasoning problems through Chain-of-Thought |
|
| [Thinking](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-thinking) | ★☆☆ <br> *Beginner* | Solve complex reasoning problems through Chain-of-Thought |
|
||||||
| [Memory](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Beginner* | A chat bot with short-term and long-term memory |
|
| [Memory](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-chat-memory) | ★☆☆ <br> *Beginner* | A chat bot with short-term and long-term memory |
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
👀 Want to see other tutorials for dummies? [Create an issue!](https://github.com/The-Pocket/PocketFlow/issues/new)
|
👀 Want to see other tutorials for dummies? [Create an issue!](https://github.com/The-Pocket/PocketFlow/issues/new)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Multi-Agent Taboo Game
|
||||||
|
|
||||||
|
A PocketFlow example that demonstrates how to implement asynchronous multi-agent communication using the Taboo word guessing game.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Implement asynchronous communication between two AI agents (Hinter and Guesser)
|
||||||
|
- Use AsyncNode for non-blocking agent interactions
|
||||||
|
- Create dynamic conversation flow through asyncio message queues
|
||||||
|
- Demonstrate complex turn-based game mechanics with LLMs
|
||||||
|
- Automatically terminate the game when the correct word is guessed
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install the required dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set your OpenAI API key as an environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OPENAI_API_KEY=your_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The workflow follows an asynchronous multi-agent communication pattern:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
AsyncHinter[AsyncHinter Node] <--> MessageQueue{Message Queue}
|
||||||
|
MessageQueue <--> AsyncGuesser[AsyncGuesser Node]
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's what each component does:
|
||||||
|
|
||||||
|
1. **AsyncHinter Node**: Generates hints about the target word while avoiding forbidden words
|
||||||
|
2. **AsyncGuesser Node**: Makes guesses based on the hints received from the Hinter
|
||||||
|
3. **Message Queue**: Facilitates asynchronous communication between the agents
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- [`main.py`](./main.py): Main entry point implementing the AsyncHinter and AsyncGuesser nodes and game flow
|
||||||
|
- [`utils.py`](./utils.py): Utility functions including LLM wrappers for generating hints and guesses
|
||||||
|
- [`requirements.txt`](./requirements.txt): Lists the required dependencies
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
=========== Taboo Game Starting! ===========
|
||||||
|
Target word: nostalgic
|
||||||
|
Forbidden words: ['memory', 'past', 'remember', 'feeling', 'longing']
|
||||||
|
============================================
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Sentiment for earlier times.
|
||||||
|
Guesser: I guess it's - Nostalgia
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Sentiment for earlier times.
|
||||||
|
Guesser: I guess it's - Reminiscence
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Yearning for days gone by.
|
||||||
|
Guesser: I guess it's - Sentimentality
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Reliving cherished moments or experiences.
|
||||||
|
Guesser: I guess it's - Memories
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Recollection of cherished experiences.
|
||||||
|
Guesser: I guess it's - Reflection
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Yearning for earlier times.
|
||||||
|
Guesser: I guess it's - Longing
|
||||||
|
|
||||||
|
Hinter: Here's your hint - Sentiment for earlier times.
|
||||||
|
Guesser: I guess it's - Nostalgic
|
||||||
|
Game Over - Correct guess!
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import asyncio
|
||||||
|
from pocketflow import AsyncNode, AsyncFlow
|
||||||
|
from utils import call_llm
|
||||||
|
|
||||||
|
class AsyncHinter(AsyncNode):
|
||||||
|
async def prep_async(self, shared):
|
||||||
|
# Wait for message from guesser (or empty string at start)
|
||||||
|
guess = await shared["hinter_queue"].get()
|
||||||
|
if guess == "GAME_OVER":
|
||||||
|
return None
|
||||||
|
return shared["target_word"], shared["forbidden_words"], shared.get("past_guesses", [])
|
||||||
|
|
||||||
|
async def exec_async(self, inputs):
|
||||||
|
if inputs is None:
|
||||||
|
return None
|
||||||
|
target, forbidden, past_guesses = inputs
|
||||||
|
prompt = f"Generate hint for '{target}'\nForbidden words: {forbidden}"
|
||||||
|
if past_guesses:
|
||||||
|
prompt += f"\nPrevious wrong guesses: {past_guesses}\nMake hint more specific."
|
||||||
|
prompt += "\nUse at most 5 words."
|
||||||
|
|
||||||
|
hint = call_llm(prompt)
|
||||||
|
print(f"\nHinter: Here's your hint - {hint}")
|
||||||
|
return hint
|
||||||
|
|
||||||
|
async def post_async(self, shared, prep_res, exec_res):
|
||||||
|
if exec_res is None:
|
||||||
|
return "end"
|
||||||
|
# Send hint to guesser
|
||||||
|
await shared["guesser_queue"].put(exec_res)
|
||||||
|
return "continue"
|
||||||
|
|
||||||
|
class AsyncGuesser(AsyncNode):
|
||||||
|
async def prep_async(self, shared):
|
||||||
|
# Wait for hint from hinter
|
||||||
|
hint = await shared["guesser_queue"].get()
|
||||||
|
return hint, shared.get("past_guesses", [])
|
||||||
|
|
||||||
|
async def exec_async(self, inputs):
|
||||||
|
hint, past_guesses = inputs
|
||||||
|
prompt = f"Given hint: {hint}, past wrong guesses: {past_guesses}, make a new guess. Directly reply a single word:"
|
||||||
|
guess = call_llm(prompt)
|
||||||
|
print(f"Guesser: I guess it's - {guess}")
|
||||||
|
return guess
|
||||||
|
|
||||||
|
async def post_async(self, shared, prep_res, exec_res):
|
||||||
|
# Check if guess is correct
|
||||||
|
if exec_res.lower() == shared["target_word"].lower():
|
||||||
|
print("Game Over - Correct guess!")
|
||||||
|
await shared["hinter_queue"].put("GAME_OVER")
|
||||||
|
return "end"
|
||||||
|
|
||||||
|
# Store the guess in shared state
|
||||||
|
if "past_guesses" not in shared:
|
||||||
|
shared["past_guesses"] = []
|
||||||
|
shared["past_guesses"].append(exec_res)
|
||||||
|
|
||||||
|
# Send guess to hinter
|
||||||
|
await shared["hinter_queue"].put(exec_res)
|
||||||
|
return "continue"
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
# Set up game
|
||||||
|
shared = {
|
||||||
|
"target_word": "nostalgic",
|
||||||
|
"forbidden_words": ["memory", "past", "remember", "feeling", "longing"],
|
||||||
|
"hinter_queue": asyncio.Queue(),
|
||||||
|
"guesser_queue": asyncio.Queue()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("=========== Taboo Game Starting! ===========")
|
||||||
|
print(f"Target word: {shared['target_word']}")
|
||||||
|
print(f"Forbidden words: {shared['forbidden_words']}")
|
||||||
|
print("============================================")
|
||||||
|
|
||||||
|
# Initialize by sending empty guess to hinter
|
||||||
|
await shared["hinter_queue"].put("")
|
||||||
|
|
||||||
|
# Create nodes and flows
|
||||||
|
hinter = AsyncHinter()
|
||||||
|
guesser = AsyncGuesser()
|
||||||
|
|
||||||
|
# Set up flows
|
||||||
|
hinter_flow = AsyncFlow(start=hinter)
|
||||||
|
guesser_flow = AsyncFlow(start=guesser)
|
||||||
|
|
||||||
|
# Connect nodes to themselves for looping
|
||||||
|
hinter - "continue" >> hinter
|
||||||
|
guesser - "continue" >> guesser
|
||||||
|
|
||||||
|
# Run both agents concurrently
|
||||||
|
await asyncio.gather(
|
||||||
|
hinter_flow.run_async(shared),
|
||||||
|
guesser_flow.run_async(shared)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("=========== Game Complete! ===========")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import os
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
def call_llm(prompt):
|
||||||
|
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "your-api-key"))
|
||||||
|
r = client.chat.completions.create(
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
messages=[{"role": "user", "content": prompt}]
|
||||||
|
)
|
||||||
|
return r.choices[0].message.content
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(call_llm("Tell me a short joke"))
|
||||||
Loading…
Reference in New Issue