update cmd to cli
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# PocketFlow Command-Line Joke Generator (Human-in-the-Loop Example)
|
||||
|
||||
A simple, interactive command-line application that generates jokes based on user-provided topics and direct human feedback. This serves as a clear example of a Human-in-the-Loop (HITL) workflow orchestrated by PocketFlow.
|
||||
|
||||
## Features
|
||||
|
||||
- **Interactive Joke Generation**: Ask for jokes on any topic.
|
||||
- **Human-in-the-Loop Feedback**: Dislike a joke? Your feedback directly influences the next generation attempt.
|
||||
- **Minimalist Design**: A straightforward example of using PocketFlow for HITL tasks.
|
||||
- **Powered by LLMs**: (Uses Anthropic Claude via an API call for joke generation).
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is part of the PocketFlow cookbook examples. It's assumed you have already cloned the [PocketFlow repository](https://github.com/the-pocket/PocketFlow) and are in the `cookbook/pocketflow-cli-hitl` directory.
|
||||
|
||||
1. **Install required dependencies**:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Set up your Anthropic API key**:
|
||||
The application uses Anthropic Claude to generate jokes. You need to set your API key as an environment variable.
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="your-anthropic-api-key-here"
|
||||
```
|
||||
You can test if your `call_llm.py` utility is working by running it directly:
|
||||
```bash
|
||||
python utils/call_llm.py
|
||||
```
|
||||
|
||||
3. **Run the Joke Generator**:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The system uses a simple PocketFlow workflow:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
GetTopic[GetTopicNode] --> GenerateJoke[GenerateJokeNode]
|
||||
GenerateJoke --> GetFeedback[GetFeedbackNode]
|
||||
GetFeedback -- "Approve" --> Z((End))
|
||||
GetFeedback -- "Disapprove" --> GenerateJoke
|
||||
```
|
||||
|
||||
1. **GetTopicNode**: Prompts the user to enter a topic for the joke.
|
||||
2. **GenerateJokeNode**: Sends the topic (and any previously disliked jokes as context) to an LLM to generate a new joke.
|
||||
3. **GetFeedbackNode**: Shows the joke to the user and asks if they liked it.
|
||||
* If **yes** (approved), the application ends.
|
||||
* If **no** (disapproved), the disliked joke is recorded, and the flow loops back to `GenerateJokeNode` to try again.
|
||||
|
||||
## Sample Output
|
||||
|
||||
Here's an example of an interaction with the Joke Generator:
|
||||
|
||||
```
|
||||
Welcome to the Command-Line Joke Generator!
|
||||
What topic would you like a joke about? Pocket Flow: 100-line LLM framework
|
||||
|
||||
Joke: Pocket Flow: Finally, an LLM framework that fits in your pocket! Too bad your model still needs a data center.
|
||||
Did you like this joke? (yes/no): no
|
||||
Okay, let me try another one.
|
||||
|
||||
Joke: Pocket Flow: A 100-line LLM framework where 99 lines are imports and the last line is `print("TODO: implement intelligence")`.
|
||||
Did you like this joke? (yes/no): yes
|
||||
Great! Glad you liked it.
|
||||
|
||||
Thanks for using the Joke Generator!
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- [`main.py`](./main.py): Entry point for the application.
|
||||
- [`flow.py`](./flow.py): Defines the PocketFlow graph and node connections.
|
||||
- [`nodes.py`](./nodes.py): Contains the definitions for `GetTopicNode`, `GenerateJokeNode`, and `GetFeedbackNode`.
|
||||
- [`utils/call_llm.py`](./utils/call_llm.py): Utility function to interact with the LLM (Anthropic Claude).
|
||||
- [`requirements.txt`](./requirements.txt): Lists project dependencies.
|
||||
- [`docs/design.md`](./docs/design.md): The design document for this application.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Design Doc: Command-Line Joke Generator
|
||||
|
||||
> Please DON'T remove notes for AI
|
||||
|
||||
## Requirements
|
||||
|
||||
> Notes for AI: Keep it simple and clear.
|
||||
> If the requirements are abstract, write concrete user stories
|
||||
|
||||
The system will be a command-line application that:
|
||||
1. Asks the user for a topic for a joke.
|
||||
2. Generates a joke based on the provided topic.
|
||||
3. Asks the user if they approve of the joke.
|
||||
4. If the user approves, the application can end or offer to generate another joke (for simplicity, we'll end for now).
|
||||
5. If the user does not approve, the application should:
|
||||
a. Take note that the user disliked the previous joke.
|
||||
b. Generate a new joke about the same topic, attempting to make it different from the disliked one.
|
||||
c. Repeat step 3.
|
||||
|
||||
## Flow Design
|
||||
|
||||
> Notes for AI:
|
||||
> 1. Consider the design patterns of agent, map-reduce, rag, and workflow. Apply them if they fit.
|
||||
> 2. Present a concise, high-level description of the workflow.
|
||||
|
||||
### Applicable Design Pattern:
|
||||
|
||||
**Agent**: The system acts as an agent that interacts with the user. It takes user input (topic, feedback), performs an action (generates a joke), and then decides the next step based on user feedback (either end or try generating another joke). This iterative process of generation and feedback fits the agent pattern.
|
||||
|
||||
### Flow high-level Design:
|
||||
|
||||
1. **GetTopicNode**: Prompts the user to enter the topic for the joke.
|
||||
2. **GenerateJokeNode**: Generates a joke based on the topic and any previous feedback.
|
||||
3. **GetFeedbackNode**: Presents the joke to the user and asks for approval. Based on the feedback, it either transitions to end the flow or back to `GenerateJokeNode`.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
GetTopic[GetTopicNode] --> GenerateJoke[GenerateJokeNode]
|
||||
GenerateJoke --> GetFeedback[GetFeedbackNode]
|
||||
GetFeedback -- "Approve" --> Z((End))
|
||||
GetFeedback -- "Disapprove" --> GenerateJoke
|
||||
```
|
||||
## Utility Functions
|
||||
|
||||
> Notes for AI:
|
||||
> 1. Understand the utility function definition thoroughly by reviewing the doc.
|
||||
> 2. Include only the necessary utility functions, based on nodes in the flow.
|
||||
|
||||
1. **Call LLM** (`utils/call_llm.py`)
|
||||
* *Input*: `prompt` (str), potentially including context like previously disliked jokes.
|
||||
* *Output*: `response` (str) - the generated joke.
|
||||
* *Necessity*: Used by `GenerateJokeNode` to generate jokes.
|
||||
|
||||
## Node Design
|
||||
|
||||
### Shared Store
|
||||
|
||||
> Notes for AI: Try to minimize data redundancy
|
||||
|
||||
The shared store structure is organized as follows:
|
||||
|
||||
```python
|
||||
shared = {
|
||||
"topic": None, # Stores the user-provided joke topic
|
||||
"current_joke": None, # Stores the most recently generated joke
|
||||
"disliked_jokes": [], # A list to store jokes the user didn't like, for context
|
||||
"user_feedback": None # Stores the user's latest feedback (e.g., "approve", "disapprove")
|
||||
}
|
||||
```
|
||||
|
||||
### Node Steps
|
||||
|
||||
> Notes for AI: Carefully decide whether to use Batch/Async Node/Flow.
|
||||
|
||||
1. **GetTopicNode**
|
||||
* *Purpose*: To get the desired joke topic from the user.
|
||||
* *Type*: Regular
|
||||
* *Steps*:
|
||||
* `prep`: (None needed for the first run, or could check if a topic already exists if we were to loop for a new topic)
|
||||
* `exec`: Prompt the user via `input()` for a joke topic.
|
||||
* `post`: Store the user's input topic into `shared["topic"]`. Return `"default"` action to proceed to `GenerateJokeNode`.
|
||||
|
||||
2. **GenerateJokeNode**
|
||||
* *Purpose*: To generate a joke using an LLM, based on the topic and any previously disliked jokes.
|
||||
* *Type*: Regular
|
||||
* *Steps*:
|
||||
* `prep`: Read `shared["topic"]` and `shared["disliked_jokes"]`. Construct a prompt for the LLM, including the topic and a message like "The user did not like the following jokes: [list of disliked jokes]. Please generate a new, different joke about [topic]."
|
||||
* `exec`: Call the `call_llm` utility function with the prepared prompt.
|
||||
* `post`: Store the generated joke in `shared["current_joke"]`. Print the joke to the console. Return `"default"` action to proceed to `GetFeedbackNode`.
|
||||
|
||||
3. **GetFeedbackNode**
|
||||
* *Purpose*: To get feedback from the user about the generated joke and decide the next step.
|
||||
* *Type*: Regular
|
||||
* *Steps*:
|
||||
* `prep`: Read `shared["current_joke"]`.
|
||||
* `exec`: Prompt the user (e.g., "Did you like this joke? (yes/no) or (approve/disapprove): "). Get user's input.
|
||||
* `post`:
|
||||
* If user input indicates approval (e.g., "yes", "approve"):
|
||||
* Store "approve" in `shared["user_feedback"]`.
|
||||
* Return `"Approve"` action (leading to flow termination or a thank you message).
|
||||
* If user input indicates disapproval (e.g., "no", "disapprove"):
|
||||
* Store "disapprove" in `shared["user_feedback"]`.
|
||||
* Add `shared["current_joke"]` to the `shared["disliked_jokes"]` list.
|
||||
* Return `"Disapprove"` action (leading back to `GenerateJokeNode`).
|
||||
@@ -0,0 +1,15 @@
|
||||
from pocketflow import Flow
|
||||
from nodes import GetTopicNode, GenerateJokeNode, GetFeedbackNode
|
||||
|
||||
def create_joke_flow() -> Flow:
|
||||
"""Creates and returns the joke generation flow."""
|
||||
get_topic_node = GetTopicNode()
|
||||
generate_joke_node = GenerateJokeNode()
|
||||
get_feedback_node = GetFeedbackNode()
|
||||
|
||||
get_topic_node >> generate_joke_node
|
||||
generate_joke_node >> get_feedback_node
|
||||
get_feedback_node - "Disapprove" >> generate_joke_node
|
||||
|
||||
joke_flow = Flow(start=get_topic_node)
|
||||
return joke_flow
|
||||
@@ -0,0 +1,20 @@
|
||||
from flow import create_joke_flow
|
||||
|
||||
def main():
|
||||
"""Main function to run the joke generator application."""
|
||||
print("Welcome to the Command-Line Joke Generator!")
|
||||
|
||||
shared = {
|
||||
"topic": None,
|
||||
"current_joke": None,
|
||||
"disliked_jokes": [],
|
||||
"user_feedback": None
|
||||
}
|
||||
|
||||
joke_flow = create_joke_flow()
|
||||
joke_flow.run(shared)
|
||||
|
||||
print("\nThanks for using the Joke Generator!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
from pocketflow import Node
|
||||
from utils.call_llm import call_llm
|
||||
|
||||
class GetTopicNode(Node):
|
||||
"""Prompts the user to enter the topic for the joke."""
|
||||
def exec(self, _shared):
|
||||
return input("What topic would you like a joke about? ")
|
||||
|
||||
def post(self, shared, _prep_res, exec_res):
|
||||
shared["topic"] = exec_res
|
||||
|
||||
class GenerateJokeNode(Node):
|
||||
"""Generates a joke based on the topic and any previous feedback."""
|
||||
def prep(self, shared):
|
||||
topic = shared.get("topic", "anything")
|
||||
disliked_jokes = shared.get("disliked_jokes", [])
|
||||
|
||||
prompt = f"Please generate an one-liner joke about: {topic}. Make it short and funny."
|
||||
if disliked_jokes:
|
||||
disliked_str = "; ".join(disliked_jokes)
|
||||
prompt = f"The user did not like the following jokes: [{disliked_str}]. Please generate a new, different joke about {topic}."
|
||||
return prompt
|
||||
|
||||
def exec(self, prep_res):
|
||||
return call_llm(prep_res)
|
||||
|
||||
def post(self, shared, _prep_res, exec_res):
|
||||
shared["current_joke"] = exec_res
|
||||
print(f"\nJoke: {exec_res}")
|
||||
|
||||
class GetFeedbackNode(Node):
|
||||
"""Presents the joke to the user and asks for approval."""
|
||||
def exec(self, _prep_res):
|
||||
while True:
|
||||
feedback = input("Did you like this joke? (yes/no): ").strip().lower()
|
||||
if feedback in ["yes", "y", "no", "n"]:
|
||||
return feedback
|
||||
print("Invalid input. Please type 'yes' or 'no'.")
|
||||
|
||||
def post(self, shared, _prep_res, exec_res):
|
||||
if exec_res in ["yes", "y"]:
|
||||
shared["user_feedback"] = "approve"
|
||||
print("Great! Glad you liked it.")
|
||||
return "Approve"
|
||||
else:
|
||||
shared["user_feedback"] = "disapprove"
|
||||
current_joke = shared.get("current_joke")
|
||||
if current_joke:
|
||||
if "disliked_jokes" not in shared:
|
||||
shared["disliked_jokes"] = []
|
||||
shared["disliked_jokes"].append(current_joke)
|
||||
print("Okay, let me try another one.")
|
||||
return "Disapprove"
|
||||
@@ -0,0 +1,2 @@
|
||||
pocketflow>=0.0.1
|
||||
anthropic>=0.20.0 # Or a recent version
|
||||
@@ -0,0 +1,24 @@
|
||||
from anthropic import Anthropic
|
||||
import os
|
||||
|
||||
def call_llm(prompt: str) -> str:
|
||||
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY", "your-anthropic-api-key")) # Default if key not found
|
||||
response = client.messages.create(
|
||||
model="claude-3-haiku-20240307", # Using a smaller model for jokes
|
||||
max_tokens=150, # Jokes don't need to be very long
|
||||
messages=[
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
)
|
||||
return response.content[0].text
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Testing Anthropic LLM call for jokes:")
|
||||
joke_prompt = "Tell me a one-liner joke about a cat."
|
||||
print(f"Prompt: {joke_prompt}")
|
||||
try:
|
||||
response = call_llm(joke_prompt)
|
||||
print(f"Response: {response}")
|
||||
except Exception as e:
|
||||
print(f"Error calling LLM: {e}")
|
||||
print("Please ensure your ANTHROPIC_API_KEY environment variable is set correctly.")
|
||||
Reference in New Issue
Block a user