pocketflow/cookbook/pocketflow-gradio-hitl/nodes.py

242 lines
9.1 KiB
Python

from datetime import datetime
from queue import Queue
import yaml
from pocketflow import Node
from utils.call_llm import call_llm
from utils.call_mock_api import call_book_hotel_api, call_check_weather_api
from utils.conversation import load_conversation, save_conversation
from utils.format_chat_history import format_chat_history
class DecideAction(Node):
def prep(self, shared):
conversation_id = shared["conversation_id"]
session = load_conversation(conversation_id)
return session, shared["history"], shared["query"]
def exec(self, prep_res):
session, history, query = prep_res
prompt = f"""
### INSTRUCTIONS
You are a lifestyle assistant capable of helping users book hotels and check weather conditions.
You need to decide the next action based on your last action, action execution result, chat history, and current user question.
### CHAT HISTORY
{format_chat_history(history)}
### CURRENT USER QUESTION
user: {query}
### CONTEXT
Last Action: {session.get("last_action", None)}
Last Action Result: {session.get("action_result", None)}
Current Date: {datetime.now().date()}
### ACTION SPACE
[1] check-weather
Description: When the user asks about the weather, use this tool.
Parameters:
- name: city
description: The city to check the weather
required: true
example: Beijing
- name: date
description: The date to check the weather, if not provided, use the current date
required: false
example: 2025-05-28
[2] book-hotel
Description: When the user wants to book a hotel, use this tool.
Parameters:
- name: hotel
description: The name of the hotel to be booked
required: true
example: ShanghaiHilton Hotel
- name: checkin_date
description: The check-in date
required: true
example: 2025-05-28
- name: checkout_date
description: The check-out date
required: true
example: 2025-05-29
[3] follow-up
Description: 1. When the user's question is out of the scope of booking hotels and checking weather, use this tool to guide the user; 2. When the current information cannot meet the parameter requirements of the corresponding tool, use this tool to ask the user.
Parameters:
- name: question
description: Your guidance or follow-up to the user, maintain an enthusiastic and lively language style, and use the same language as the user's question.
required: true
example: Which hotel would you like to book?😊
[4] result-notification
Description: When the booking of a hotel or checking the weather is completed, use this tool to notify the user of the result and ask if they need any other help. If you find that the user's question is not completed in the history conversation, you can guide the user to complete the intention in the last step.
Parameters:
- name: result
description: Notify the user of the result based on the Last Action Result. Maintain an enthusiastic and lively language style, and use the same language as the user's question.
required: true
example: The hotel has been successfully booked for you. 😉\n\nThe check-in date is XX, and the check-out date is XX. Thank you for using it. Would you like any other help?😀
## NEXT ACTION
Decide the next action based on the context and available actions.
Return your response in this format:
```yaml
thinking: |
<your step-by-step reasoning process>
action: check-weather OR book-hotel OR follow-up OR result-notification
reason: <why you chose this action>
question: <if action is follow-up>
city: <if action is check-weather>
hotel: <if action is book-hotel>
checkin_date: <if action is book-hotel>
checkout_date: <if action is book-hotel>
result: <if action is result-notification>
```
IMPORTANT: Make sure to:
1. Use proper indentation (4 spaces) for all multi-line fields
2. Use the | character for multi-line text fields
3. Keep single-line fields without the | character
"""
response = call_llm(prompt.strip())
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
print(f"🤖 Agent response: \n{yaml_str}")
decision = yaml.safe_load(yaml_str)
return decision
def post(self, shared, prep_res, exec_res):
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
"""Save the decision and determine the next step in the flow."""
# If LLM decided to search, save the search query
session["last_action"] = exec_res["action"]
flow_log: Queue = shared["flow_queue"]
for line in exec_res["thinking"].split("\n"):
line = line.replace("-", "").strip()
if line:
flow_log.put(f"🤔 {line}")
if exec_res["action"] == "check-weather":
session["check_weather_params"] = {
"city": exec_res["city"],
"date": exec_res.get("date", None),
}
flow_log.put(f"➡️ Agent decided to check weather for: {exec_res['city']}")
elif exec_res["action"] == "book-hotel":
session["book_hotel_params"] = {
"hotel": exec_res["hotel"],
"checkin_date": exec_res["checkin_date"],
"checkout_date": exec_res["checkout_date"],
}
flow_log.put(f"➡️ Agent decided to book hotel: {exec_res['hotel']}")
elif exec_res["action"] == "follow-up":
session["follow_up_params"] = {"question": exec_res["question"]}
flow_log.put(f"➡️ Agent decided to follow up: {exec_res['question']}")
elif exec_res["action"] == "result-notification":
session["result_notification_params"] = {"result": exec_res["result"]}
flow_log.put(f"➡️ Agent decided to notify the result: {exec_res['result']}")
save_conversation(conversation_id, session)
# Return the action to determine the next node in the flow
return exec_res["action"]
class CheckWeather(Node):
def prep(self, shared):
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
city = session["check_weather_params"]["city"]
date = session["check_weather_params"].get("date", None)
return city, date
def exec(self, prep_res):
city, date = prep_res
return call_check_weather_api(city, date)
def post(self, shared, prep_res, exec_res):
flow_log: Queue = shared["flow_queue"]
flow_log.put(f"⬅️ Check weather result: {exec_res}")
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
session["action_result"] = exec_res
save_conversation(conversation_id, session)
return "default"
class BookHotel(Node):
def prep(self, shared):
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
hotel = session["book_hotel_params"]["hotel"]
checkin_date = session["book_hotel_params"]["checkin_date"]
checkout_date = session["book_hotel_params"]["checkout_date"]
return hotel, checkin_date, checkout_date
def exec(self, prep_res):
hotel, checkin_date, checkout_date = prep_res
return call_book_hotel_api(hotel, checkin_date, checkout_date)
def post(self, shared, prep_res, exec_res):
flow_log: Queue = shared["flow_queue"]
flow_log.put(f"⬅️ Book hotel result: {exec_res}")
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
session["action_result"] = exec_res
save_conversation(conversation_id, session)
return "default"
class FollowUp(Node):
def prep(self, shared):
flow_log: Queue = shared["flow_queue"]
flow_log.put(None)
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
question = session["follow_up_params"]["question"]
return question, shared["queue"]
def exec(self, prep_res):
question, queue = prep_res
queue.put(question)
queue.put(None)
return question
def post(self, shared, prep_res, exec_res):
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
session["action_result"] = exec_res
return "done"
class ResultNotification(Node):
def prep(self, shared):
flow_log: Queue = shared["flow_queue"]
flow_log.put(None)
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
result = session["result_notification_params"]["result"]
return result, shared["queue"]
def exec(self, prep_res):
result, queue = prep_res
queue.put(result)
queue.put(None)
return result
def post(self, shared, prep_res, exec_res):
conversation_id = shared["conversation_id"]
session: dict = load_conversation(conversation_id)
session["action_result"] = None
session["last_action"] = None
save_conversation(conversation_id, session)
return "done"