Spaces:
Sleeping
Sleeping
update app simulator
Browse files- app.py +47 -52
- chatbot_simulator.py +125 -79
- prompts.py +89 -0
app.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1 |
import gradio as gr
|
2 |
from chatbot_simulator import ChatbotSimulation
|
3 |
from task_specific_data_population import DataPopulation
|
4 |
-
import
|
5 |
-
import
|
|
|
6 |
|
7 |
openai_api_key = os.getenv("OPENAI_API_KEY")
|
8 |
|
@@ -12,42 +13,46 @@ class AppSimulator:
|
|
12 |
self.simulation = None
|
13 |
self.openai_api_key = openai_api_key
|
14 |
|
15 |
-
def initialize_simulator(self,
|
16 |
"""Initialize the simulator with retries and elapsed time tracking."""
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
51 |
|
52 |
def chat_interaction(self, user_input, history):
|
53 |
"""Handle one round of conversation."""
|
@@ -57,19 +62,11 @@ class AppSimulator:
|
|
57 |
# Initialize the simulator
|
58 |
simulator_app = AppSimulator(openai_api_key=openai_api_key)
|
59 |
|
60 |
-
def is_valid_input(user_input):
|
61 |
-
"""Validate user input format."""
|
62 |
-
pattern = r"^\d+\.\s+.*"
|
63 |
-
return bool(re.match(pattern, user_input))
|
64 |
|
65 |
def chat(user_input, history):
|
66 |
"""Chat handler that validates input and interacts with the simulator."""
|
67 |
-
|
68 |
-
|
69 |
-
return valid_response
|
70 |
-
else:
|
71 |
-
invalid_response = "Invalid input. Please use the format: Number. Description: query"
|
72 |
-
return invalid_response
|
73 |
|
74 |
|
75 |
# Gradio Interface using ChatInterface
|
@@ -77,8 +74,6 @@ with gr.Blocks(fill_height=True) as demo:
|
|
77 |
gr.Markdown("## Simulator Setup")
|
78 |
|
79 |
# Input fields for initialization
|
80 |
-
task_input = gr.Textbox(label="Task", placeholder="Describe your task...")
|
81 |
-
app_name_input = gr.Textbox(label="App Name", placeholder="Enter the app name... (eg. DoorDash)")
|
82 |
sitemap_input = gr.Textbox(label="Sitemap", placeholder="Enter the Hugging Face link to sitemap... (eg.jjz5463/DoorDash_synthetic_sitemap)")
|
83 |
initialize_button = gr.Button("Initialize Simulator")
|
84 |
|
@@ -89,13 +84,13 @@ with gr.Blocks(fill_height=True) as demo:
|
|
89 |
chat_interface = gr.ChatInterface(fn=chat, type='messages')
|
90 |
|
91 |
# Define the callback function to initialize the simulator and update status
|
92 |
-
def initialize_and_start_chat(
|
93 |
-
return simulator_app.initialize_simulator(
|
94 |
|
95 |
# Set up the button click to initialize simulator and update status only
|
96 |
initialize_button.click(
|
97 |
fn=initialize_and_start_chat,
|
98 |
-
inputs=[
|
99 |
outputs=status # Update only the status block
|
100 |
)
|
101 |
|
|
|
1 |
import gradio as gr
|
2 |
from chatbot_simulator import ChatbotSimulation
|
3 |
from task_specific_data_population import DataPopulation
|
4 |
+
from datasets import load_dataset
|
5 |
+
import json_repair
|
6 |
+
import random
|
7 |
|
8 |
openai_api_key = os.getenv("OPENAI_API_KEY")
|
9 |
|
|
|
13 |
self.simulation = None
|
14 |
self.openai_api_key = openai_api_key
|
15 |
|
16 |
+
def initialize_simulator(self, sitemap_url, progress=gr.Progress(track_tqdm=True)):
|
17 |
"""Initialize the simulator with retries and elapsed time tracking."""
|
18 |
+
synthetic_sitemap = load_dataset(sitemap_url, "sitemap", split='train')
|
19 |
+
app_name, sitemap, page_details, user_state, system_data = None, None, None, None, None
|
20 |
+
for row in synthetic_sitemap:
|
21 |
+
if row['name'] == 'app_name':
|
22 |
+
app_name = row['value'] # Use `eval` to convert the string to a list
|
23 |
+
elif row['name'] == 'sitemap':
|
24 |
+
sitemap = json_repair.loads(row['value'])
|
25 |
+
elif row['name'] == 'page_details':
|
26 |
+
page_details = json_repair.loads(row['value'])
|
27 |
+
elif row['name'] == 'user_state':
|
28 |
+
user_state = json_repair.loads(row['value'])
|
29 |
+
elif row['name'] == 'system_data':
|
30 |
+
system_data = json_repair.loads(row['value'])
|
31 |
+
|
32 |
+
synthetic_tasks = load_dataset(sitemap_url, "tasks", split='train')
|
33 |
+
random_index = random.randint(0, len(synthetic_tasks) - 1) # Generate a random index
|
34 |
+
random_row = synthetic_tasks[random_index]
|
35 |
+
task = random_row['tasks']
|
36 |
+
solution = random_row['steps']
|
37 |
+
user_data = random_row['attributes']['user_data']
|
38 |
+
|
39 |
+
self.simulation = ChatbotSimulation(
|
40 |
+
app_name=app_name,
|
41 |
+
site_map=sitemap,
|
42 |
+
page_details=page_details,
|
43 |
+
user_state=user_state,
|
44 |
+
system_data=system_data,
|
45 |
+
user_data=user_data,
|
46 |
+
task=task,
|
47 |
+
solution=solution,
|
48 |
+
log_location=f'conversation_log_{app_name}.txt',
|
49 |
+
openai_api_key=openai_api_key,
|
50 |
+
agent='llm'
|
51 |
+
)
|
52 |
+
|
53 |
+
initial_message = self.simulation.start_conversation()
|
54 |
+
progress.update("Initialization Successful")
|
55 |
+
return initial_message # Return the initial assistant message for chat
|
56 |
|
57 |
def chat_interaction(self, user_input, history):
|
58 |
"""Handle one round of conversation."""
|
|
|
62 |
# Initialize the simulator
|
63 |
simulator_app = AppSimulator(openai_api_key=openai_api_key)
|
64 |
|
|
|
|
|
|
|
|
|
65 |
|
66 |
def chat(user_input, history):
|
67 |
"""Chat handler that validates input and interacts with the simulator."""
|
68 |
+
response = simulator_app.chat_interaction(user_input, history)
|
69 |
+
return response
|
|
|
|
|
|
|
|
|
70 |
|
71 |
|
72 |
# Gradio Interface using ChatInterface
|
|
|
74 |
gr.Markdown("## Simulator Setup")
|
75 |
|
76 |
# Input fields for initialization
|
|
|
|
|
77 |
sitemap_input = gr.Textbox(label="Sitemap", placeholder="Enter the Hugging Face link to sitemap... (eg.jjz5463/DoorDash_synthetic_sitemap)")
|
78 |
initialize_button = gr.Button("Initialize Simulator")
|
79 |
|
|
|
84 |
chat_interface = gr.ChatInterface(fn=chat, type='messages')
|
85 |
|
86 |
# Define the callback function to initialize the simulator and update status
|
87 |
+
def initialize_and_start_chat(sitemap):
|
88 |
+
return simulator_app.initialize_simulator(sitemap) # Use progress tracking
|
89 |
|
90 |
# Set up the button click to initialize simulator and update status only
|
91 |
initialize_button.click(
|
92 |
fn=initialize_and_start_chat,
|
93 |
+
inputs=[sitemap_input],
|
94 |
outputs=status # Update only the status block
|
95 |
)
|
96 |
|
chatbot_simulator.py
CHANGED
@@ -3,20 +3,26 @@ import json_repair
|
|
3 |
from transformers import AutoTokenizer
|
4 |
from openai import RateLimitError
|
5 |
import time
|
|
|
|
|
6 |
|
7 |
|
8 |
class ChatbotSimulation:
|
9 |
-
def __init__(self, site_map, page_details, user_state, task,
|
10 |
-
|
11 |
max_steps=50, max_tokens=8192, buffer_tokens=500):
|
|
|
12 |
self.sitemap = site_map
|
13 |
self.page_details = page_details
|
14 |
self.user_state = user_state
|
15 |
self.user_state['current_page'] = 'Home' # Initialize current page
|
16 |
-
self.user_state['last_page'] = 'Home'
|
17 |
self.user_state['task_completed'] = 'False'
|
|
|
|
|
|
|
18 |
self.task = task
|
19 |
-
self.
|
|
|
20 |
self.log_location = log_location
|
21 |
self.agent = agent.lower()
|
22 |
if self.agent not in ['human', 'llm']:
|
@@ -28,11 +34,14 @@ class ChatbotSimulation:
|
|
28 |
self.prompt_count = 0
|
29 |
self.client = OpenAI(api_key=openai_api_key)
|
30 |
self.actions = []
|
31 |
-
self.tokenizer = AutoTokenizer.from_pretrained("gpt2")
|
|
|
|
|
|
|
32 |
|
33 |
def _get_page_uid(self, page_name):
|
34 |
"""Retrieve the UID of the given page from the sitemap."""
|
35 |
-
return self.sitemap
|
36 |
|
37 |
def _get_page_details(self, page_name):
|
38 |
"""Retrieve the page details using its UID."""
|
@@ -41,43 +50,22 @@ class ChatbotSimulation:
|
|
41 |
|
42 |
def _generate_system_prompt(self):
|
43 |
"""Create a dynamic system prompt based on the current state."""
|
44 |
-
current_page = self.user_state['current_page']
|
45 |
-
last_page = self.user_state['last_page']
|
|
|
|
|
46 |
page_info = self._get_page_details(current_page)
|
47 |
|
48 |
-
return
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
Present this fabricated information convincingly as if it were real data.
|
59 |
-
{page_info}
|
60 |
-
|
61 |
-
- **Features**: Represent available options the user can select on this page.
|
62 |
-
- **User Data**: Represents user-specific data accessible on this page.
|
63 |
-
|
64 |
-
Provide instructions or request input from the user. If the user provides an invalid action, respond with:
|
65 |
-
"Invalid action. Please select a valid option."
|
66 |
-
|
67 |
-
### Instruction Format:
|
68 |
-
<if actions is non-empty: You have successfully done actions[-1]> You are at the {current_page} page. You have the following options:
|
69 |
-
1. Feature 1
|
70 |
-
2. Feature 2
|
71 |
-
3. Feature 3
|
72 |
-
4. Feature 4
|
73 |
-
|
74 |
-
Please enter your choice as 'Number. Description'. If you have a query, enter as 'Number. Description: query'
|
75 |
-
|
76 |
-
Rules:
|
77 |
-
- Be sure to display all options that is available in features.
|
78 |
-
- Be robotic and emotionless. Avoid offering any advice to the user.
|
79 |
-
- Make sure there is always a back to last_page and Home page button.
|
80 |
-
"""
|
81 |
|
82 |
def _get_openai_response(self, prompt):
|
83 |
"""Fetch response from OpenAI API."""
|
@@ -120,81 +108,139 @@ Rules:
|
|
120 |
def one_conversation_round(self, user_input):
|
121 |
"""Conduct one round of conversation between the user and the assistant."""
|
122 |
# User provides input
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
self.actions.append(user_input + f'on {self.user_state["current_page"]} page')
|
124 |
self.conversation.append({"role": "user", "content": user_input})
|
125 |
self.prompt_count += 1
|
126 |
|
127 |
# Update user state using GPT's response
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
Instructions:
|
136 |
-
1. If the 'current_page' has changed, update it to a page from the sitemap.
|
137 |
-
2. If the task is finished, update 'task_completed' to True. Otherwise, leave it as False.
|
138 |
-
3. If no updates are needed, return the user state exactly as provided, without modification.
|
139 |
-
|
140 |
-
Important:
|
141 |
-
- Ensure 'current_page' and 'task_completed' are keys in the returned dictionary.
|
142 |
-
- Return only the dictionary without additional output or wrapping.
|
143 |
-
|
144 |
-
Example Output Format:
|
145 |
-
{{
|
146 |
-
'current_page': 'Home',
|
147 |
-
'last_page': 'Home',
|
148 |
-
'task_completed': 'False',
|
149 |
-
}}
|
150 |
-
"""
|
151 |
|
152 |
self.conversation.append({"role": "assistant", "content": update_prompt})
|
153 |
-
updated_state = self._get_openai_response(self.conversation)
|
154 |
self.conversation.pop(-1) ## update prompt don't have to stay in conversation history
|
155 |
|
156 |
# Parse and update the user state
|
157 |
updated_state = json_repair.loads(updated_state)
|
158 |
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
|
|
|
|
163 |
Example Output Format:
|
164 |
{{
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
}}
|
169 |
"""
|
170 |
updated_state = self._get_openai_response([{"role": "system", "content": transform_prompt}])
|
171 |
updated_state = json_repair.loads(updated_state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
|
173 |
try:
|
174 |
-
if updated_state['task_completed'].lower() == 'true':
|
175 |
return f"Task completed! You took {self.prompt_count} steps."
|
176 |
except:
|
177 |
updated_state['task_completed'] = 'False'
|
178 |
|
179 |
self.user_state = updated_state
|
|
|
|
|
|
|
|
|
180 |
|
181 |
-
#self.conversation.clear()
|
182 |
-
system_prompt = self._generate_system_prompt()
|
183 |
-
|
184 |
-
# GPT generates the page instructions
|
185 |
## no need to store old system prompt while we get a new one
|
186 |
self.conversation = [entry for entry in self.conversation if entry["role"] != "system"]
|
|
|
|
|
187 |
self.conversation.append({"role": "system", "content": system_prompt})
|
188 |
-
|
189 |
gpt_instruction = self._get_openai_response(self.conversation)
|
190 |
self.conversation.append({"role": "assistant", "content": gpt_instruction})
|
191 |
return gpt_instruction
|
192 |
|
193 |
def start_conversation(self):
|
194 |
-
greeting = f'\
|
195 |
system_prompt = self._generate_system_prompt()
|
196 |
# GPT generates the page instructions
|
197 |
self.conversation.append({"role": "system", "content": system_prompt})
|
198 |
gpt_instruction = self._get_openai_response(self.conversation)
|
199 |
self.conversation.append({"role": "assistant", "content": gpt_instruction})
|
200 |
return greeting + gpt_instruction
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
from transformers import AutoTokenizer
|
4 |
from openai import RateLimitError
|
5 |
import time
|
6 |
+
from prompts import *
|
7 |
+
import re
|
8 |
|
9 |
|
10 |
class ChatbotSimulation:
|
11 |
+
def __init__(self, app_name, site_map, page_details, user_state, system_data, user_data, task, solution,
|
12 |
+
log_location, openai_api_key, agent='human',
|
13 |
max_steps=50, max_tokens=8192, buffer_tokens=500):
|
14 |
+
self.app_name = app_name
|
15 |
self.sitemap = site_map
|
16 |
self.page_details = page_details
|
17 |
self.user_state = user_state
|
18 |
self.user_state['current_page'] = 'Home' # Initialize current page
|
|
|
19 |
self.user_state['task_completed'] = 'False'
|
20 |
+
self.user_state['back'] = 'False'
|
21 |
+
self.system_data = system_data
|
22 |
+
self.user_data = user_data
|
23 |
self.task = task
|
24 |
+
self.solution = solution
|
25 |
+
|
26 |
self.log_location = log_location
|
27 |
self.agent = agent.lower()
|
28 |
if self.agent not in ['human', 'llm']:
|
|
|
34 |
self.prompt_count = 0
|
35 |
self.client = OpenAI(api_key=openai_api_key)
|
36 |
self.actions = []
|
37 |
+
self.tokenizer = AutoTokenizer.from_pretrained("gpt2", clean_up_tokenization_spaces=True)
|
38 |
+
|
39 |
+
# back button
|
40 |
+
self.page_history = ['Home']
|
41 |
|
42 |
def _get_page_uid(self, page_name):
|
43 |
"""Retrieve the UID of the given page from the sitemap."""
|
44 |
+
return self.sitemap.get(page_name, {}).get('uid')
|
45 |
|
46 |
def _get_page_details(self, page_name):
|
47 |
"""Retrieve the page details using its UID."""
|
|
|
50 |
|
51 |
def _generate_system_prompt(self):
|
52 |
"""Create a dynamic system prompt based on the current state."""
|
53 |
+
#current_page = self.user_state['current_page']
|
54 |
+
#last_page = self.user_state['last_page']
|
55 |
+
current_page = self.page_history[-1] if len(self.page_history) >= 1 else "Home"
|
56 |
+
last_page = self.page_history[-2] if len(self.page_history) > 1 else "Home"
|
57 |
page_info = self._get_page_details(current_page)
|
58 |
|
59 |
+
return get_system_prompt(app_name=self.app_name,
|
60 |
+
system_data=self.system_data,
|
61 |
+
task=self.task,
|
62 |
+
user_data=self.user_data,
|
63 |
+
current_page=current_page,
|
64 |
+
last_page=last_page,
|
65 |
+
actions=self.actions,
|
66 |
+
user_state=self.user_state,
|
67 |
+
page_info=page_info
|
68 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
def _get_openai_response(self, prompt):
|
71 |
"""Fetch response from OpenAI API."""
|
|
|
108 |
def one_conversation_round(self, user_input):
|
109 |
"""Conduct one round of conversation between the user and the assistant."""
|
110 |
# User provides input
|
111 |
+
valid_input = self._is_valid_input(user_input)
|
112 |
+
if valid_input[0]:
|
113 |
+
pass
|
114 |
+
else:
|
115 |
+
return f"\n{self.app_name}: Invalid input. {valid_input[1]}"
|
116 |
+
|
117 |
self.actions.append(user_input + f'on {self.user_state["current_page"]} page')
|
118 |
self.conversation.append({"role": "user", "content": user_input})
|
119 |
self.prompt_count += 1
|
120 |
|
121 |
# Update user state using GPT's response
|
122 |
+
current_page = self.page_history[-1] if len(self.page_history) >= 1 else "Home"
|
123 |
+
update_prompt = get_user_state_update_prompt(user_input=user_input,
|
124 |
+
current_page=current_page,
|
125 |
+
task=self.task,
|
126 |
+
solution=self.solution,
|
127 |
+
user_state=self.user_state,
|
128 |
+
sitemap=self.sitemap)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
self.conversation.append({"role": "assistant", "content": update_prompt})
|
131 |
+
updated_state = self._get_openai_response(self.conversation).split("UPDATED", 1)[1].strip()
|
132 |
self.conversation.pop(-1) ## update prompt don't have to stay in conversation history
|
133 |
|
134 |
# Parse and update the user state
|
135 |
updated_state = json_repair.loads(updated_state)
|
136 |
|
137 |
+
# format forcing of updated state
|
138 |
+
required_keys = {'current_page', 'task_completed', 'back'}
|
139 |
+
# Ensure `updated_state` is a dictionary
|
140 |
+
while not isinstance(updated_state, dict):
|
141 |
+
transform_prompt = f"""
|
142 |
+
Transform {updated_state} to a properly formatted JSON file.
|
143 |
Example Output Format:
|
144 |
{{
|
145 |
+
'current_page': 'Home',
|
146 |
+
'task_completed': False,
|
147 |
+
'back': False
|
148 |
}}
|
149 |
"""
|
150 |
updated_state = self._get_openai_response([{"role": "system", "content": transform_prompt}])
|
151 |
updated_state = json_repair.loads(updated_state)
|
152 |
+
# Manually add missing required keys
|
153 |
+
for key in required_keys:
|
154 |
+
if key not in updated_state:
|
155 |
+
if key == 'current_page':
|
156 |
+
updated_state[key] = self.page_history[-1] if len(self.page_history) >= 1 else "Home"
|
157 |
+
else:
|
158 |
+
updated_state[key] = False
|
159 |
|
160 |
try:
|
161 |
+
if str(updated_state['task_completed']).lower() == 'true':
|
162 |
return f"Task completed! You took {self.prompt_count} steps."
|
163 |
except:
|
164 |
updated_state['task_completed'] = 'False'
|
165 |
|
166 |
self.user_state = updated_state
|
167 |
+
if str(updated_state['back']).lower() == 'false':
|
168 |
+
self.page_history.append(updated_state['current_page'])
|
169 |
+
elif self.page_history:
|
170 |
+
self.page_history.pop()
|
171 |
|
|
|
|
|
|
|
|
|
172 |
## no need to store old system prompt while we get a new one
|
173 |
self.conversation = [entry for entry in self.conversation if entry["role"] != "system"]
|
174 |
+
system_prompt = self._generate_system_prompt()
|
175 |
+
# GPT generates the page instructions
|
176 |
self.conversation.append({"role": "system", "content": system_prompt})
|
|
|
177 |
gpt_instruction = self._get_openai_response(self.conversation)
|
178 |
self.conversation.append({"role": "assistant", "content": gpt_instruction})
|
179 |
return gpt_instruction
|
180 |
|
181 |
def start_conversation(self):
|
182 |
+
greeting = f'\nWelcome to {self.app_name} simulator! Your task is: {self.task}. \n'
|
183 |
system_prompt = self._generate_system_prompt()
|
184 |
# GPT generates the page instructions
|
185 |
self.conversation.append({"role": "system", "content": system_prompt})
|
186 |
gpt_instruction = self._get_openai_response(self.conversation)
|
187 |
self.conversation.append({"role": "assistant", "content": gpt_instruction})
|
188 |
return greeting + gpt_instruction
|
189 |
+
|
190 |
+
def _extract_buttons(self):
|
191 |
+
"""Extract buttons and their action types from the latest conversation if role is 'assistant'."""
|
192 |
+
# Get the last message
|
193 |
+
last_message = self.conversation[-1]
|
194 |
+
|
195 |
+
# Ensure the role of the last message is 'assistant'
|
196 |
+
if last_message.get("role") != "assistant":
|
197 |
+
return {}
|
198 |
+
|
199 |
+
# Extract the content of the last message
|
200 |
+
message_content = last_message.get("content", "")
|
201 |
+
|
202 |
+
# Make the split case-insensitive by searching for the phrase with re.IGNORECASE
|
203 |
+
options_split = re.split(r"you have the following options:", message_content, flags=re.IGNORECASE)
|
204 |
+
|
205 |
+
# If the split doesn't produce at least two parts, return an empty dictionary
|
206 |
+
if len(options_split) < 2:
|
207 |
+
return {}
|
208 |
+
|
209 |
+
# Extract button definitions from the second part of the split content
|
210 |
+
button_section = options_split[1]
|
211 |
+
pattern = r"\d+\.\s+(.*?):\s+([a-zA-Z_]+)"
|
212 |
+
buttons = re.findall(pattern, button_section)
|
213 |
+
|
214 |
+
# Construct the dictionary with button names as keys and action types as values
|
215 |
+
return {name.strip().lower(): action_type.strip().lower() for name, action_type in buttons}
|
216 |
+
|
217 |
+
def _is_valid_input(self, user_input):
|
218 |
+
"""Validate user input format."""
|
219 |
+
valid_buttons = self._extract_buttons()
|
220 |
+
|
221 |
+
# Validate input format
|
222 |
+
pattern = r"^(?P<action_type>\w+)\((?P<button_name>[^,]+)(?:,\s*(?P<query>.+))?\)$"
|
223 |
+
match = re.match(pattern, user_input)
|
224 |
+
|
225 |
+
if not match:
|
226 |
+
return [False, "Your input doesn't match the format: action_type(button name), OR if type, use type(button name, query)"]
|
227 |
+
|
228 |
+
# Extract parsed components
|
229 |
+
action_type = match.group("action_type").lower()
|
230 |
+
button_name = match.group("button_name").strip().lower()
|
231 |
+
query = match.group("query") # Optional query for `type`
|
232 |
+
|
233 |
+
# Validate button name and action type
|
234 |
+
if button_name not in valid_buttons:
|
235 |
+
return [False,
|
236 |
+
"Invalid Button name! Recall: Each button is in the format: `number. button name: action_type`"] # Button name must match exactly (case insensitive)
|
237 |
+
if action_type != valid_buttons[button_name]:
|
238 |
+
return [False,
|
239 |
+
"Invalid action type! Recall: Each button is in the format: `number. button name: action_type`"] # Action type must match the button's specified type
|
240 |
+
if action_type == "type" and query is None:
|
241 |
+
return [False,
|
242 |
+
"Missing Query for action type 'type'! Recall: use the format: `type(button name, query)`"] # `type` action requires a query
|
243 |
+
if action_type != "type" and query is not None:
|
244 |
+
return [False,
|
245 |
+
"Non-`type` action_type cannot take query!"] # Non-`type` actions must not have a query
|
246 |
+
return [True, 'Pass']
|
prompts.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
def get_system_prompt(app_name, system_data, task, user_data, current_page, last_page, actions, user_state, page_info):
|
3 |
+
system_prompt = f"""
|
4 |
+
You are a text-based {app_name}-like app simulator game.
|
5 |
+
The {app_name} app contains the following system data: {system_data}.
|
6 |
+
You are interacting with a user whose task is: {task}.
|
7 |
+
User's data: {user_data}.
|
8 |
+
The user's last page was {last_page}, and they have taken the following actions: {actions}.
|
9 |
+
After performing the most recent action, the user is now on the {current_page} page.
|
10 |
+
Current user state: {user_state}.
|
11 |
+
|
12 |
+
Details and buttons on the current page:
|
13 |
+
{page_info}
|
14 |
+
|
15 |
+
Each button on the page has a `name` and an `action_type`.
|
16 |
+
|
17 |
+
### Instruction Format:
|
18 |
+
<If `actions` is non-empty: "You have successfully performed actions[-1]">
|
19 |
+
<If current_page == log in & user_state['logged_in'] == False: You need to log in first. Your credentials are: Username: user_data['credentials']['user_id'] Password: user_data['credentials']['password']>
|
20 |
+
<If current_page == log in & user_state['logged_in'] == True: You have successfully logged in as user_data['credentials']['user_id']>
|
21 |
+
You are currently on the **{current_page}** page. You have the following options:
|
22 |
+
1. option 1: action type
|
23 |
+
2. option 2: action type
|
24 |
+
3. option 3: action type
|
25 |
+
4. option 4: action type
|
26 |
+
5. Back to last page: click
|
27 |
+
6. Back to Home page: click
|
28 |
+
7. <If user_state['logged_in'] == False, display Log In: click to take user to log in page>
|
29 |
+
8. Do nothing: noop
|
30 |
+
|
31 |
+
Each button is in the format: `number. button name: action_type`.
|
32 |
+
Please provide your choice in the format: `action_type(button name)`.
|
33 |
+
If you have a query, use the format: `type(button name, query)`.
|
34 |
+
Please provide only one choice per turn.
|
35 |
+
|
36 |
+
### **Rules**:
|
37 |
+
1. Display all available options from the buttons on the page.
|
38 |
+
2. Remain robotic and emotionless. Avoid offering any advice or opinions to the user.
|
39 |
+
3. Ensure there is always a button to navigate back to the **{last_page}** and a button to go to the **Home page**.
|
40 |
+
4. Ensure there is always a button that allow user to do nothing.
|
41 |
+
5. Ensure the action type is a single word, such as ‘type’ or ‘click.’ If the action type is not a single word, convert it to a single word. For example, type(SUMMER, apply) should be transformed to type.
|
42 |
+
6. Group similar action types under ‘type’ and display them as ‘type’ (e.g., display ‘input’ or ‘search’ as ‘type’).
|
43 |
+
"""
|
44 |
+
return system_prompt
|
45 |
+
|
46 |
+
|
47 |
+
def get_user_state_update_prompt(user_input, current_page, task, solution, user_state, sitemap):
|
48 |
+
structure = """
|
49 |
+
{
|
50 |
+
'current_page': 'Home',
|
51 |
+
'task_completed': False,
|
52 |
+
'back': False
|
53 |
+
}
|
54 |
+
"""
|
55 |
+
update_prompt = f"""
|
56 |
+
You will now update the user state.
|
57 |
+
The user takes the action '{user_input}' on the {current_page} page. Determine which page the user will move to next.
|
58 |
+
The next page must from the sitemap: {sitemap}
|
59 |
+
Recall the user's task: {task}. Solution to the user's task: {solution}.
|
60 |
+
Update the `user_state` dictionary based on the user's last action:
|
61 |
+
Current user_state: {user_state}.
|
62 |
+
|
63 |
+
### Instructions:
|
64 |
+
1. If the 'current_page' has changed, update it to a valid page from the sitemap.
|
65 |
+
2. If the task is completed, update 'task_completed' to `True`. Otherwise, leave it as `False`.
|
66 |
+
3. If no updates are needed, return the `user_state` exactly as provided, without any changes.
|
67 |
+
|
68 |
+
### Important Notes:
|
69 |
+
- Ensure 'current_page' and 'task_completed' are always present as keys in the returned dictionary.
|
70 |
+
- If the user go back a page from current page, set 'back' to `True`. Otherwise, it should remain `False`.
|
71 |
+
- Return only the updated dictionary, without additional text, explanations, or wrapping.
|
72 |
+
|
73 |
+
### Output Format:
|
74 |
+
1. Start with "REASON": Explain which fields in `user_state` need to be updated and why.
|
75 |
+
2. End with "UPDATED": Provide only the updated `user_state` dictionary. Follow the input user_state schema structure exactly without modifying its organization. Do not include explanations or any additional text after updated user state.
|
76 |
+
"""
|
77 |
+
return update_prompt
|
78 |
+
|
79 |
+
|
80 |
+
def get_agent_prompt(app_name, task, conversation):
|
81 |
+
agent_prompt = f"""
|
82 |
+
Imagine you are an agent navigate through the {app_name} environment.
|
83 |
+
Your overarching task is: {task}. You may have done some part of the task, or none at all.
|
84 |
+
You will have access to all of your previous actions in the environment, as well as the last message from the assistant giving the current state of the environment.
|
85 |
+
The last message from the assistant was: {conversation[-1]['content']}
|
86 |
+
Respond first with a brief "Plan" which suggests what steps you are going to take to accomplish the task, and what your immediate.
|
87 |
+
Then generate an "Action" which is the immediate next step you can take.
|
88 |
+
"""
|
89 |
+
return agent_prompt
|