browsergym-workarena 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browsergym/workarena/__init__.py +13 -1
- browsergym/workarena/api/category.py +74 -0
- browsergym/workarena/api/change_request.py +87 -0
- browsergym/workarena/api/computer_asset.py +90 -0
- browsergym/workarena/api/cost_center.py +19 -0
- browsergym/workarena/api/expense_line.py +89 -0
- browsergym/workarena/api/incident.py +45 -0
- browsergym/workarena/api/knowledge.py +29 -0
- browsergym/workarena/api/problem.py +90 -0
- browsergym/workarena/api/report.py +183 -0
- browsergym/workarena/api/requested_items.py +63 -0
- browsergym/workarena/api/user.py +11 -8
- browsergym/workarena/api/utils.py +47 -3
- browsergym/workarena/config.py +21 -1
- browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
- browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
- browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
- browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
- browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
- browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
- browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
- browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
- browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
- browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
- browsergym/workarena/data_files/task_configs/all_menu.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
- browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +1 -1
- browsergym/workarena/data_files/task_configs/impersonation_users.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
- browsergym/workarena/human_eval/console.js +176 -0
- browsergym/workarena/human_eval/tool.py +366 -0
- browsergym/workarena/install.py +81 -20
- browsergym/workarena/tasks/base.py +55 -20
- browsergym/workarena/tasks/comp_building_block.py +4 -0
- browsergym/workarena/tasks/compositional/__init__.py +76 -0
- browsergym/workarena/tasks/compositional/base.py +364 -0
- browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
- browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
- browsergym/workarena/tasks/compositional/delete_record.py +341 -0
- browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
- browsergym/workarena/tasks/compositional/expense_management.py +598 -0
- browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
- browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
- browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
- browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
- browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
- browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
- browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
- browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
- browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
- browsergym/workarena/tasks/compositional/update_task.py +145 -0
- browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
- browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
- browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
- browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
- browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
- browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
- browsergym/workarena/tasks/dashboard.py +188 -8
- browsergym/workarena/tasks/form.py +1024 -232
- browsergym/workarena/tasks/knowledge.py +216 -25
- browsergym/workarena/tasks/list.py +519 -102
- browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
- browsergym/workarena/tasks/navigation.py +55 -13
- browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
- browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
- browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
- browsergym/workarena/tasks/scripts/validate.py +8 -2
- browsergym/workarena/tasks/send_chat_message.py +90 -0
- browsergym/workarena/tasks/service_catalog.py +94 -26
- browsergym/workarena/tasks/utils/form.py +1 -4
- browsergym/workarena/tasks/utils/private_tasks.py +63 -0
- browsergym/workarena/tasks/utils/utils.py +13 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +19 -18
- browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
- browsergym_workarena-0.2.1.dist-info/RECORD +0 -85
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
- {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import faker
|
|
2
|
+
|
|
3
|
+
faker = faker.Faker()
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from playwright.sync_api import Page
|
|
7
|
+
from typing import List, Tuple
|
|
8
|
+
|
|
9
|
+
from .base import AbstractServiceNowTask
|
|
10
|
+
|
|
11
|
+
from ..utils.utils import check_url_suffix_match
|
|
12
|
+
|
|
13
|
+
from ...api.utils import db_delete_from_table, table_api_call
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeleteRecordTask(AbstractServiceNowTask):
|
|
17
|
+
"""
|
|
18
|
+
Delete a record from a list.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
-----------
|
|
22
|
+
instance: SNowInstance
|
|
23
|
+
The instance to use.
|
|
24
|
+
start_rel_url: str
|
|
25
|
+
The relative URL of the list containing the record to delete.
|
|
26
|
+
list_name: str
|
|
27
|
+
The displayed name of the list containing the record to delete.
|
|
28
|
+
fixed_config: dict
|
|
29
|
+
Configuration to use for the task. If provided, the task will use the provided configuration instead of
|
|
30
|
+
selecting a random one. See browsergym/workarena/data_files/task_configs/filter_change_request_list_task.json
|
|
31
|
+
for an example of a configuration file.
|
|
32
|
+
all_configs: list[dict]
|
|
33
|
+
A list of all possible configurations to use for the task.
|
|
34
|
+
record_sys_id: str
|
|
35
|
+
The sys_id of the record to delete. If not provided, a record will be created during the setup.
|
|
36
|
+
record_number: str
|
|
37
|
+
The number of the record to delete; used in the cheat. If not provided, the cheat will select the last one.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
seed: int = None,
|
|
43
|
+
instance=None,
|
|
44
|
+
start_rel_url: str = "",
|
|
45
|
+
list_name: str = "",
|
|
46
|
+
fixed_config: dict = None,
|
|
47
|
+
all_configs: list[dict] = None,
|
|
48
|
+
record_sys_id: str = None,
|
|
49
|
+
record_number: str = None,
|
|
50
|
+
**kwargs,
|
|
51
|
+
) -> None:
|
|
52
|
+
super().__init__(seed=seed, instance=instance, start_rel_url=start_rel_url)
|
|
53
|
+
self.list_name = list_name
|
|
54
|
+
self.table_name = start_rel_url.split("/")[-1].split("_list.do")[0]
|
|
55
|
+
self.fixed_config = fixed_config
|
|
56
|
+
self.config = None
|
|
57
|
+
self.pretty_printed_field_name = None
|
|
58
|
+
self.field_name = None
|
|
59
|
+
self.field_value = None
|
|
60
|
+
self.other_fields = None
|
|
61
|
+
self.all_configs = all_configs
|
|
62
|
+
# If the record_sys_id is not provided, it will be created during the setup
|
|
63
|
+
self.record_sys_id = record_sys_id
|
|
64
|
+
self.record_number = record_number
|
|
65
|
+
self.__dict__.update(kwargs)
|
|
66
|
+
|
|
67
|
+
def setup_goal(self, page: Page) -> tuple[str, dict]:
|
|
68
|
+
self.config = (
|
|
69
|
+
self.fixed_config if self.fixed_config else self.random.choice(self.all_configs)
|
|
70
|
+
)
|
|
71
|
+
self.field_name = self.config.get("field_name")
|
|
72
|
+
self.pretty_printed_field_name = self.config.get("pretty_printed_field_name")
|
|
73
|
+
self.field_value = self.config.get("field_value")
|
|
74
|
+
self.other_fields = self.config.get("other_fields")
|
|
75
|
+
if self.record_sys_id is None:
|
|
76
|
+
# First, check if the record already exists
|
|
77
|
+
record = table_api_call(
|
|
78
|
+
instance=self.instance,
|
|
79
|
+
table=self.table_name,
|
|
80
|
+
params={
|
|
81
|
+
"sysparm_query": f"{self.field_name}={self.field_value}",
|
|
82
|
+
"sysparm_fields": "sys_id",
|
|
83
|
+
},
|
|
84
|
+
)["result"]
|
|
85
|
+
if len(record) > 0:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Record already with {self.field_name} = {self.field_value} exists. Please delete it before proceeding."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.record_sys_id = table_api_call(
|
|
91
|
+
instance=self.instance,
|
|
92
|
+
table=self.table_name,
|
|
93
|
+
data=json.dumps(
|
|
94
|
+
{
|
|
95
|
+
self.field_name: self.field_value,
|
|
96
|
+
**self.other_fields,
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
method="POST",
|
|
100
|
+
)["result"]["sys_id"]
|
|
101
|
+
|
|
102
|
+
goal = self.get_pretty_printed_description()
|
|
103
|
+
|
|
104
|
+
return goal, {}
|
|
105
|
+
|
|
106
|
+
def get_init_scripts(self) -> List[str]:
|
|
107
|
+
return super().get_init_scripts() + ["registerGsftMainLoaded();"]
|
|
108
|
+
|
|
109
|
+
def get_pretty_printed_description(self) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Get the task info for this task when used in a private task; Used in L3 compositional tasks.
|
|
112
|
+
called by subclasses
|
|
113
|
+
"""
|
|
114
|
+
task_info = f"- Delete the record with {self.pretty_printed_field_name}={self.field_value} from the {self.list_name} list."
|
|
115
|
+
|
|
116
|
+
return task_info
|
|
117
|
+
|
|
118
|
+
def cheat(self, page: Page, chat_messages: list[str]) -> None:
|
|
119
|
+
super().cheat(page, chat_messages)
|
|
120
|
+
frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
|
|
121
|
+
|
|
122
|
+
# If the record number is provided, click on the record with that number...
|
|
123
|
+
if self.record_number is not None:
|
|
124
|
+
frame.locator(f"[aria-label='Preview record: {self.record_number}']").click()
|
|
125
|
+
page.wait_for_timeout(500)
|
|
126
|
+
frame.get_by_text("Open Record").click()
|
|
127
|
+
# ....Otherwise, otherwise filter the list and click on the record
|
|
128
|
+
else:
|
|
129
|
+
# Search for the record
|
|
130
|
+
frame.get_by_label(
|
|
131
|
+
f"Search a specific field of the {self.list_name} list"
|
|
132
|
+
).select_option(f"{self.field_name}")
|
|
133
|
+
search_input = frame.locator('input[aria-label="Search"]')
|
|
134
|
+
search_input.click()
|
|
135
|
+
search_input.fill(self.field_value)
|
|
136
|
+
search_input.press("Enter")
|
|
137
|
+
page.wait_for_function(
|
|
138
|
+
"typeof window.gsft_main !== 'undefined' && window.gsft_main.WORKARENA_LOAD_COMPLETE"
|
|
139
|
+
)
|
|
140
|
+
# Click on the record to open it
|
|
141
|
+
# The first 2 displays of the record are in the search bar; the 3rd and last will be the link to open it
|
|
142
|
+
frame.get_by_label(self.field_value).last.click()
|
|
143
|
+
|
|
144
|
+
page.wait_for_function(
|
|
145
|
+
"typeof window.gsft_main !== 'undefined' && window.gsft_main.WORKARENA_LOAD_COMPLETE"
|
|
146
|
+
)
|
|
147
|
+
frame = page.wait_for_selector('iframe[name="gsft_main"]').content_frame()
|
|
148
|
+
# Click on delete, then confirm delete in the popup
|
|
149
|
+
frame.get_by_text("delete").first.click()
|
|
150
|
+
frame.wait_for_selector('header[aria-label="Confirmation"]')
|
|
151
|
+
page.keyboard.press("Enter")
|
|
152
|
+
# Wait for record to be updated in the DB
|
|
153
|
+
record_deleted = False
|
|
154
|
+
while not record_deleted:
|
|
155
|
+
record = table_api_call(
|
|
156
|
+
instance=self.instance,
|
|
157
|
+
table=self.table_name,
|
|
158
|
+
params={
|
|
159
|
+
"sysparm_query": f"{self.field_name}={self.field_value}",
|
|
160
|
+
"sysparm_fields": "sys_id",
|
|
161
|
+
},
|
|
162
|
+
)["result"]
|
|
163
|
+
record_deleted = len(record) == 0
|
|
164
|
+
page.wait_for_timeout(3000)
|
|
165
|
+
|
|
166
|
+
def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
|
|
167
|
+
"""
|
|
168
|
+
Validate the solution
|
|
169
|
+
"""
|
|
170
|
+
record = table_api_call(
|
|
171
|
+
instance=self.instance,
|
|
172
|
+
table=self.table_name,
|
|
173
|
+
params={"sysparm_query": f"{self.field_name}={self.field_value}"},
|
|
174
|
+
)["result"]
|
|
175
|
+
if len(record) > 0:
|
|
176
|
+
return 0, False, "", {"message": "Record was not deleted."}
|
|
177
|
+
|
|
178
|
+
return 1, True, "Nice work, thank you!", {"message": "Record was deleted successfully."}
|
|
179
|
+
|
|
180
|
+
def teardown(self) -> None:
|
|
181
|
+
super().teardown()
|
|
182
|
+
result = table_api_call(
|
|
183
|
+
instance=self.instance,
|
|
184
|
+
table=self.table_name,
|
|
185
|
+
params={
|
|
186
|
+
"sysparm_query": f"{self.field_name}={self.field_value}",
|
|
187
|
+
"sysparm_fields": "sys_id",
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
if len(result["result"]) > 0:
|
|
191
|
+
db_delete_from_table(
|
|
192
|
+
instance=self.instance,
|
|
193
|
+
table=self.table_name,
|
|
194
|
+
sys_id=self.record_sys_id,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class DeleteUserTask(DeleteRecordTask):
|
|
199
|
+
def __init__(self, instance=None, fixed_config=None, record_sys_id=None, **kwargs) -> None:
|
|
200
|
+
super().__init__(
|
|
201
|
+
instance=instance,
|
|
202
|
+
start_rel_url="/now/nav/ui/classic/params/target/sys_user_list.do",
|
|
203
|
+
list_name="Users",
|
|
204
|
+
fixed_config=fixed_config,
|
|
205
|
+
record_sys_id=record_sys_id,
|
|
206
|
+
**kwargs,
|
|
207
|
+
)
|
|
208
|
+
if fixed_config is None:
|
|
209
|
+
first_name = faker.first_name()
|
|
210
|
+
last_name = faker.last_name()
|
|
211
|
+
email = first_name.lower() + "." + last_name.lower() + "@workarena.com"
|
|
212
|
+
self.fixed_config = {
|
|
213
|
+
"field_name": "user_name",
|
|
214
|
+
"pretty_printed_field_name": "User ID",
|
|
215
|
+
"field_value": first_name + " " + last_name,
|
|
216
|
+
"other_fields": {"email": email},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class DeleteExpenseLineExpenseManagementTask(DeleteRecordTask):
|
|
221
|
+
"""
|
|
222
|
+
Delete one row from the expense lines list
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
--------
|
|
226
|
+
goal_type (str):
|
|
227
|
+
The type of goal to generate. Choice of "base", "date", "amount", "any"
|
|
228
|
+
level (int):
|
|
229
|
+
The level of the task
|
|
230
|
+
skip_description (bool):
|
|
231
|
+
Whether to skip the description of the task
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
instance=None,
|
|
238
|
+
fixed_config=None,
|
|
239
|
+
record_sys_id=None,
|
|
240
|
+
goal_type="base",
|
|
241
|
+
level=2,
|
|
242
|
+
skip_description=False,
|
|
243
|
+
**kwargs,
|
|
244
|
+
) -> None:
|
|
245
|
+
super().__init__(
|
|
246
|
+
instance=instance,
|
|
247
|
+
start_rel_url="/now/nav/ui/classic/params/target/fm_expense_line_list.do",
|
|
248
|
+
list_name="Expense Lines",
|
|
249
|
+
fixed_config=fixed_config,
|
|
250
|
+
record_sys_id=record_sys_id,
|
|
251
|
+
**kwargs,
|
|
252
|
+
)
|
|
253
|
+
self.goal_type = goal_type
|
|
254
|
+
self.level = level
|
|
255
|
+
self.skip_description = skip_description
|
|
256
|
+
|
|
257
|
+
def get_pretty_printed_description(self) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Get the task info for this task when used in a private task; Used in compositional tasks.
|
|
260
|
+
called by subclasses
|
|
261
|
+
"""
|
|
262
|
+
task_info = f"Delete expense lines with duplicated short descriptions"
|
|
263
|
+
if self.skip_description:
|
|
264
|
+
task_info = ""
|
|
265
|
+
elif self.level == 3:
|
|
266
|
+
task_info += f" according to the protocol."
|
|
267
|
+
elif self.goal_type == "base":
|
|
268
|
+
task_info += f" where the duplicated expense lines are not associated with tasks."
|
|
269
|
+
elif self.goal_type == "date":
|
|
270
|
+
task_info += f", keeping only the one that has the oldest date."
|
|
271
|
+
elif self.goal_type == "amount":
|
|
272
|
+
task_info += f", keeping only the most expensive duplicate."
|
|
273
|
+
elif self.goal_type == "any":
|
|
274
|
+
task_info += f", keeping only one."
|
|
275
|
+
|
|
276
|
+
return task_info
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class DeleteExpenseLineKnapsack(DeleteRecordTask):
|
|
280
|
+
"""
|
|
281
|
+
Delete one row from the expense lines list
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
--------
|
|
285
|
+
goal_type (str):
|
|
286
|
+
The type of goal to generate. Choice of "base", "date", "amount", "any"
|
|
287
|
+
answer_format (str):
|
|
288
|
+
The type of answer to generate. Choice of total_return_only, total_return_and_investments, investments_only, cleanup, cleanup_and_return
|
|
289
|
+
level (int):
|
|
290
|
+
The level of the task
|
|
291
|
+
skip_description (bool):
|
|
292
|
+
Whether to skip the description of the task
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def __init__(
|
|
297
|
+
self,
|
|
298
|
+
instance=None,
|
|
299
|
+
fixed_config=None,
|
|
300
|
+
record_sys_id=None,
|
|
301
|
+
goal_type="base",
|
|
302
|
+
level=2,
|
|
303
|
+
answer_format=None,
|
|
304
|
+
skip_description=False,
|
|
305
|
+
**kwargs,
|
|
306
|
+
) -> None:
|
|
307
|
+
super().__init__(
|
|
308
|
+
instance=instance,
|
|
309
|
+
start_rel_url="/now/nav/ui/classic/params/target/fm_expense_line_list.do",
|
|
310
|
+
list_name="Expense Lines",
|
|
311
|
+
fixed_config=fixed_config,
|
|
312
|
+
record_sys_id=record_sys_id,
|
|
313
|
+
**kwargs,
|
|
314
|
+
)
|
|
315
|
+
self.goal_type = goal_type
|
|
316
|
+
self.level = level
|
|
317
|
+
self.answer_format = answer_format
|
|
318
|
+
self.skip_description = skip_description
|
|
319
|
+
|
|
320
|
+
def get_pretty_printed_description(self) -> str:
|
|
321
|
+
if self.skip_description:
|
|
322
|
+
return ""
|
|
323
|
+
if self.level == 3:
|
|
324
|
+
task_info = "Allocate the budget to maximize revenue."
|
|
325
|
+
elif self.level == 2:
|
|
326
|
+
task_info = f"Allocate the budget to maximize revenue. This involves going over expense lines and identifying the ones maximizing revenue while fitting in the allowed budget of {self.budget}. The returns are written in their short description."
|
|
327
|
+
if self.answer_format == "total_return_only":
|
|
328
|
+
task_info += " Provide only the total return of the investments in the chat."
|
|
329
|
+
if self.answer_format == "total_return_and_investments":
|
|
330
|
+
task_info += " Provide the total return of the investments as well as the number of the investments in the chat."
|
|
331
|
+
if self.answer_format == "investments_only":
|
|
332
|
+
task_info += " Provide only the numbers of the investments in the chat."
|
|
333
|
+
if self.answer_format == "cleanup":
|
|
334
|
+
task_info += " Delete the investments that will not be kept so that only the selected investments remain."
|
|
335
|
+
if self.answer_format == "cleanup_and_return":
|
|
336
|
+
task_info += " Delete the investments that will not be kept so that only the selected investments remain as well as returning their total value in the chat."
|
|
337
|
+
|
|
338
|
+
return task_info
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
__TASKS__ = [DeleteUserTask]
|