update cmd to cli

This commit is contained in:
zachary62
2025-06-01 17:45:14 -04:00
parent 6cf3ec4984
commit 02ff1e7e9b
9 changed files with 2 additions and 2 deletions
+80
View File
@@ -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.
+104
View File
@@ -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`).
+15
View File
@@ -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
+20
View File
@@ -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()
+53
View File
@@ -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.")