Spaces:
Runtime error
Runtime error
Commit
·
670dd87
1
Parent(s):
642b69c
Add new agents and tools for Devid and BrowsingAgent; remove obsolete ResearchAndReportAgent
Browse files- app/agents/BrowsingAgent/BrowsingAgent.py +188 -0
- app/agents/BrowsingAgent/__init__.py +1 -0
- app/agents/BrowsingAgent/instructions.md +21 -0
- app/agents/BrowsingAgent/requirements.txt +3 -0
- app/agents/BrowsingAgent/tools/ClickElement.py +65 -0
- app/agents/BrowsingAgent/tools/ExportFile.py +53 -0
- app/agents/BrowsingAgent/tools/GoBack.py +22 -0
- app/agents/BrowsingAgent/tools/ReadURL.py +54 -0
- app/agents/BrowsingAgent/tools/Scroll.py +58 -0
- app/agents/BrowsingAgent/tools/SelectDropdown.py +63 -0
- app/agents/BrowsingAgent/tools/SendKeys.py +76 -0
- app/agents/BrowsingAgent/tools/SolveCaptcha.py +272 -0
- app/agents/BrowsingAgent/tools/WebPageSummarizer.py +48 -0
- app/agents/BrowsingAgent/tools/__init__.py +9 -0
- app/agents/BrowsingAgent/tools/util/__init__.py +3 -0
- app/agents/BrowsingAgent/tools/util/get_b64_screenshot.py +7 -0
- app/agents/BrowsingAgent/tools/util/highlights.py +141 -0
- app/agents/BrowsingAgent/tools/util/selenium.py +169 -0
- app/agents/Devid/Devid.py +47 -0
- app/agents/Devid/__init__.py +1 -0
- app/agents/Devid/instructions.md +21 -0
- app/agents/Devid/tools/ChangeFile.py +107 -0
- app/agents/Devid/tools/CheckCurrentDir.py +23 -0
- app/agents/Devid/tools/CommandExecutor.py +46 -0
- app/agents/Devid/tools/DirectoryNavigator.py +56 -0
- app/agents/Devid/tools/FileMover.py +39 -0
- app/agents/Devid/tools/FileReader.py +32 -0
- app/agents/Devid/tools/FileWriter.py +246 -0
- app/agents/Devid/tools/ListDir.py +84 -0
- app/agents/Devid/tools/__init__.py +0 -0
- app/agents/Devid/tools/util/__init__.py +1 -0
- app/agents/Devid/tools/util/format_file_deps.py +68 -0
- app/agents/NotionProjectAgent/NotionProjectAgent.py +0 -48
- app/agents/NotionProjectAgent/__init__.py +0 -1
- app/agents/NotionProjectAgent/instructions.md +0 -39
- app/agents/ResearchAndReportAgent/ResearchAndReportAgent.py +0 -20
- app/agents/ResearchAndReportAgent/__init__.py +0 -1
- app/agents/ResearchAndReportAgent/instructions.md +0 -2
- app/agents/ResearchAndReportAgent/tools/ExampleTool.py +0 -30
- app/agents/TechnicalProjectManager/TechnicalProjectManager.py +14 -2
- app/agents/TechnicalProjectManager/instructions.md +44 -3
- app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/CreateTask.py +0 -0
- app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/DeleteTask.py +0 -0
- app/agents/TechnicalProjectManager/tools/ExampleTool.py +0 -30
- app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/GetTask.py +0 -0
- app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/GetTasks.py +0 -0
- app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/UpdateTask.py +0 -0
app/agents/BrowsingAgent/BrowsingAgent.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
import re
|
3 |
+
|
4 |
+
from typing_extensions import override
|
5 |
+
|
6 |
+
from agency_swarm.agents import Agent
|
7 |
+
from agency_swarm.tools.oai import FileSearch
|
8 |
+
|
9 |
+
|
10 |
+
class BrowsingAgent(Agent):
|
11 |
+
SCREENSHOT_FILE_NAME = "screenshot.jpg"
|
12 |
+
|
13 |
+
def __init__(self, selenium_config=None, **kwargs):
|
14 |
+
from .tools.util.selenium import set_selenium_config
|
15 |
+
|
16 |
+
super().__init__(
|
17 |
+
name="BrowsingAgent",
|
18 |
+
description="This agent is designed to navigate and search web effectively.",
|
19 |
+
instructions="./instructions.md",
|
20 |
+
files_folder="./files",
|
21 |
+
schemas_folder="./schemas",
|
22 |
+
tools=[],
|
23 |
+
tools_folder="./tools",
|
24 |
+
temperature=0,
|
25 |
+
max_prompt_tokens=16000,
|
26 |
+
model="gpt-4o",
|
27 |
+
validation_attempts=25,
|
28 |
+
**kwargs,
|
29 |
+
)
|
30 |
+
if selenium_config is not None:
|
31 |
+
set_selenium_config(selenium_config)
|
32 |
+
|
33 |
+
self.prev_message = ""
|
34 |
+
|
35 |
+
@override
|
36 |
+
def response_validator(self, message):
|
37 |
+
from selenium.webdriver.common.by import By
|
38 |
+
from selenium.webdriver.support.select import Select
|
39 |
+
|
40 |
+
from .tools.util import (
|
41 |
+
highlight_elements_with_labels,
|
42 |
+
remove_highlight_and_labels,
|
43 |
+
)
|
44 |
+
from .tools.util.selenium import get_web_driver, set_web_driver
|
45 |
+
|
46 |
+
# Filter out everything in square brackets
|
47 |
+
filtered_message = re.sub(r"\[.*?\]", "", message).strip()
|
48 |
+
|
49 |
+
if filtered_message and self.prev_message == filtered_message:
|
50 |
+
raise ValueError(
|
51 |
+
"Do not repeat yourself. If you are stuck, try a different approach or search in google for the page you are looking for directly."
|
52 |
+
)
|
53 |
+
|
54 |
+
self.prev_message = filtered_message
|
55 |
+
|
56 |
+
if "[send screenshot]" in message.lower():
|
57 |
+
wd = get_web_driver()
|
58 |
+
remove_highlight_and_labels(wd)
|
59 |
+
self.take_screenshot()
|
60 |
+
response_text = "Here is the screenshot of the current web page:"
|
61 |
+
|
62 |
+
elif "[highlight clickable elements]" in message.lower():
|
63 |
+
wd = get_web_driver()
|
64 |
+
highlight_elements_with_labels(
|
65 |
+
wd,
|
66 |
+
'a, button, div[onclick], div[role="button"], div[tabindex], '
|
67 |
+
'span[onclick], span[role="button"], span[tabindex]',
|
68 |
+
)
|
69 |
+
self._shared_state.set(
|
70 |
+
"elements_highlighted",
|
71 |
+
'a, button, div[onclick], div[role="button"], div[tabindex], '
|
72 |
+
'span[onclick], span[role="button"], span[tabindex]',
|
73 |
+
)
|
74 |
+
|
75 |
+
self.take_screenshot()
|
76 |
+
|
77 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
78 |
+
|
79 |
+
all_element_texts = [element.text for element in all_elements]
|
80 |
+
|
81 |
+
element_texts_json = {}
|
82 |
+
for i, element_text in enumerate(all_element_texts):
|
83 |
+
element_texts_json[str(i + 1)] = self.remove_unicode(element_text)
|
84 |
+
|
85 |
+
element_texts_json = {k: v for k, v in element_texts_json.items() if v}
|
86 |
+
|
87 |
+
element_texts_formatted = ", ".join(
|
88 |
+
[f"{k}: {v}" for k, v in element_texts_json.items()]
|
89 |
+
)
|
90 |
+
|
91 |
+
response_text = (
|
92 |
+
"Here is the screenshot of the current web page with highlighted clickable elements. \n\n"
|
93 |
+
"Texts of the elements are: " + element_texts_formatted + ".\n\n"
|
94 |
+
"Elements without text are not shown, but are available on screenshot. \n"
|
95 |
+
"Please make sure to analyze the screenshot to find the clickable element you need to click on."
|
96 |
+
)
|
97 |
+
|
98 |
+
elif "[highlight text fields]" in message.lower():
|
99 |
+
wd = get_web_driver()
|
100 |
+
highlight_elements_with_labels(wd, "input, textarea")
|
101 |
+
self._shared_state.set("elements_highlighted", "input, textarea")
|
102 |
+
|
103 |
+
self.take_screenshot()
|
104 |
+
|
105 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
106 |
+
|
107 |
+
all_element_texts = [element.text for element in all_elements]
|
108 |
+
|
109 |
+
element_texts_json = {}
|
110 |
+
for i, element_text in enumerate(all_element_texts):
|
111 |
+
element_texts_json[str(i + 1)] = self.remove_unicode(element_text)
|
112 |
+
|
113 |
+
element_texts_formatted = ", ".join(
|
114 |
+
[f"{k}: {v}" for k, v in element_texts_json.items()]
|
115 |
+
)
|
116 |
+
|
117 |
+
response_text = (
|
118 |
+
"Here is the screenshot of the current web page with highlighted text fields: \n"
|
119 |
+
"Texts of the elements are: " + element_texts_formatted + ".\n"
|
120 |
+
"Please make sure to analyze the screenshot to find the text field you need to fill."
|
121 |
+
)
|
122 |
+
|
123 |
+
elif "[highlight dropdowns]" in message.lower():
|
124 |
+
wd = get_web_driver()
|
125 |
+
highlight_elements_with_labels(wd, "select")
|
126 |
+
self._shared_state.set("elements_highlighted", "select")
|
127 |
+
|
128 |
+
self.take_screenshot()
|
129 |
+
|
130 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
131 |
+
|
132 |
+
all_selector_values = {}
|
133 |
+
|
134 |
+
i = 0
|
135 |
+
for element in all_elements:
|
136 |
+
select = Select(element)
|
137 |
+
options = select.options
|
138 |
+
selector_values = {}
|
139 |
+
for j, option in enumerate(options):
|
140 |
+
selector_values[str(j)] = option.text
|
141 |
+
if j > 10:
|
142 |
+
break
|
143 |
+
all_selector_values[str(i + 1)] = selector_values
|
144 |
+
|
145 |
+
all_selector_values = {k: v for k, v in all_selector_values.items() if v}
|
146 |
+
all_selector_values_formatted = ", ".join(
|
147 |
+
[f"{k}: {v}" for k, v in all_selector_values.items()]
|
148 |
+
)
|
149 |
+
|
150 |
+
response_text = (
|
151 |
+
"Here is the screenshot with highlighted dropdowns. \n"
|
152 |
+
"Selector values are: " + all_selector_values_formatted + ".\n"
|
153 |
+
"Please make sure to analyze the screenshot to find the dropdown you need to select."
|
154 |
+
)
|
155 |
+
|
156 |
+
else:
|
157 |
+
return message
|
158 |
+
|
159 |
+
set_web_driver(wd)
|
160 |
+
content = self.create_response_content(response_text)
|
161 |
+
raise ValueError(content)
|
162 |
+
|
163 |
+
def take_screenshot(self):
|
164 |
+
from .tools.util import get_b64_screenshot
|
165 |
+
from .tools.util.selenium import get_web_driver
|
166 |
+
|
167 |
+
wd = get_web_driver()
|
168 |
+
screenshot = get_b64_screenshot(wd)
|
169 |
+
screenshot_data = base64.b64decode(screenshot)
|
170 |
+
with open(self.SCREENSHOT_FILE_NAME, "wb") as screenshot_file:
|
171 |
+
screenshot_file.write(screenshot_data)
|
172 |
+
|
173 |
+
def create_response_content(self, response_text):
|
174 |
+
with open(self.SCREENSHOT_FILE_NAME, "rb") as file:
|
175 |
+
file_id = self.client.files.create(
|
176 |
+
file=file,
|
177 |
+
purpose="vision",
|
178 |
+
).id
|
179 |
+
|
180 |
+
content = [
|
181 |
+
{"type": "text", "text": response_text},
|
182 |
+
{"type": "image_file", "image_file": {"file_id": file_id}},
|
183 |
+
]
|
184 |
+
return content
|
185 |
+
|
186 |
+
# Function to check for Unicode escape sequences
|
187 |
+
def remove_unicode(self, data):
|
188 |
+
return re.sub(r"[^\x00-\x7F]+", "", data)
|
app/agents/BrowsingAgent/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .BrowsingAgent import BrowsingAgent
|
app/agents/BrowsingAgent/instructions.md
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Browsing Agent Instructions
|
2 |
+
|
3 |
+
As an advanced browsing agent, you are equipped with specialized tools to navigate and search the web effectively. Your primary objective is to fulfill the user's requests by efficiently utilizing these tools.
|
4 |
+
|
5 |
+
### Primary Instructions:
|
6 |
+
|
7 |
+
1. **Avoid Guessing URLs**: Never attempt to guess the direct URL. Always perform a Google search if applicable, or return to your previous search results.
|
8 |
+
2. **Navigating to New Pages**: Always use the `ClickElement` tool to open links when navigating to a new web page from the current source. Do not guess the direct URL.
|
9 |
+
3. **Single Page Interaction**: You can only open and interact with one web page at a time. The previous web page will be closed when you open a new one. To navigate back, use the `GoBack` tool.
|
10 |
+
4. **Requesting Screenshots**: Before using tools that interact with the web page, ask the user to send you the appropriate screenshot using one of the commands below.
|
11 |
+
|
12 |
+
### Commands to Request Screenshots:
|
13 |
+
|
14 |
+
- **'[send screenshot]'**: Sends the current browsing window as an image. Use this command if the user asks what is on the page.
|
15 |
+
- **'[highlight clickable elements]'**: Highlights all clickable elements on the current web page. This must be done before using the `ClickElement` tool.
|
16 |
+
- **'[highlight text fields]'**: Highlights all text fields on the current web page. This must be done before using the `SendKeys` tool.
|
17 |
+
- **'[highlight dropdowns]'**: Highlights all dropdowns on the current web page. This must be done before using the `SelectDropdown` tool.
|
18 |
+
|
19 |
+
### Important Reminders:
|
20 |
+
|
21 |
+
- Only open and interact with one web page at a time. Do not attempt to read or click on multiple links simultaneously. Complete your interactions with the current web page before proceeding to a different source.
|
app/agents/BrowsingAgent/requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
selenium
|
2 |
+
webdriver-manager
|
3 |
+
selenium_stealth
|
app/agents/BrowsingAgent/tools/ClickElement.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
from pydantic import Field
|
4 |
+
from selenium.webdriver.common.by import By
|
5 |
+
|
6 |
+
from agency_swarm.tools import BaseTool
|
7 |
+
|
8 |
+
from .util import get_web_driver, set_web_driver
|
9 |
+
from .util.highlights import remove_highlight_and_labels
|
10 |
+
|
11 |
+
|
12 |
+
class ClickElement(BaseTool):
|
13 |
+
"""
|
14 |
+
This tool clicks on an element on the current web page based on its number.
|
15 |
+
|
16 |
+
Before using this tool make sure to highlight clickable elements on the page by outputting '[highlight clickable elements]' message.
|
17 |
+
"""
|
18 |
+
|
19 |
+
element_number: int = Field(
|
20 |
+
...,
|
21 |
+
description="The number of the element to click on. The element numbers are displayed on the page after highlighting elements.",
|
22 |
+
)
|
23 |
+
|
24 |
+
def run(self):
|
25 |
+
wd = get_web_driver()
|
26 |
+
|
27 |
+
if "button" not in self._shared_state.get("elements_highlighted", ""):
|
28 |
+
raise ValueError(
|
29 |
+
"Please highlight clickable elements on the page first by outputting '[highlight clickable elements]' message. You must output just the message without calling the tool first, so the user can respond with the screenshot."
|
30 |
+
)
|
31 |
+
|
32 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
33 |
+
|
34 |
+
# iterate through all elements with a number in the text
|
35 |
+
try:
|
36 |
+
element_text = all_elements[self.element_number - 1].text
|
37 |
+
element_text = element_text.strip() if element_text else ""
|
38 |
+
# Subtract 1 because sequence numbers start at 1, but list indices start at 0
|
39 |
+
try:
|
40 |
+
all_elements[self.element_number - 1].click()
|
41 |
+
except Exception as e:
|
42 |
+
if "element click intercepted" in str(e).lower():
|
43 |
+
wd.execute_script(
|
44 |
+
"arguments[0].click();", all_elements[self.element_number - 1]
|
45 |
+
)
|
46 |
+
else:
|
47 |
+
raise e
|
48 |
+
|
49 |
+
time.sleep(3)
|
50 |
+
|
51 |
+
result = f"Clicked on element {self.element_number}. Text on clicked element: '{element_text}'. Current URL is {wd.current_url} To further analyze the page, output '[send screenshot]' command."
|
52 |
+
except IndexError:
|
53 |
+
result = "Element number is invalid. Please try again with a valid element number."
|
54 |
+
except Exception as e:
|
55 |
+
result = str(e)
|
56 |
+
|
57 |
+
wd = remove_highlight_and_labels(wd)
|
58 |
+
|
59 |
+
wd.execute_script("document.body.style.zoom='1.5'")
|
60 |
+
|
61 |
+
set_web_driver(wd)
|
62 |
+
|
63 |
+
self._shared_state.set("elements_highlighted", "")
|
64 |
+
|
65 |
+
return result
|
app/agents/BrowsingAgent/tools/ExportFile.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
|
3 |
+
from agency_swarm.tools import BaseTool
|
4 |
+
|
5 |
+
from .util import get_web_driver
|
6 |
+
|
7 |
+
|
8 |
+
class ExportFile(BaseTool):
|
9 |
+
"""This tool converts the current full web page into a file and returns its file_id. You can then send this file id back to the user for further processing."""
|
10 |
+
|
11 |
+
def run(self):
|
12 |
+
wd = get_web_driver()
|
13 |
+
from agency_swarm import get_openai_client
|
14 |
+
|
15 |
+
client = get_openai_client()
|
16 |
+
|
17 |
+
# Define the parameters for the PDF
|
18 |
+
params = {
|
19 |
+
"landscape": False,
|
20 |
+
"displayHeaderFooter": False,
|
21 |
+
"printBackground": True,
|
22 |
+
"preferCSSPageSize": True,
|
23 |
+
}
|
24 |
+
|
25 |
+
# Execute the command to print to PDF
|
26 |
+
result = wd.execute_cdp_cmd("Page.printToPDF", params)
|
27 |
+
pdf = result["data"]
|
28 |
+
|
29 |
+
pdf_bytes = base64.b64decode(pdf)
|
30 |
+
|
31 |
+
# Save the PDF to a file
|
32 |
+
with open("exported_file.pdf", "wb") as f:
|
33 |
+
f.write(pdf_bytes)
|
34 |
+
|
35 |
+
file_id = client.files.create(
|
36 |
+
file=open("exported_file.pdf", "rb"),
|
37 |
+
purpose="assistants",
|
38 |
+
).id
|
39 |
+
|
40 |
+
self._shared_state.set("file_id", file_id)
|
41 |
+
|
42 |
+
return (
|
43 |
+
"Success. File exported with id: `"
|
44 |
+
+ file_id
|
45 |
+
+ "` You can now send this file id back to the user."
|
46 |
+
)
|
47 |
+
|
48 |
+
|
49 |
+
if __name__ == "__main__":
|
50 |
+
wd = get_web_driver()
|
51 |
+
wd.get("https://www.google.com")
|
52 |
+
tool = ExportFile()
|
53 |
+
tool.run()
|
app/agents/BrowsingAgent/tools/GoBack.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
from agency_swarm.tools import BaseTool
|
4 |
+
|
5 |
+
from .util.selenium import get_web_driver, set_web_driver
|
6 |
+
|
7 |
+
|
8 |
+
class GoBack(BaseTool):
|
9 |
+
"""W
|
10 |
+
This tool allows you to go back 1 page in the browser history. Use it in case of a mistake or if a page shows you unexpected content.
|
11 |
+
"""
|
12 |
+
|
13 |
+
def run(self):
|
14 |
+
wd = get_web_driver()
|
15 |
+
|
16 |
+
wd.back()
|
17 |
+
|
18 |
+
time.sleep(3)
|
19 |
+
|
20 |
+
set_web_driver(wd)
|
21 |
+
|
22 |
+
return "Success. Went back 1 page. Current URL is: " + wd.current_url
|
app/agents/BrowsingAgent/tools/ReadURL.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
from pydantic import Field
|
4 |
+
|
5 |
+
from agency_swarm.tools import BaseTool
|
6 |
+
|
7 |
+
from .util.selenium import get_web_driver, set_web_driver
|
8 |
+
|
9 |
+
|
10 |
+
class ReadURL(BaseTool):
|
11 |
+
"""
|
12 |
+
This tool reads a single URL and opens it in your current browser window. For each new source, either navigate directly to a URL that you believe contains the answer to the user's question or perform a Google search (e.g., 'https://google.com/search?q=search') if necessary.
|
13 |
+
|
14 |
+
If you are unsure of the direct URL, do not guess. Instead, use the ClickElement tool to click on links that might contain the desired information on the current web page.
|
15 |
+
|
16 |
+
Note: This tool only supports opening one URL at a time. The previous URL will be closed when you open a new one.
|
17 |
+
"""
|
18 |
+
|
19 |
+
chain_of_thought: str = Field(
|
20 |
+
...,
|
21 |
+
description="Think step-by-step about where you need to navigate next to find the necessary information.",
|
22 |
+
exclude=True,
|
23 |
+
)
|
24 |
+
url: str = Field(
|
25 |
+
...,
|
26 |
+
description="URL of the webpage.",
|
27 |
+
examples=["https://google.com/search?q=search"],
|
28 |
+
)
|
29 |
+
|
30 |
+
class ToolConfig:
|
31 |
+
one_call_at_a_time: bool = True
|
32 |
+
|
33 |
+
def run(self):
|
34 |
+
wd = get_web_driver()
|
35 |
+
|
36 |
+
wd.get(self.url)
|
37 |
+
|
38 |
+
time.sleep(2)
|
39 |
+
|
40 |
+
set_web_driver(wd)
|
41 |
+
|
42 |
+
self._shared_state.set("elements_highlighted", "")
|
43 |
+
|
44 |
+
return (
|
45 |
+
"Current URL is: "
|
46 |
+
+ wd.current_url
|
47 |
+
+ "\n"
|
48 |
+
+ "Please output '[send screenshot]' next to analyze the current web page or '[highlight clickable elements]' for further navigation."
|
49 |
+
)
|
50 |
+
|
51 |
+
|
52 |
+
if __name__ == "__main__":
|
53 |
+
tool = ReadURL(url="https://google.com")
|
54 |
+
print(tool.run())
|
app/agents/BrowsingAgent/tools/Scroll.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Literal
|
2 |
+
|
3 |
+
from pydantic import Field
|
4 |
+
|
5 |
+
from agency_swarm.tools import BaseTool
|
6 |
+
|
7 |
+
from .util.selenium import get_web_driver, set_web_driver
|
8 |
+
|
9 |
+
|
10 |
+
class Scroll(BaseTool):
|
11 |
+
"""
|
12 |
+
This tool allows you to scroll the current web page up or down by 1 screen height.
|
13 |
+
"""
|
14 |
+
|
15 |
+
direction: Literal["up", "down"] = Field(..., description="Direction to scroll.")
|
16 |
+
|
17 |
+
def run(self):
|
18 |
+
wd = get_web_driver()
|
19 |
+
|
20 |
+
height = wd.get_window_size()["height"]
|
21 |
+
|
22 |
+
# Get the zoom level
|
23 |
+
zoom_level = wd.execute_script("return document.body.style.zoom || '1';")
|
24 |
+
zoom_level = (
|
25 |
+
float(zoom_level.strip("%")) / 100
|
26 |
+
if "%" in zoom_level
|
27 |
+
else float(zoom_level)
|
28 |
+
)
|
29 |
+
|
30 |
+
# Adjust height by zoom level
|
31 |
+
adjusted_height = height / zoom_level
|
32 |
+
|
33 |
+
current_scroll_position = wd.execute_script("return window.pageYOffset;")
|
34 |
+
total_scroll_height = wd.execute_script("return document.body.scrollHeight;")
|
35 |
+
|
36 |
+
result = ""
|
37 |
+
|
38 |
+
if self.direction == "up":
|
39 |
+
if current_scroll_position == 0:
|
40 |
+
# Reached the top of the page
|
41 |
+
result = "Reached the top of the page. Cannot scroll up any further.\n"
|
42 |
+
else:
|
43 |
+
wd.execute_script(f"window.scrollBy(0, -{adjusted_height});")
|
44 |
+
result = "Scrolled up by 1 screen height. Make sure to output '[send screenshot]' command to analyze the page after scrolling."
|
45 |
+
|
46 |
+
elif self.direction == "down":
|
47 |
+
if current_scroll_position + adjusted_height >= total_scroll_height:
|
48 |
+
# Reached the bottom of the page
|
49 |
+
result = (
|
50 |
+
"Reached the bottom of the page. Cannot scroll down any further.\n"
|
51 |
+
)
|
52 |
+
else:
|
53 |
+
wd.execute_script(f"window.scrollBy(0, {adjusted_height});")
|
54 |
+
result = "Scrolled down by 1 screen height. Make sure to output '[send screenshot]' command to analyze the page after scrolling."
|
55 |
+
|
56 |
+
set_web_driver(wd)
|
57 |
+
|
58 |
+
return result
|
app/agents/BrowsingAgent/tools/SelectDropdown.py
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Dict
|
2 |
+
|
3 |
+
from pydantic import Field, model_validator
|
4 |
+
from selenium.webdriver.common.by import By
|
5 |
+
from selenium.webdriver.support.select import Select
|
6 |
+
|
7 |
+
from agency_swarm.tools import BaseTool
|
8 |
+
|
9 |
+
from .util import get_web_driver, set_web_driver
|
10 |
+
from .util.highlights import remove_highlight_and_labels
|
11 |
+
|
12 |
+
|
13 |
+
class SelectDropdown(BaseTool):
|
14 |
+
"""
|
15 |
+
This tool selects an option in a dropdown on the current web page based on the description of that element and which option to select.
|
16 |
+
|
17 |
+
Before using this tool make sure to highlight dropdown elements on the page by outputting '[highlight dropdowns]' message.
|
18 |
+
"""
|
19 |
+
|
20 |
+
key_value_pairs: Dict[str, str] = Field(
|
21 |
+
...,
|
22 |
+
description="A dictionary where the key is the sequence number of the dropdown element and the value is the index of the option to select.",
|
23 |
+
examples=[{"1": 0, "2": 1}, {"3": 2}],
|
24 |
+
)
|
25 |
+
|
26 |
+
@model_validator(mode="before")
|
27 |
+
@classmethod
|
28 |
+
def check_key_value_pairs(cls, data):
|
29 |
+
if not data.get("key_value_pairs"):
|
30 |
+
raise ValueError(
|
31 |
+
"key_value_pairs is required. Example format: "
|
32 |
+
"key_value_pairs={'1': 0, '2': 1}"
|
33 |
+
)
|
34 |
+
return data
|
35 |
+
|
36 |
+
def run(self):
|
37 |
+
wd = get_web_driver()
|
38 |
+
|
39 |
+
if "select" not in self._shared_state.get("elements_highlighted", ""):
|
40 |
+
raise ValueError(
|
41 |
+
"Please highlight dropdown elements on the page first by outputting '[highlight dropdowns]' message. You must output just the message without calling the tool first, so the user can respond with the screenshot."
|
42 |
+
)
|
43 |
+
|
44 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
45 |
+
|
46 |
+
try:
|
47 |
+
for key, value in self.key_value_pairs.items():
|
48 |
+
key = int(key)
|
49 |
+
element = all_elements[key - 1]
|
50 |
+
|
51 |
+
select = Select(element)
|
52 |
+
|
53 |
+
# Select the first option (index 0)
|
54 |
+
select.select_by_index(int(value))
|
55 |
+
result = f"Success. Option is selected in the dropdown. To further analyze the page, output '[send screenshot]' command."
|
56 |
+
except Exception as e:
|
57 |
+
result = str(e)
|
58 |
+
|
59 |
+
remove_highlight_and_labels(wd)
|
60 |
+
|
61 |
+
set_web_driver(wd)
|
62 |
+
|
63 |
+
return result
|
app/agents/BrowsingAgent/tools/SendKeys.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
from typing import Dict
|
3 |
+
|
4 |
+
from pydantic import Field, model_validator
|
5 |
+
from selenium.webdriver import Keys
|
6 |
+
from selenium.webdriver.common.by import By
|
7 |
+
|
8 |
+
from agency_swarm.tools import BaseTool
|
9 |
+
|
10 |
+
from .util import get_web_driver, set_web_driver
|
11 |
+
from .util.highlights import remove_highlight_and_labels
|
12 |
+
|
13 |
+
|
14 |
+
class SendKeys(BaseTool):
|
15 |
+
"""
|
16 |
+
This tool sends keys into input fields on the current webpage based on the description of that element and what needs to be typed. It then clicks "Enter" on the last element to submit the form. You do not need to tell it to press "Enter"; it will do that automatically.
|
17 |
+
|
18 |
+
Before using this tool make sure to highlight the input elements on the page by outputting '[highlight text fields]' message.
|
19 |
+
"""
|
20 |
+
|
21 |
+
elements_and_texts: Dict[int, str] = Field(
|
22 |
+
...,
|
23 |
+
description="A dictionary where the key is the element number and the value is the text to be typed.",
|
24 |
+
examples=[
|
25 |
+
{52: "[email protected]", 53: "password123"},
|
26 |
+
{3: "John Doe", 4: "123 Main St"},
|
27 |
+
],
|
28 |
+
)
|
29 |
+
|
30 |
+
@model_validator(mode="before")
|
31 |
+
@classmethod
|
32 |
+
def check_elements_and_texts(cls, data):
|
33 |
+
if not data.get("elements_and_texts"):
|
34 |
+
raise ValueError(
|
35 |
+
"elements_and_texts is required. Example format: "
|
36 |
+
"elements_and_texts={1: 'John Doe', 2: '123 Main St'}"
|
37 |
+
)
|
38 |
+
return data
|
39 |
+
|
40 |
+
def run(self):
|
41 |
+
wd = get_web_driver()
|
42 |
+
if "input" not in self._shared_state.get("elements_highlighted", ""):
|
43 |
+
raise ValueError(
|
44 |
+
"Please highlight input elements on the page first by outputting '[highlight text fields]' message. You must output just the message without calling the tool first, so the user can respond with the screenshot."
|
45 |
+
)
|
46 |
+
|
47 |
+
all_elements = wd.find_elements(By.CSS_SELECTOR, ".highlighted-element")
|
48 |
+
|
49 |
+
i = 0
|
50 |
+
try:
|
51 |
+
for key, value in self.elements_and_texts.items():
|
52 |
+
key = int(key)
|
53 |
+
element = all_elements[key - 1]
|
54 |
+
|
55 |
+
try:
|
56 |
+
element.click()
|
57 |
+
element.send_keys(Keys.CONTROL + "a") # Select all text in input
|
58 |
+
element.send_keys(Keys.DELETE)
|
59 |
+
element.clear()
|
60 |
+
except Exception as e:
|
61 |
+
pass
|
62 |
+
element.send_keys(value)
|
63 |
+
# send enter key to the last element
|
64 |
+
if i == len(self.elements_and_texts) - 1:
|
65 |
+
element.send_keys(Keys.RETURN)
|
66 |
+
time.sleep(3)
|
67 |
+
i += 1
|
68 |
+
result = f"Sent input to element and pressed Enter. Current URL is {wd.current_url} To further analyze the page, output '[send screenshot]' command."
|
69 |
+
except Exception as e:
|
70 |
+
result = str(e)
|
71 |
+
|
72 |
+
remove_highlight_and_labels(wd)
|
73 |
+
|
74 |
+
set_web_driver(wd)
|
75 |
+
|
76 |
+
return result
|
app/agents/BrowsingAgent/tools/SolveCaptcha.py
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
import time
|
3 |
+
|
4 |
+
from selenium.webdriver.common.by import By
|
5 |
+
from selenium.webdriver.support.expected_conditions import (
|
6 |
+
frame_to_be_available_and_switch_to_it,
|
7 |
+
presence_of_element_located,
|
8 |
+
)
|
9 |
+
from selenium.webdriver.support.wait import WebDriverWait
|
10 |
+
|
11 |
+
from agency_swarm.tools import BaseTool
|
12 |
+
from agency_swarm.util import get_openai_client
|
13 |
+
|
14 |
+
from .util import get_b64_screenshot, remove_highlight_and_labels
|
15 |
+
from .util.selenium import get_web_driver
|
16 |
+
|
17 |
+
|
18 |
+
class SolveCaptcha(BaseTool):
|
19 |
+
"""
|
20 |
+
This tool asks a human to solve captcha on the current webpage. Make sure that captcha is visible before running it.
|
21 |
+
"""
|
22 |
+
|
23 |
+
def run(self):
|
24 |
+
wd = get_web_driver()
|
25 |
+
|
26 |
+
try:
|
27 |
+
WebDriverWait(wd, 10).until(
|
28 |
+
frame_to_be_available_and_switch_to_it(
|
29 |
+
(By.XPATH, "//iframe[@title='reCAPTCHA']")
|
30 |
+
)
|
31 |
+
)
|
32 |
+
|
33 |
+
element = WebDriverWait(wd, 3).until(
|
34 |
+
presence_of_element_located((By.ID, "recaptcha-anchor"))
|
35 |
+
)
|
36 |
+
except Exception as e:
|
37 |
+
return "Could not find captcha checkbox"
|
38 |
+
|
39 |
+
try:
|
40 |
+
# Scroll the element into view
|
41 |
+
wd.execute_script("arguments[0].scrollIntoView(true);", element)
|
42 |
+
time.sleep(1) # Give some time for the scrolling to complete
|
43 |
+
|
44 |
+
# Click the element using JavaScript
|
45 |
+
wd.execute_script("arguments[0].click();", element)
|
46 |
+
except Exception as e:
|
47 |
+
return f"Could not click captcha checkbox: {str(e)}"
|
48 |
+
|
49 |
+
try:
|
50 |
+
# Now check if the reCAPTCHA is checked
|
51 |
+
WebDriverWait(wd, 3).until(
|
52 |
+
lambda d: d.find_element(
|
53 |
+
By.CLASS_NAME, "recaptcha-checkbox"
|
54 |
+
).get_attribute("aria-checked")
|
55 |
+
== "true"
|
56 |
+
)
|
57 |
+
|
58 |
+
return "Success"
|
59 |
+
except Exception as e:
|
60 |
+
pass
|
61 |
+
|
62 |
+
wd.switch_to.default_content()
|
63 |
+
|
64 |
+
client = get_openai_client()
|
65 |
+
|
66 |
+
WebDriverWait(wd, 10).until(
|
67 |
+
frame_to_be_available_and_switch_to_it(
|
68 |
+
(
|
69 |
+
By.XPATH,
|
70 |
+
"//iframe[@title='recaptcha challenge expires in two minutes']",
|
71 |
+
)
|
72 |
+
)
|
73 |
+
)
|
74 |
+
|
75 |
+
time.sleep(2)
|
76 |
+
|
77 |
+
attempts = 0
|
78 |
+
while attempts < 5:
|
79 |
+
tiles = wd.find_elements(By.CLASS_NAME, "rc-imageselect-tile")
|
80 |
+
|
81 |
+
# filter out tiles with rc-imageselect-dynamic-selected class
|
82 |
+
tiles = [
|
83 |
+
tile
|
84 |
+
for tile in tiles
|
85 |
+
if not tile.get_attribute("class").endswith(
|
86 |
+
"rc-imageselect-dynamic-selected"
|
87 |
+
)
|
88 |
+
]
|
89 |
+
|
90 |
+
image_content = []
|
91 |
+
i = 0
|
92 |
+
for tile in tiles:
|
93 |
+
i += 1
|
94 |
+
screenshot = get_b64_screenshot(wd, tile)
|
95 |
+
|
96 |
+
image_content.append(
|
97 |
+
{
|
98 |
+
"type": "text",
|
99 |
+
"text": f"Image {i}:",
|
100 |
+
}
|
101 |
+
)
|
102 |
+
image_content.append(
|
103 |
+
{
|
104 |
+
"type": "image_url",
|
105 |
+
"image_url": {
|
106 |
+
"url": f"data:image/jpeg;base64,{screenshot}",
|
107 |
+
"detail": "high",
|
108 |
+
},
|
109 |
+
},
|
110 |
+
)
|
111 |
+
# highlight all titles with rc-imageselect-tile class but not with rc-imageselect-dynamic-selected
|
112 |
+
# wd = highlight_elements_with_labels(wd, 'td.rc-imageselect-tile:not(.rc-imageselect-dynamic-selected)')
|
113 |
+
|
114 |
+
# screenshot = get_b64_screenshot(wd, wd.find_element(By.ID, "rc-imageselect"))
|
115 |
+
|
116 |
+
task_text = (
|
117 |
+
wd.find_element(By.CLASS_NAME, "rc-imageselect-instructions")
|
118 |
+
.text.strip()
|
119 |
+
.replace("\n", " ")
|
120 |
+
)
|
121 |
+
|
122 |
+
continuous_task = "once there are none left" in task_text.lower()
|
123 |
+
|
124 |
+
task_text = task_text.replace("Click verify", "Output 0")
|
125 |
+
task_text = task_text.replace("click skip", "Output 0")
|
126 |
+
task_text = task_text.replace("once", "if")
|
127 |
+
task_text = task_text.replace("none left", "none")
|
128 |
+
task_text = task_text.replace("all", "only")
|
129 |
+
task_text = task_text.replace("squares", "images")
|
130 |
+
|
131 |
+
additional_info = ""
|
132 |
+
if len(tiles) > 9:
|
133 |
+
additional_info = (
|
134 |
+
"Keep in mind that all images are a part of a bigger image "
|
135 |
+
"from left to right, and top to bottom. The grid is 4x4. "
|
136 |
+
)
|
137 |
+
|
138 |
+
messages = [
|
139 |
+
{
|
140 |
+
"role": "system",
|
141 |
+
"content": f"""You are an advanced AI designed to support users with visual impairments.
|
142 |
+
User will provide you with {i} images numbered from 1 to {i}. Your task is to output
|
143 |
+
the numbers of the images that contain the requested object, or at least some part of the requested
|
144 |
+
object. {additional_info}If there are no individual images that satisfy this condition, output 0.
|
145 |
+
""".replace("\n", ""),
|
146 |
+
},
|
147 |
+
{
|
148 |
+
"role": "user",
|
149 |
+
"content": [
|
150 |
+
*image_content,
|
151 |
+
{
|
152 |
+
"type": "text",
|
153 |
+
"text": f"{task_text}. Only output numbers separated by commas and nothing else. "
|
154 |
+
f"Output 0 if there are none.",
|
155 |
+
},
|
156 |
+
],
|
157 |
+
},
|
158 |
+
]
|
159 |
+
|
160 |
+
response = client.chat.completions.create(
|
161 |
+
model="gpt-4o",
|
162 |
+
messages=messages,
|
163 |
+
max_tokens=1024,
|
164 |
+
temperature=0.0,
|
165 |
+
)
|
166 |
+
|
167 |
+
message = response.choices[0].message
|
168 |
+
message_text = message.content
|
169 |
+
|
170 |
+
# check if 0 is in the message
|
171 |
+
if "0" in message_text and "10" not in message_text:
|
172 |
+
# Find the button by its ID
|
173 |
+
verify_button = wd.find_element(By.ID, "recaptcha-verify-button")
|
174 |
+
|
175 |
+
verify_button_text = verify_button.text
|
176 |
+
|
177 |
+
# Click the button
|
178 |
+
wd.execute_script("arguments[0].click();", verify_button)
|
179 |
+
|
180 |
+
time.sleep(1)
|
181 |
+
|
182 |
+
try:
|
183 |
+
if self.verify_checkbox(wd):
|
184 |
+
return "Success. Captcha solved."
|
185 |
+
except Exception as e:
|
186 |
+
print("Not checked")
|
187 |
+
pass
|
188 |
+
|
189 |
+
else:
|
190 |
+
numbers = [
|
191 |
+
int(s.strip())
|
192 |
+
for s in message_text.split(",")
|
193 |
+
if s.strip().isdigit()
|
194 |
+
]
|
195 |
+
|
196 |
+
# Click the tiles based on the provided numbers
|
197 |
+
for number in numbers:
|
198 |
+
wd.execute_script("arguments[0].click();", tiles[number - 1])
|
199 |
+
time.sleep(0.5)
|
200 |
+
|
201 |
+
time.sleep(3)
|
202 |
+
|
203 |
+
if not continuous_task:
|
204 |
+
# Find the button by its ID
|
205 |
+
verify_button = wd.find_element(By.ID, "recaptcha-verify-button")
|
206 |
+
|
207 |
+
verify_button_text = verify_button.text
|
208 |
+
|
209 |
+
# Click the button
|
210 |
+
wd.execute_script("arguments[0].click();", verify_button)
|
211 |
+
|
212 |
+
try:
|
213 |
+
if self.verify_checkbox(wd):
|
214 |
+
return "Success. Captcha solved."
|
215 |
+
except Exception as e:
|
216 |
+
pass
|
217 |
+
else:
|
218 |
+
continue
|
219 |
+
|
220 |
+
if "verify" in verify_button_text.lower():
|
221 |
+
attempts += 1
|
222 |
+
|
223 |
+
wd = remove_highlight_and_labels(wd)
|
224 |
+
|
225 |
+
wd.switch_to.default_content()
|
226 |
+
|
227 |
+
# close captcha
|
228 |
+
try:
|
229 |
+
element = WebDriverWait(wd, 3).until(
|
230 |
+
presence_of_element_located((By.XPATH, "//iframe[@title='reCAPTCHA']"))
|
231 |
+
)
|
232 |
+
|
233 |
+
wd.execute_script(
|
234 |
+
f"document.elementFromPoint({element.location['x']}, {element.location['y']-10}).click();"
|
235 |
+
)
|
236 |
+
except Exception as e:
|
237 |
+
print(e)
|
238 |
+
pass
|
239 |
+
|
240 |
+
return "Could not solve captcha."
|
241 |
+
|
242 |
+
def verify_checkbox(self, wd):
|
243 |
+
wd.switch_to.default_content()
|
244 |
+
|
245 |
+
try:
|
246 |
+
WebDriverWait(wd, 10).until(
|
247 |
+
frame_to_be_available_and_switch_to_it(
|
248 |
+
(By.XPATH, "//iframe[@title='reCAPTCHA']")
|
249 |
+
)
|
250 |
+
)
|
251 |
+
|
252 |
+
WebDriverWait(wd, 5).until(
|
253 |
+
lambda d: d.find_element(
|
254 |
+
By.CLASS_NAME, "recaptcha-checkbox"
|
255 |
+
).get_attribute("aria-checked")
|
256 |
+
== "true"
|
257 |
+
)
|
258 |
+
|
259 |
+
return True
|
260 |
+
except Exception as e:
|
261 |
+
wd.switch_to.default_content()
|
262 |
+
|
263 |
+
WebDriverWait(wd, 10).until(
|
264 |
+
frame_to_be_available_and_switch_to_it(
|
265 |
+
(
|
266 |
+
By.XPATH,
|
267 |
+
"//iframe[@title='recaptcha challenge expires in two minutes']",
|
268 |
+
)
|
269 |
+
)
|
270 |
+
)
|
271 |
+
|
272 |
+
return False
|
app/agents/BrowsingAgent/tools/WebPageSummarizer.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from selenium.webdriver.common.by import By
|
2 |
+
|
3 |
+
from agency_swarm.tools import BaseTool
|
4 |
+
|
5 |
+
from .util import get_web_driver, set_web_driver
|
6 |
+
|
7 |
+
|
8 |
+
class WebPageSummarizer(BaseTool):
|
9 |
+
"""
|
10 |
+
This tool summarizes the content of the current web page, extracting the main points and providing a concise summary.
|
11 |
+
"""
|
12 |
+
|
13 |
+
def run(self):
|
14 |
+
from agency_swarm import get_openai_client
|
15 |
+
|
16 |
+
wd = get_web_driver()
|
17 |
+
client = get_openai_client()
|
18 |
+
|
19 |
+
content = wd.find_element(By.TAG_NAME, "body").text
|
20 |
+
|
21 |
+
# only use the first 10000 characters
|
22 |
+
content = " ".join(content.split()[:10000])
|
23 |
+
|
24 |
+
completion = client.chat.completions.create(
|
25 |
+
model="gpt-3.5-turbo",
|
26 |
+
messages=[
|
27 |
+
{
|
28 |
+
"role": "system",
|
29 |
+
"content": "Your task is to summarize the content of the provided webpage. The summary should be concise and informative, capturing the main points and takeaways of the page.",
|
30 |
+
},
|
31 |
+
{
|
32 |
+
"role": "user",
|
33 |
+
"content": "Summarize the content of the following webpage:\n\n"
|
34 |
+
+ content,
|
35 |
+
},
|
36 |
+
],
|
37 |
+
temperature=0.0,
|
38 |
+
)
|
39 |
+
|
40 |
+
return completion.choices[0].message.content
|
41 |
+
|
42 |
+
|
43 |
+
if __name__ == "__main__":
|
44 |
+
wd = get_web_driver()
|
45 |
+
wd.get("https://en.wikipedia.org/wiki/Python_(programming_language)")
|
46 |
+
set_web_driver(wd)
|
47 |
+
tool = WebPageSummarizer()
|
48 |
+
print(tool.run())
|
app/agents/BrowsingAgent/tools/__init__.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .ClickElement import ClickElement
|
2 |
+
from .ExportFile import ExportFile
|
3 |
+
from .GoBack import GoBack
|
4 |
+
from .ReadURL import ReadURL
|
5 |
+
from .Scroll import Scroll
|
6 |
+
from .SelectDropdown import SelectDropdown
|
7 |
+
from .SendKeys import SendKeys
|
8 |
+
from .SolveCaptcha import SolveCaptcha
|
9 |
+
from .WebPageSummarizer import WebPageSummarizer
|
app/agents/BrowsingAgent/tools/util/__init__.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
from .get_b64_screenshot import get_b64_screenshot
|
2 |
+
from .highlights import highlight_elements_with_labels, remove_highlight_and_labels
|
3 |
+
from .selenium import get_web_driver, set_web_driver
|
app/agents/BrowsingAgent/tools/util/get_b64_screenshot.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def get_b64_screenshot(wd, element=None):
|
2 |
+
if element:
|
3 |
+
screenshot_b64 = element.screenshot_as_base64
|
4 |
+
else:
|
5 |
+
screenshot_b64 = wd.get_screenshot_as_base64()
|
6 |
+
|
7 |
+
return screenshot_b64
|
app/agents/BrowsingAgent/tools/util/highlights.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def highlight_elements_with_labels(driver, selector):
|
2 |
+
"""
|
3 |
+
This function highlights clickable elements like buttons, links, and certain divs and spans
|
4 |
+
that match the given CSS selector on the webpage with a red border and ensures that labels are visible and positioned
|
5 |
+
correctly within the viewport.
|
6 |
+
|
7 |
+
:param driver: Instance of Selenium WebDriver.
|
8 |
+
:param selector: CSS selector for the elements to be highlighted.
|
9 |
+
"""
|
10 |
+
script = f"""
|
11 |
+
// Helper function to check if an element is visible
|
12 |
+
function isElementVisible(element) {{
|
13 |
+
var rect = element.getBoundingClientRect();
|
14 |
+
if (rect.width <= 0 || rect.height <= 0 ||
|
15 |
+
rect.top >= (window.innerHeight || document.documentElement.clientHeight) ||
|
16 |
+
rect.bottom <= 0 ||
|
17 |
+
rect.left >= (window.innerWidth || document.documentElement.clientWidth) ||
|
18 |
+
rect.right <= 0) {{
|
19 |
+
return false;
|
20 |
+
}}
|
21 |
+
// Check if any parent element is hidden, which would hide this element as well
|
22 |
+
var parent = element;
|
23 |
+
while (parent) {{
|
24 |
+
var style = window.getComputedStyle(parent);
|
25 |
+
if (style.display === 'none' || style.visibility === 'hidden') {{
|
26 |
+
return false;
|
27 |
+
}}
|
28 |
+
parent = parent.parentElement;
|
29 |
+
}}
|
30 |
+
return true;
|
31 |
+
}}
|
32 |
+
|
33 |
+
// Remove previous labels and styles if they exist
|
34 |
+
document.querySelectorAll('.highlight-label').forEach(function(label) {{
|
35 |
+
label.remove();
|
36 |
+
}});
|
37 |
+
document.querySelectorAll('.highlighted-element').forEach(function(element) {{
|
38 |
+
element.classList.remove('highlighted-element');
|
39 |
+
element.removeAttribute('data-highlighted');
|
40 |
+
}});
|
41 |
+
|
42 |
+
// Inject custom style for highlighting elements
|
43 |
+
var styleElement = document.getElementById('highlight-style');
|
44 |
+
if (!styleElement) {{
|
45 |
+
styleElement = document.createElement('style');
|
46 |
+
styleElement.id = 'highlight-style';
|
47 |
+
document.head.appendChild(styleElement);
|
48 |
+
}}
|
49 |
+
styleElement.textContent = `
|
50 |
+
.highlighted-element {{
|
51 |
+
border: 2px solid red !important;
|
52 |
+
position: relative;
|
53 |
+
box-sizing: border-box;
|
54 |
+
}}
|
55 |
+
.highlight-label {{
|
56 |
+
position: absolute;
|
57 |
+
z-index: 2147483647;
|
58 |
+
background: yellow;
|
59 |
+
color: black;
|
60 |
+
font-size: 25px;
|
61 |
+
padding: 3px 5px;
|
62 |
+
border: 1px solid black;
|
63 |
+
border-radius: 3px;
|
64 |
+
white-space: nowrap;
|
65 |
+
box-shadow: 0px 0px 2px #000;
|
66 |
+
top: -25px;
|
67 |
+
left: 0;
|
68 |
+
display: none;
|
69 |
+
}}
|
70 |
+
`;
|
71 |
+
|
72 |
+
// Function to create and append a label to the body
|
73 |
+
function createAndAdjustLabel(element, index) {{
|
74 |
+
if (!isElementVisible(element)) return;
|
75 |
+
|
76 |
+
element.classList.add('highlighted-element');
|
77 |
+
var label = document.createElement('div');
|
78 |
+
label.className = 'highlight-label';
|
79 |
+
label.textContent = index.toString();
|
80 |
+
label.style.display = 'block'; // Make the label visible
|
81 |
+
|
82 |
+
// Calculate label position
|
83 |
+
var rect = element.getBoundingClientRect();
|
84 |
+
var top = rect.top + window.scrollY - 25; // Position label above the element
|
85 |
+
var left = rect.left + window.scrollX;
|
86 |
+
|
87 |
+
label.style.top = top + 'px';
|
88 |
+
label.style.left = left + 'px';
|
89 |
+
|
90 |
+
document.body.appendChild(label); // Append the label to the body
|
91 |
+
}}
|
92 |
+
|
93 |
+
// Select all clickable elements and apply the styles
|
94 |
+
var allElements = document.querySelectorAll('{selector}');
|
95 |
+
var index = 1;
|
96 |
+
allElements.forEach(function(element) {{
|
97 |
+
// Check if the element is not already highlighted and is visible
|
98 |
+
if (!element.dataset.highlighted && isElementVisible(element)) {{
|
99 |
+
element.dataset.highlighted = 'true';
|
100 |
+
createAndAdjustLabel(element, index++);
|
101 |
+
}}
|
102 |
+
}});
|
103 |
+
"""
|
104 |
+
|
105 |
+
driver.execute_script(script)
|
106 |
+
|
107 |
+
return driver
|
108 |
+
|
109 |
+
|
110 |
+
def remove_highlight_and_labels(driver):
|
111 |
+
"""
|
112 |
+
This function removes all red borders and labels from the webpage elements,
|
113 |
+
reversing the changes made by the highlight functions using Selenium WebDriver.
|
114 |
+
|
115 |
+
:param driver: Instance of Selenium WebDriver.
|
116 |
+
"""
|
117 |
+
selector = (
|
118 |
+
'a, button, input, textarea, div[onclick], div[role="button"], div[tabindex], span[onclick], '
|
119 |
+
'span[role="button"], span[tabindex]'
|
120 |
+
)
|
121 |
+
script = f"""
|
122 |
+
// Remove all labels
|
123 |
+
document.querySelectorAll('.highlight-label').forEach(function(label) {{
|
124 |
+
label.remove();
|
125 |
+
}});
|
126 |
+
|
127 |
+
// Remove the added style for red borders
|
128 |
+
var highlightStyle = document.getElementById('highlight-style');
|
129 |
+
if (highlightStyle) {{
|
130 |
+
highlightStyle.remove();
|
131 |
+
}}
|
132 |
+
|
133 |
+
// Remove inline styles added by highlighting function
|
134 |
+
document.querySelectorAll('{selector}').forEach(function(element) {{
|
135 |
+
element.style.border = '';
|
136 |
+
}});
|
137 |
+
"""
|
138 |
+
|
139 |
+
driver.execute_script(script)
|
140 |
+
|
141 |
+
return driver
|
app/agents/BrowsingAgent/tools/util/selenium.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
wd = None
|
4 |
+
|
5 |
+
selenium_config = {
|
6 |
+
"chrome_profile_path": None,
|
7 |
+
"headless": True,
|
8 |
+
"full_page_screenshot": True,
|
9 |
+
}
|
10 |
+
|
11 |
+
|
12 |
+
def get_web_driver():
|
13 |
+
print("Initializing WebDriver...")
|
14 |
+
try:
|
15 |
+
from selenium import webdriver
|
16 |
+
from selenium.webdriver.chrome.service import Service as ChromeService
|
17 |
+
|
18 |
+
print("Selenium imported successfully.")
|
19 |
+
except ImportError:
|
20 |
+
print("Selenium not installed. Please install it with pip install selenium")
|
21 |
+
raise ImportError
|
22 |
+
|
23 |
+
try:
|
24 |
+
from webdriver_manager.chrome import ChromeDriverManager
|
25 |
+
|
26 |
+
print("webdriver_manager imported successfully.")
|
27 |
+
except ImportError:
|
28 |
+
print(
|
29 |
+
"webdriver_manager not installed. Please install it with pip install webdriver-manager"
|
30 |
+
)
|
31 |
+
raise ImportError
|
32 |
+
|
33 |
+
try:
|
34 |
+
from selenium_stealth import stealth
|
35 |
+
|
36 |
+
print("selenium_stealth imported successfully.")
|
37 |
+
except ImportError:
|
38 |
+
print(
|
39 |
+
"selenium_stealth not installed. Please install it with pip install selenium-stealth"
|
40 |
+
)
|
41 |
+
raise ImportError
|
42 |
+
|
43 |
+
global wd, selenium_config
|
44 |
+
|
45 |
+
if wd:
|
46 |
+
print("Returning existing WebDriver instance.")
|
47 |
+
return wd
|
48 |
+
|
49 |
+
chrome_profile_path = selenium_config.get("chrome_profile_path", None)
|
50 |
+
profile_directory = None
|
51 |
+
user_data_dir = None
|
52 |
+
if isinstance(chrome_profile_path, str) and os.path.exists(chrome_profile_path):
|
53 |
+
profile_directory = (
|
54 |
+
os.path.split(chrome_profile_path)[-1].strip("\\").rstrip("/")
|
55 |
+
)
|
56 |
+
user_data_dir = os.path.split(chrome_profile_path)[0].strip("\\").rstrip("/")
|
57 |
+
print(f"Using Chrome profile: {profile_directory}")
|
58 |
+
print(f"Using Chrome user data dir: {user_data_dir}")
|
59 |
+
print(f"Using Chrome profile path: {chrome_profile_path}")
|
60 |
+
|
61 |
+
chrome_options = webdriver.ChromeOptions()
|
62 |
+
print("ChromeOptions initialized.")
|
63 |
+
|
64 |
+
chrome_driver_path = "/usr/bin/chromedriver"
|
65 |
+
if not os.path.exists(chrome_driver_path):
|
66 |
+
print(
|
67 |
+
"ChromeDriver not found at /usr/bin/chromedriver. Installing using webdriver_manager."
|
68 |
+
)
|
69 |
+
chrome_driver_path = ChromeDriverManager().install()
|
70 |
+
else:
|
71 |
+
print(f"ChromeDriver found at {chrome_driver_path}.")
|
72 |
+
|
73 |
+
if selenium_config.get("headless", False):
|
74 |
+
chrome_options.add_argument("--headless")
|
75 |
+
print("Headless mode enabled.")
|
76 |
+
if selenium_config.get("full_page_screenshot", False):
|
77 |
+
chrome_options.add_argument("--start-maximized")
|
78 |
+
print("Full page screenshot mode enabled.")
|
79 |
+
else:
|
80 |
+
chrome_options.add_argument("--window-size=1920,1080")
|
81 |
+
print("Window size set to 1920,1080.")
|
82 |
+
|
83 |
+
chrome_options.add_argument("--no-sandbox")
|
84 |
+
chrome_options.add_argument("--disable-gpu")
|
85 |
+
chrome_options.add_argument("--disable-dev-shm-usage")
|
86 |
+
chrome_options.add_argument("--remote-debugging-port=9222")
|
87 |
+
chrome_options.add_argument("--disable-extensions")
|
88 |
+
chrome_options.add_argument("--disable-popup-blocking")
|
89 |
+
chrome_options.add_argument("--ignore-certificate-errors")
|
90 |
+
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
91 |
+
chrome_options.add_argument("--disable-web-security")
|
92 |
+
chrome_options.add_argument("--allow-running-insecure-content")
|
93 |
+
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
94 |
+
chrome_options.add_experimental_option("useAutomationExtension", False)
|
95 |
+
print("Chrome options configured.")
|
96 |
+
|
97 |
+
if user_data_dir and profile_directory:
|
98 |
+
chrome_options.add_argument(f"user-data-dir={user_data_dir}")
|
99 |
+
chrome_options.add_argument(f"profile-directory={profile_directory}")
|
100 |
+
print(
|
101 |
+
f"Using user data dir: {user_data_dir} and profile directory: {profile_directory}"
|
102 |
+
)
|
103 |
+
|
104 |
+
try:
|
105 |
+
wd = webdriver.Chrome(
|
106 |
+
service=ChromeService(chrome_driver_path), options=chrome_options
|
107 |
+
)
|
108 |
+
print("WebDriver initialized successfully.")
|
109 |
+
if wd.capabilities["chrome"]["userDataDir"]:
|
110 |
+
print(f"Profile path in use: {wd.capabilities['chrome']['userDataDir']}")
|
111 |
+
except Exception as e:
|
112 |
+
print(f"Error initializing WebDriver: {e}")
|
113 |
+
raise e
|
114 |
+
|
115 |
+
if not selenium_config.get("chrome_profile_path", None):
|
116 |
+
stealth(
|
117 |
+
wd,
|
118 |
+
languages=["en-US", "en"],
|
119 |
+
vendor="Google Inc.",
|
120 |
+
platform="Win32",
|
121 |
+
webgl_vendor="Intel Inc.",
|
122 |
+
renderer="Intel Iris OpenGL Engine",
|
123 |
+
fix_hairline=True,
|
124 |
+
)
|
125 |
+
print("Stealth mode configured.")
|
126 |
+
|
127 |
+
wd.implicitly_wait(3)
|
128 |
+
print("Implicit wait set to 3 seconds.")
|
129 |
+
|
130 |
+
return wd
|
131 |
+
|
132 |
+
|
133 |
+
def set_web_driver(new_wd):
|
134 |
+
# remove all popups
|
135 |
+
js_script = """
|
136 |
+
var popUpSelectors = ['modal', 'popup', 'overlay', 'dialog']; // Add more selectors that are commonly used for pop-ups
|
137 |
+
popUpSelectors.forEach(function(selector) {
|
138 |
+
var elements = document.querySelectorAll(selector);
|
139 |
+
elements.forEach(function(element) {
|
140 |
+
// You can choose to hide or remove; here we're removing the element
|
141 |
+
element.parentNode.removeChild(element);
|
142 |
+
});
|
143 |
+
});
|
144 |
+
"""
|
145 |
+
|
146 |
+
new_wd.execute_script(js_script)
|
147 |
+
|
148 |
+
# Close LinkedIn specific popups
|
149 |
+
if "linkedin.com" in new_wd.current_url:
|
150 |
+
linkedin_js_script = """
|
151 |
+
var linkedinSelectors = ['div.msg-overlay-list-bubble', 'div.ml4.msg-overlay-list-bubble__tablet-height'];
|
152 |
+
linkedinSelectors.forEach(function(selector) {
|
153 |
+
var elements = document.querySelectorAll(selector);
|
154 |
+
elements.forEach(function(element) {
|
155 |
+
element.parentNode.removeChild(element);
|
156 |
+
});
|
157 |
+
});
|
158 |
+
"""
|
159 |
+
new_wd.execute_script(linkedin_js_script)
|
160 |
+
|
161 |
+
new_wd.execute_script("document.body.style.zoom='1.2'")
|
162 |
+
|
163 |
+
global wd
|
164 |
+
wd = new_wd
|
165 |
+
|
166 |
+
|
167 |
+
def set_selenium_config(config):
|
168 |
+
global selenium_config
|
169 |
+
selenium_config = config
|
app/agents/Devid/Devid.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
from typing_extensions import override
|
4 |
+
|
5 |
+
from agency_swarm.agents import Agent
|
6 |
+
from agency_swarm.tools import FileSearch
|
7 |
+
from agency_swarm.util.validators import llm_validator
|
8 |
+
|
9 |
+
|
10 |
+
class Devid(Agent):
|
11 |
+
def __init__(self):
|
12 |
+
super().__init__(
|
13 |
+
name="Devid",
|
14 |
+
description="Devid is an AI software engineer capable of performing advanced coding tasks.",
|
15 |
+
instructions="./instructions.md",
|
16 |
+
files_folder="./files",
|
17 |
+
schemas_folder="./schemas",
|
18 |
+
tools=[FileSearch],
|
19 |
+
tools_folder="./tools",
|
20 |
+
validation_attempts=1,
|
21 |
+
temperature=0,
|
22 |
+
max_prompt_tokens=25000,
|
23 |
+
)
|
24 |
+
|
25 |
+
@override
|
26 |
+
def response_validator(self, message):
|
27 |
+
pattern = r"(```)((.*\n){5,})(```)"
|
28 |
+
|
29 |
+
if re.search(pattern, message):
|
30 |
+
# take only first 100 characters
|
31 |
+
raise ValueError(
|
32 |
+
"You returned code snippet. Please never return code snippets to me. "
|
33 |
+
"Use the FileWriter tool to write the code locally. Then, test it if possible. Continue."
|
34 |
+
)
|
35 |
+
|
36 |
+
llm_validator(
|
37 |
+
statement="Verify whether the update from the AI Developer Agent confirms the task's "
|
38 |
+
"successful completion. If the task remains unfinished, provide guidance "
|
39 |
+
"within the 'reason' argument on the next steps the agent should take. For "
|
40 |
+
"instance, if the agent encountered an error, advise the inclusion of debug "
|
41 |
+
"statements for another attempt. Should the agent outline potential "
|
42 |
+
"solutions or further actions, direct the agent to execute those plans. "
|
43 |
+
"Message does not have to contain code snippets. Just confirmation.",
|
44 |
+
client=self.client,
|
45 |
+
)(message)
|
46 |
+
|
47 |
+
return message
|
app/agents/Devid/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .Devid import Devid
|
app/agents/Devid/instructions.md
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Devid Operational Guide
|
2 |
+
|
3 |
+
As an AI software developer known as Devid, your role involves reading, writing, and modifying files to fulfill tasks derived from user requests.
|
4 |
+
|
5 |
+
**Operational Environment**:
|
6 |
+
- You have direct access to the internet, system executions, or environment variables.
|
7 |
+
- Interaction with the local file system to read, write, and modify files is permitted.
|
8 |
+
- Python is installed in your environment, enabling the execution of Python scripts and code snippets.
|
9 |
+
- Node.js and npm are also installed, allowing for the execution of Node.js scripts and code snippets.
|
10 |
+
- Installation of additional third-party libraries is within your capabilities.
|
11 |
+
- Execution of commands in the terminal to compile and run code is possible.
|
12 |
+
|
13 |
+
## Primary Instructions:
|
14 |
+
|
15 |
+
1. Begin by fully understanding the task at hand. Use the `myfiles_browser` tool to access and review any files uploaded by the user. If initial access to files fails, retry the operation until successful. Continue browsing the files until you have gathered sufficient information to proceed. Skip this step if no files were provided.
|
16 |
+
2. Verify your current directory's path and contents with `ListDir` and `CheckCurrentDir`. If necessary, navigate to the correct directory using the `DirectoryNavigator` tool or create a new directory for the task.
|
17 |
+
3. Utilize the `FileWriter` for creating or modifying files. To read a file, employ the `FileReader` tool. Always modify local files when executing tasks and avoid sending code snippets to the user. Work on one file at a time and refrain from creating or modifying multiple files simultaneously. Complete each file with `FileWriter` before proceeding to the next, integrating these files into the dependencies of the main file.
|
18 |
+
4. Execute your written code with the `CommandExecutor` by running the appropriate terminal commands. Iteratively debug and test to achieve the desired outcome. Seek clarification from the user only after all internal resolution efforts have been exhausted. To install additional libraries, execute the necessary terminal commands.
|
19 |
+
5. Repeat the above steps for each task.
|
20 |
+
|
21 |
+
**Important Note**: Your capabilities include accessing and interacting with local files, online resources, and the terminal. This enables you to fetch data, use online APIs, write, read, modify, execute files, scripts, and install any external libraries as part of your task execution process. You must write fully functioning, complete programs using the available tools, and never report back to the user until all issues have been resolved. Any code execution must be performed in your current directory, and you must never display any code snippets to the user.
|
app/agents/Devid/tools/ChangeFile.py
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from enum import Enum
|
3 |
+
from typing import List, Literal, Optional
|
4 |
+
|
5 |
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
6 |
+
|
7 |
+
from agency_swarm import BaseTool
|
8 |
+
|
9 |
+
|
10 |
+
class LineChange(BaseModel):
|
11 |
+
"""
|
12 |
+
Line changes to be made.
|
13 |
+
"""
|
14 |
+
|
15 |
+
line_number: int = Field(
|
16 |
+
..., description="Line number to change.", examples=[1, 2, 3]
|
17 |
+
)
|
18 |
+
new_line: Optional[str] = Field(
|
19 |
+
None,
|
20 |
+
description="New line to replace the old line. Not required only for delete mode.",
|
21 |
+
examples=["This is a new line"],
|
22 |
+
)
|
23 |
+
mode: Literal["replace", "insert", "delete"] = Field(
|
24 |
+
"replace",
|
25 |
+
description='Mode to use for the line change. "replace" replaces the line with the new line. '
|
26 |
+
'"insert" inserts the new line at the specified line number, moving the previous line down.'
|
27 |
+
' "delete" deletes the specified line number.',
|
28 |
+
)
|
29 |
+
|
30 |
+
@model_validator(mode="after")
|
31 |
+
def validate_new_line(self):
|
32 |
+
mode, new_line = self.mode, self.new_line
|
33 |
+
if mode == "delete" and new_line is not None:
|
34 |
+
raise ValueError("new_line should not be specified for delete mode.")
|
35 |
+
elif mode in ["replace", "insert"] and new_line is None:
|
36 |
+
raise ValueError(
|
37 |
+
"new_line should be specified for replace and insert modes."
|
38 |
+
)
|
39 |
+
return self
|
40 |
+
|
41 |
+
|
42 |
+
class ChangeFile(BaseTool):
|
43 |
+
"""
|
44 |
+
This tool changes specified lines in a file. Returns the new file contents with line numbers at the start of each line.
|
45 |
+
"""
|
46 |
+
|
47 |
+
chain_of_thought: str = Field(
|
48 |
+
...,
|
49 |
+
description="Please think step-by-step about the required changes to the file in order to construct a fully functioning and correct program according to the requirements.",
|
50 |
+
exclude=True,
|
51 |
+
)
|
52 |
+
file_path: str = Field(
|
53 |
+
...,
|
54 |
+
description="Path to the file with extension.",
|
55 |
+
examples=["./file.txt", "./file.json", "../../file.py"],
|
56 |
+
)
|
57 |
+
changes: List[LineChange] = Field(
|
58 |
+
...,
|
59 |
+
description="Line changes to be made to the file.",
|
60 |
+
examples=[
|
61 |
+
{"line_number": 1, "new_line": "This is a new line", "mode": "replace"}
|
62 |
+
],
|
63 |
+
)
|
64 |
+
|
65 |
+
def run(self):
|
66 |
+
# read file
|
67 |
+
with open(self.file_path, "r") as f:
|
68 |
+
file_contents = f.readlines()
|
69 |
+
|
70 |
+
# Process changes in a way that accounts for modifications affecting line numbers
|
71 |
+
for change in sorted(
|
72 |
+
self.changes, key=lambda x: x.line_number, reverse=True
|
73 |
+
):
|
74 |
+
try:
|
75 |
+
if change.mode == "replace" and 0 < change.line_number <= len(
|
76 |
+
file_contents
|
77 |
+
):
|
78 |
+
file_contents[change.line_number - 1] = change.new_line + "\n"
|
79 |
+
elif change.mode == "insert":
|
80 |
+
file_contents.insert(
|
81 |
+
change.line_number - 1, change.new_line + "\n"
|
82 |
+
)
|
83 |
+
elif change.mode == "delete" and 0 < change.line_number <= len(
|
84 |
+
file_contents
|
85 |
+
):
|
86 |
+
file_contents.pop(change.line_number - 1)
|
87 |
+
except IndexError:
|
88 |
+
return f"Error: Line number {change.line_number} is out of the file's range."
|
89 |
+
|
90 |
+
# write file
|
91 |
+
with open(self.file_path, "w") as f:
|
92 |
+
f.writelines(file_contents)
|
93 |
+
|
94 |
+
with open(self.file_path, "r") as f:
|
95 |
+
file_contents = f.readlines()
|
96 |
+
|
97 |
+
# return file contents with line numbers
|
98 |
+
return "\n".join([f"{i + 1}. {line}" for i, line in enumerate(file_contents)])
|
99 |
+
|
100 |
+
# use field validation to ensure that the file path is valid
|
101 |
+
@field_validator("file_path", mode="after")
|
102 |
+
@classmethod
|
103 |
+
def validate_file_path(cls, v: str):
|
104 |
+
if not os.path.exists(v):
|
105 |
+
raise ValueError("File path does not exist.")
|
106 |
+
|
107 |
+
return v
|
app/agents/Devid/tools/CheckCurrentDir.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import Field
|
2 |
+
|
3 |
+
from agency_swarm import BaseTool
|
4 |
+
|
5 |
+
|
6 |
+
class CheckCurrentDir(BaseTool):
|
7 |
+
"""
|
8 |
+
This tool checks the current directory path.
|
9 |
+
"""
|
10 |
+
|
11 |
+
chain_of_thought: str = Field(
|
12 |
+
...,
|
13 |
+
description="Please think step-by-step about what you need to do next, after checking current directory to solve the task.",
|
14 |
+
exclude=True,
|
15 |
+
)
|
16 |
+
|
17 |
+
class ToolConfig:
|
18 |
+
one_call_at_a_time: bool = True
|
19 |
+
|
20 |
+
def run(self):
|
21 |
+
import os
|
22 |
+
|
23 |
+
return os.getcwd()
|
app/agents/Devid/tools/CommandExecutor.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import shlex
|
2 |
+
import subprocess
|
3 |
+
|
4 |
+
from dotenv import find_dotenv, load_dotenv
|
5 |
+
from pydantic import Field
|
6 |
+
|
7 |
+
from agency_swarm.tools import BaseTool
|
8 |
+
|
9 |
+
|
10 |
+
class CommandExecutor(BaseTool):
|
11 |
+
"""
|
12 |
+
Executes a specified command in the terminal and captures the output.
|
13 |
+
|
14 |
+
This tool runs a given command in the system's default shell and returns the stdout and stderr.
|
15 |
+
"""
|
16 |
+
|
17 |
+
command: str = Field(..., description="The command to execute in the terminal.")
|
18 |
+
|
19 |
+
def run(self):
|
20 |
+
"""
|
21 |
+
Executes the command and captures its output.
|
22 |
+
|
23 |
+
Returns:
|
24 |
+
A dictionary containing the standard output (stdout), standard error (stderr),
|
25 |
+
and the exit code of the command.
|
26 |
+
"""
|
27 |
+
load_dotenv(find_dotenv() or None)
|
28 |
+
# Ensure the command is safely split for subprocess
|
29 |
+
command_parts = shlex.split(self.command)
|
30 |
+
|
31 |
+
# Execute the command and capture the output
|
32 |
+
result = subprocess.run(command_parts, capture_output=True, text=True)
|
33 |
+
|
34 |
+
# check if the command failed
|
35 |
+
if result.returncode != 0 or result.stderr:
|
36 |
+
return (
|
37 |
+
f"stdout: {result.stdout}\nstderr: {result.stderr}\nexit code: {result.returncode}\n\n"
|
38 |
+
f"Please add error handling and continue debugging until the command runs successfully."
|
39 |
+
)
|
40 |
+
|
41 |
+
return f"stdout: {result.stdout}\nstderr: {result.stderr}\nexit code: {result.returncode}"
|
42 |
+
|
43 |
+
|
44 |
+
if __name__ == "__main__":
|
45 |
+
tool = CommandExecutor(command="ls -l")
|
46 |
+
print(tool.run())
|
app/agents/Devid/tools/DirectoryNavigator.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
from pydantic import Field, field_validator, model_validator
|
4 |
+
|
5 |
+
from agency_swarm.tools import BaseTool
|
6 |
+
|
7 |
+
|
8 |
+
class DirectoryNavigator(BaseTool):
|
9 |
+
"""Allows you to navigate directories. Do not use this tool more than once at a time.
|
10 |
+
You must finish all tasks in the current directory before navigating into new directory."""
|
11 |
+
|
12 |
+
path: str = Field(..., description="The path of the directory to navigate to.")
|
13 |
+
create: bool = Field(
|
14 |
+
False,
|
15 |
+
description="If True, the directory will be created if it does not exist.",
|
16 |
+
)
|
17 |
+
|
18 |
+
class ToolConfig:
|
19 |
+
one_call_at_a_time: bool = True
|
20 |
+
|
21 |
+
def run(self):
|
22 |
+
try:
|
23 |
+
os.chdir(self.path)
|
24 |
+
return f"Successfully changed directory to: {self.path}"
|
25 |
+
except Exception as e:
|
26 |
+
return f"Error changing directory: {e}"
|
27 |
+
|
28 |
+
@field_validator("create", mode="before")
|
29 |
+
@classmethod
|
30 |
+
def validate_create(cls, v):
|
31 |
+
if not isinstance(v, bool):
|
32 |
+
if v.lower() == "true":
|
33 |
+
return True
|
34 |
+
elif v.lower() == "false":
|
35 |
+
return False
|
36 |
+
return v
|
37 |
+
|
38 |
+
@model_validator(mode="after")
|
39 |
+
def validate_path(self):
|
40 |
+
if not os.path.isdir(self.path):
|
41 |
+
if "/mnt/data" in self.path:
|
42 |
+
raise ValueError(
|
43 |
+
"You tried to access an openai file directory with a local directory reader tool. "
|
44 |
+
+ "Please use the `myfiles_browser` tool to access openai files instead. "
|
45 |
+
+ "Your local files are most likely located in your current directory."
|
46 |
+
)
|
47 |
+
|
48 |
+
if self.create:
|
49 |
+
os.makedirs(self.path)
|
50 |
+
else:
|
51 |
+
raise ValueError(
|
52 |
+
f"The path {self.path} does not exist. Please provide a valid directory path. "
|
53 |
+
+ "If you want to create the directory, set the `create` parameter to True."
|
54 |
+
)
|
55 |
+
|
56 |
+
return self
|
app/agents/Devid/tools/FileMover.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import shutil
|
3 |
+
|
4 |
+
from pydantic import Field
|
5 |
+
|
6 |
+
from agency_swarm.tools import BaseTool
|
7 |
+
|
8 |
+
|
9 |
+
class FileMover(BaseTool):
|
10 |
+
"""
|
11 |
+
FileMover is a tool designed to move files from a source path to a destination path. If the destination directory does not exist, it will be created.
|
12 |
+
"""
|
13 |
+
|
14 |
+
source_path: str = Field(
|
15 |
+
...,
|
16 |
+
description="The full path of the file to move, including the file name and extension.",
|
17 |
+
)
|
18 |
+
destination_path: str = Field(
|
19 |
+
...,
|
20 |
+
description="The destination path where the file should be moved, including the new file name and extension if changing.",
|
21 |
+
)
|
22 |
+
|
23 |
+
def run(self):
|
24 |
+
"""
|
25 |
+
Executes the file moving operation from the source path to the destination path.
|
26 |
+
It checks if the destination directory exists and creates it if necessary, then moves the file.
|
27 |
+
"""
|
28 |
+
if not os.path.exists(self.source_path):
|
29 |
+
return f"Source file does not exist at {self.source_path}"
|
30 |
+
|
31 |
+
# Ensure the destination directory exists
|
32 |
+
destination_dir = os.path.dirname(self.destination_path)
|
33 |
+
if not os.path.exists(destination_dir):
|
34 |
+
os.makedirs(destination_dir)
|
35 |
+
|
36 |
+
# Move the file
|
37 |
+
shutil.move(self.source_path, self.destination_path)
|
38 |
+
|
39 |
+
return f"File moved successfully from {self.source_path} to {self.destination_path}"
|
app/agents/Devid/tools/FileReader.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import Field, field_validator
|
2 |
+
|
3 |
+
from agency_swarm.tools import BaseTool
|
4 |
+
|
5 |
+
|
6 |
+
class FileReader(BaseTool):
|
7 |
+
"""This tool reads a file and returns the contents along with line numbers on the left."""
|
8 |
+
|
9 |
+
file_path: str = Field(
|
10 |
+
...,
|
11 |
+
description="Path to the file to read with extension.",
|
12 |
+
examples=["./file.txt", "./file.json", "../../file.py"],
|
13 |
+
)
|
14 |
+
|
15 |
+
def run(self):
|
16 |
+
# read file
|
17 |
+
with open(self.file_path, "r") as f:
|
18 |
+
file_contents = f.readlines()
|
19 |
+
|
20 |
+
# return file contents
|
21 |
+
return "\n".join([f"{i + 1}. {line}" for i, line in enumerate(file_contents)])
|
22 |
+
|
23 |
+
@field_validator("file_path", mode="after")
|
24 |
+
@classmethod
|
25 |
+
def validate_file_path(cls, v):
|
26 |
+
if "file-" in v:
|
27 |
+
raise ValueError(
|
28 |
+
"You tried to access an openai file with a wrong file reader tool. "
|
29 |
+
"Please use the `myfiles_browser` tool to access openai files instead."
|
30 |
+
"This tool is only for reading local files."
|
31 |
+
)
|
32 |
+
return v
|
app/agents/Devid/tools/FileWriter.py
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
from typing import List, Literal, Optional
|
4 |
+
|
5 |
+
from pydantic import Field, field_validator
|
6 |
+
|
7 |
+
from agency_swarm import get_openai_client
|
8 |
+
from agency_swarm.tools import BaseTool
|
9 |
+
from agency_swarm.util.validators import llm_validator
|
10 |
+
|
11 |
+
from .util import format_file_deps
|
12 |
+
|
13 |
+
history = [
|
14 |
+
{
|
15 |
+
"role": "system",
|
16 |
+
"content": "As a top-tier software engineer focused on developing programs incrementally, you are entrusted with the creation or modification of files based on user requirements. It's imperative to operate under the assumption that all necessary dependencies are pre-installed and accessible, and the file in question will be deployed in an appropriate environment. Furthermore, it is presumed that all other modules or files upon which this file relies are accurate and error-free. Your output should be encapsulated within a code block, without specifying the programming language. Prior to embarking on the coding process, you must outline a methodical, step-by-step plan to precisely fulfill the requirements — no more, no less. It is crucial to ensure that the final code block is a complete file, without any truncation. This file should embody a flawless, fully operational program, inclusive of all requisite imports and functions, devoid of any placeholders, unless specified otherwise by the user.",
|
17 |
+
},
|
18 |
+
]
|
19 |
+
|
20 |
+
|
21 |
+
class FileWriter(BaseTool):
|
22 |
+
"""This tools allows you to write new files or modify existing files according to specified requirements. In 'write' mode, it creates a new file or overwrites an existing one. In 'modify' mode, it modifies an existing file according to the provided requirements.
|
23 |
+
Note: This tool does not have access to other files within the project. You must provide all necessary details to ensure that the generated file can be used in conjunction with other files in this project."""
|
24 |
+
|
25 |
+
file_path: str = Field(
|
26 |
+
...,
|
27 |
+
description="The path of the file to write or modify. Will create directories if they don't exist.",
|
28 |
+
)
|
29 |
+
requirements: str = Field(
|
30 |
+
...,
|
31 |
+
description="The comprehensive requirements explaining how the file should be written or modified. This should be a detailed description of what the file should contain, including example inputs, desired behaviour and ideal outputs. It must not contain any code or implementation details.",
|
32 |
+
)
|
33 |
+
details: str = Field(
|
34 |
+
None,
|
35 |
+
description="Additional details like error messages, or class, function, and variable names from other files that this file depends on.",
|
36 |
+
)
|
37 |
+
documentation: Optional[str] = Field(
|
38 |
+
None,
|
39 |
+
description="Relevant documentation extracted with the myfiles_browser tool. You must pass all the relevant code from the documentation, as this tool does not have access to those files.",
|
40 |
+
)
|
41 |
+
mode: Literal["write", "modify"] = Field(
|
42 |
+
...,
|
43 |
+
description="The mode of operation for the tool. 'write' is used to create a new file or overwrite an existing one. 'modify' is used to modify an existing file.",
|
44 |
+
)
|
45 |
+
file_dependencies: List[str] = Field(
|
46 |
+
[],
|
47 |
+
description="Paths to other files that the file being written depends on.",
|
48 |
+
examples=[
|
49 |
+
"/path/to/dependency1.py",
|
50 |
+
"/path/to/dependency2.css",
|
51 |
+
"/path/to/dependency3.js",
|
52 |
+
],
|
53 |
+
)
|
54 |
+
library_dependencies: List[str] = Field(
|
55 |
+
[],
|
56 |
+
description="Any library dependencies required for the file to be written.",
|
57 |
+
examples=["numpy", "pandas"],
|
58 |
+
)
|
59 |
+
|
60 |
+
class ToolConfig:
|
61 |
+
one_call_at_a_time = True
|
62 |
+
|
63 |
+
def run(self):
|
64 |
+
client = get_openai_client()
|
65 |
+
|
66 |
+
file_dependencies = format_file_deps(self.file_dependencies)
|
67 |
+
|
68 |
+
library_dependencies = ", ".join(self.library_dependencies)
|
69 |
+
|
70 |
+
filename = os.path.basename(self.file_path)
|
71 |
+
|
72 |
+
if self.mode == "write":
|
73 |
+
message = f"Please write {filename} file that meets the following requirements: '{self.requirements}'.\n"
|
74 |
+
else:
|
75 |
+
message = f"Please rewrite the {filename} file according to the following requirements: '{self.requirements}'.\n Only output the file content, without any other text."
|
76 |
+
|
77 |
+
if file_dependencies:
|
78 |
+
message += f"\nHere are the dependencies from other project files: {file_dependencies}."
|
79 |
+
if library_dependencies:
|
80 |
+
message += f"\nUse the following libraries: {library_dependencies}"
|
81 |
+
if self.details:
|
82 |
+
message += f"\nAdditional Details: {self.details}"
|
83 |
+
if self.documentation:
|
84 |
+
message += f"\nDocumentation: {self.documentation}"
|
85 |
+
|
86 |
+
if self.mode == "modify":
|
87 |
+
message += f"\nThe existing file content is as follows:"
|
88 |
+
|
89 |
+
try:
|
90 |
+
with open(self.file_path, "r") as file:
|
91 |
+
file_content = file.read()
|
92 |
+
message += f"\n\n```{file_content}```"
|
93 |
+
except Exception as e:
|
94 |
+
return f"Error reading {self.file_path}: {e}"
|
95 |
+
|
96 |
+
history.append({"role": "user", "content": message})
|
97 |
+
|
98 |
+
messages = history.copy()
|
99 |
+
|
100 |
+
# use the last 5 messages
|
101 |
+
messages = messages[-5:]
|
102 |
+
|
103 |
+
# add system message upfront
|
104 |
+
messages.insert(0, history[0])
|
105 |
+
|
106 |
+
n = 0
|
107 |
+
error_message = ""
|
108 |
+
while n < 3:
|
109 |
+
if self.mode == "modify":
|
110 |
+
resp = client.chat.completions.create(
|
111 |
+
messages=messages,
|
112 |
+
model="gpt-4o",
|
113 |
+
temperature=0,
|
114 |
+
prediction={"type": "content", "content": file_content},
|
115 |
+
)
|
116 |
+
else:
|
117 |
+
resp = client.chat.completions.create(
|
118 |
+
messages=messages,
|
119 |
+
model="gpt-4o",
|
120 |
+
temperature=0,
|
121 |
+
)
|
122 |
+
|
123 |
+
content = resp.choices[0].message.content
|
124 |
+
|
125 |
+
messages.append({"role": "assistant", "content": content})
|
126 |
+
|
127 |
+
pattern = r"```(?:[a-zA-Z]+\n)?(.*?)```"
|
128 |
+
match = re.findall(pattern, content, re.DOTALL)
|
129 |
+
if match:
|
130 |
+
code = match[-1].strip()
|
131 |
+
try:
|
132 |
+
self.validate_content(code)
|
133 |
+
|
134 |
+
history.append({"role": "assistant", "content": content})
|
135 |
+
|
136 |
+
break
|
137 |
+
except Exception as e:
|
138 |
+
print(f"Error: {e}. Trying again.")
|
139 |
+
error_message = str(e)
|
140 |
+
messages.append(
|
141 |
+
{"role": "user", "content": f"Error: {e}. Please try again."}
|
142 |
+
)
|
143 |
+
else:
|
144 |
+
messages.append(
|
145 |
+
{
|
146 |
+
"role": "user",
|
147 |
+
"content": f"Error: Could not find the code block in the response. Please try again.",
|
148 |
+
}
|
149 |
+
)
|
150 |
+
|
151 |
+
n += 1
|
152 |
+
|
153 |
+
if n == 3 or not code:
|
154 |
+
history.append({"role": "assistant", "content": content})
|
155 |
+
history.append({"role": "user", "content": error_message})
|
156 |
+
return "Error: Could not generate a valid file: " + error_message
|
157 |
+
|
158 |
+
try:
|
159 |
+
# create directories if they don't exist
|
160 |
+
dir_path = os.path.dirname(self.file_path)
|
161 |
+
if dir_path != "" and not os.path.exists(dir_path):
|
162 |
+
os.makedirs(dir_path, exist_ok=True)
|
163 |
+
|
164 |
+
with open(self.file_path, "w") as file:
|
165 |
+
file.write(code)
|
166 |
+
return f"Successfully wrote to file: {self.file_path}. Please make sure to now test the program. Below is the content of the file:\n\n```{content}```\n\nPlease now verify the integrity of the file and test it."
|
167 |
+
except Exception as e:
|
168 |
+
return f"Error writing to file: {e}"
|
169 |
+
|
170 |
+
@field_validator("file_dependencies", mode="after")
|
171 |
+
@classmethod
|
172 |
+
def validate_file_dependencies(cls, v):
|
173 |
+
for file in v:
|
174 |
+
if not os.path.exists(file):
|
175 |
+
raise ValueError(f"File dependency '{file}' does not exist.")
|
176 |
+
return v
|
177 |
+
|
178 |
+
def validate_content(self, v):
|
179 |
+
client = get_openai_client()
|
180 |
+
|
181 |
+
llm_validator(
|
182 |
+
statement="Check if the code is bug-free. Code should be considered in isolation, with the understanding that it is part of a larger, fully developed program that strictly adheres to these standards of completeness and correctness. All files, elements, components, functions, or modules referenced within this snippet are assumed to exist in other parts of the project and are also devoid of any errors, ensuring a cohesive and error-free integration across the entire software solution. Certain placeholders may be present.",
|
183 |
+
client=client,
|
184 |
+
model="gpt-4o",
|
185 |
+
temperature=0,
|
186 |
+
allow_override=False,
|
187 |
+
)(v)
|
188 |
+
|
189 |
+
return v
|
190 |
+
|
191 |
+
@field_validator("requirements", mode="after")
|
192 |
+
@classmethod
|
193 |
+
def validate_requirements(cls, v):
|
194 |
+
if "placeholder" in v:
|
195 |
+
raise ValueError(
|
196 |
+
"Requirements contain placeholders. "
|
197 |
+
"Please never user placeholders. Instead, implement only the code that you are confident about."
|
198 |
+
)
|
199 |
+
|
200 |
+
# check if code is included in requirements
|
201 |
+
pattern = r"(```)((.*\n){5,})(```)"
|
202 |
+
if re.search(pattern, v):
|
203 |
+
raise ValueError(
|
204 |
+
"Requirements contain a code snippet. Please never include code snippets in requirements. "
|
205 |
+
"Requirements must be a description of the complete file to be written. You can include specific class, function, and variable names, but not the actual code."
|
206 |
+
)
|
207 |
+
|
208 |
+
return v
|
209 |
+
|
210 |
+
@field_validator("details", mode="after")
|
211 |
+
@classmethod
|
212 |
+
def validate_details(cls, v):
|
213 |
+
if len(v) == 0:
|
214 |
+
raise ValueError(
|
215 |
+
"Details are required. Remember: this tool does not have access to other files. Please provide additional details like relevant documentation, error messages, or class, function, and variable names from other files that this file depends on."
|
216 |
+
)
|
217 |
+
return v
|
218 |
+
|
219 |
+
@field_validator("documentation", mode="after")
|
220 |
+
@classmethod
|
221 |
+
def validate_documentation(cls, v):
|
222 |
+
# check if documentation contains code
|
223 |
+
pattern = r"(```)((.*\n){5,})(```)"
|
224 |
+
pattern2 = r"(`)(.*)(`)"
|
225 |
+
if not (re.search(pattern, v) or re.search(pattern2, v)):
|
226 |
+
raise ValueError(
|
227 |
+
"Documentation does not contain a code snippet. Please provide relevant documentation extracted with the myfiles_browser tool. You must pass all the relevant code snippets information, as this tool does not have access to those files."
|
228 |
+
)
|
229 |
+
|
230 |
+
|
231 |
+
if __name__ == "__main__":
|
232 |
+
# Test case for 'write' mode
|
233 |
+
tool_write = FileWriter(
|
234 |
+
requirements="Write a program that takes a list of integers as input and returns the sum of all the integers in the list.",
|
235 |
+
mode="write",
|
236 |
+
file_path="test_write.py",
|
237 |
+
)
|
238 |
+
print(tool_write.run())
|
239 |
+
|
240 |
+
# Test case for 'modify' mode
|
241 |
+
tool_modify = FileWriter(
|
242 |
+
requirements="Modify the program to also return the product of all the integers in the list.",
|
243 |
+
mode="modify",
|
244 |
+
file_path="test_write.py",
|
245 |
+
)
|
246 |
+
print(tool_modify.run())
|
app/agents/Devid/tools/ListDir.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
from pydantic import Field, field_validator
|
4 |
+
|
5 |
+
from agency_swarm import BaseTool
|
6 |
+
|
7 |
+
|
8 |
+
class ListDir(BaseTool):
|
9 |
+
"""
|
10 |
+
This tool returns the tree structure of the directory.
|
11 |
+
"""
|
12 |
+
|
13 |
+
dir_path: str = Field(
|
14 |
+
...,
|
15 |
+
description="Path of the directory to read.",
|
16 |
+
examples=["./", "./test", "../../"],
|
17 |
+
)
|
18 |
+
|
19 |
+
def run(self):
|
20 |
+
tree = []
|
21 |
+
|
22 |
+
def list_directory_tree(path, indent=""):
|
23 |
+
"""Recursively list the contents of a directory in a tree-like format."""
|
24 |
+
if not os.path.isdir(path):
|
25 |
+
raise ValueError(f"The path {path} is not a valid directory")
|
26 |
+
|
27 |
+
items = os.listdir(path)
|
28 |
+
# exclude common hidden files and directories
|
29 |
+
exclude = [
|
30 |
+
".git",
|
31 |
+
".idea",
|
32 |
+
"__pycache__",
|
33 |
+
"node_modules",
|
34 |
+
".venv",
|
35 |
+
".gitignore",
|
36 |
+
".gitkeep",
|
37 |
+
".DS_Store",
|
38 |
+
".vscode",
|
39 |
+
".next",
|
40 |
+
"dist",
|
41 |
+
"build",
|
42 |
+
"out",
|
43 |
+
"venv",
|
44 |
+
"env",
|
45 |
+
"logs",
|
46 |
+
"data",
|
47 |
+
]
|
48 |
+
|
49 |
+
items = [item for item in items if item not in exclude]
|
50 |
+
|
51 |
+
for i, item in enumerate(items):
|
52 |
+
item_path = os.path.join(path, item)
|
53 |
+
if i < len(items) - 1:
|
54 |
+
tree.append(indent + "├── " + item)
|
55 |
+
if os.path.isdir(item_path):
|
56 |
+
list_directory_tree(item_path, indent + "│ ")
|
57 |
+
else:
|
58 |
+
tree.append(indent + "└── " + item)
|
59 |
+
if os.path.isdir(item_path):
|
60 |
+
list_directory_tree(item_path, indent + " ")
|
61 |
+
|
62 |
+
list_directory_tree(self.dir_path)
|
63 |
+
|
64 |
+
return "\n".join(tree)
|
65 |
+
|
66 |
+
@field_validator("dir_path", mode="after")
|
67 |
+
@classmethod
|
68 |
+
def validate_dir_path(cls, v):
|
69 |
+
if "file-" in v:
|
70 |
+
raise ValueError(
|
71 |
+
"You tried to access an openai file with a local directory reader tool. "
|
72 |
+
"Please use the `myfiles_browser` tool to access openai directories instead."
|
73 |
+
)
|
74 |
+
|
75 |
+
if not os.path.isdir(v):
|
76 |
+
if "/mnt/data" in v:
|
77 |
+
raise ValueError(
|
78 |
+
"You tried to access an openai file directory with a local directory reader tool. "
|
79 |
+
"Please use the `myfiles_browser` tool to access openai files instead. "
|
80 |
+
"You can work in your local directory by using the `FileReader` tool."
|
81 |
+
)
|
82 |
+
|
83 |
+
raise ValueError(f"The path {v} is not a valid directory")
|
84 |
+
return v
|
app/agents/Devid/tools/__init__.py
ADDED
File without changes
|
app/agents/Devid/tools/util/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .format_file_deps import format_file_deps
|
app/agents/Devid/tools/util/format_file_deps.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List, Literal
|
2 |
+
|
3 |
+
from pydantic import BaseModel, Field
|
4 |
+
|
5 |
+
from agency_swarm import get_openai_client
|
6 |
+
|
7 |
+
|
8 |
+
def format_file_deps(v):
|
9 |
+
client = get_openai_client()
|
10 |
+
result = ""
|
11 |
+
for file in v:
|
12 |
+
# extract dependencies from the file using openai
|
13 |
+
with open(file, "r") as f:
|
14 |
+
content = f.read()
|
15 |
+
|
16 |
+
class Dependency(BaseModel):
|
17 |
+
type: Literal["class", "function", "import"] = Field(
|
18 |
+
..., description="The type of the dependency."
|
19 |
+
)
|
20 |
+
name: str = Field(
|
21 |
+
...,
|
22 |
+
description="The name of the dependency, matching the import or definition.",
|
23 |
+
)
|
24 |
+
|
25 |
+
class Dependencies(BaseModel):
|
26 |
+
dependencies: List[Dependency] = Field(
|
27 |
+
[], description="The dependencies extracted from the file."
|
28 |
+
)
|
29 |
+
|
30 |
+
def append_dependencies(self):
|
31 |
+
functions = [
|
32 |
+
dep.name for dep in self.dependencies if dep.type == "function"
|
33 |
+
]
|
34 |
+
classes = [dep.name for dep in self.dependencies if dep.type == "class"]
|
35 |
+
imports = [
|
36 |
+
dep.name for dep in self.dependencies if dep.type == "import"
|
37 |
+
]
|
38 |
+
variables = [
|
39 |
+
dep.name for dep in self.dependencies if dep.type == "variable"
|
40 |
+
]
|
41 |
+
nonlocal result
|
42 |
+
result += f"File path: {file}\n"
|
43 |
+
result += f"Functions: {functions}\nClasses: {classes}\nImports: {imports}\nVariables: {variables}\n\n"
|
44 |
+
|
45 |
+
completion = client.beta.chat.completions.parse(
|
46 |
+
messages=[
|
47 |
+
{
|
48 |
+
"role": "system",
|
49 |
+
"content": "You are a world class dependency resolved. You must extract the dependencies from the file provided.",
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"role": "user",
|
53 |
+
"content": f"Extract the dependencies from the file '{file}'.",
|
54 |
+
},
|
55 |
+
],
|
56 |
+
model="gpt-4o-mini",
|
57 |
+
temperature=0,
|
58 |
+
response_format=Dependencies,
|
59 |
+
)
|
60 |
+
|
61 |
+
if completion.choices[0].message.refusal:
|
62 |
+
raise ValueError(completion.choices[0].message.refusal)
|
63 |
+
|
64 |
+
model = completion.choices[0].message.parsed
|
65 |
+
|
66 |
+
model.append_dependencies()
|
67 |
+
|
68 |
+
return result
|
app/agents/NotionProjectAgent/NotionProjectAgent.py
DELETED
@@ -1,48 +0,0 @@
|
|
1 |
-
import os
|
2 |
-
|
3 |
-
from agency_swarm.agents import Agent
|
4 |
-
from .tools.GetTasks import GetTasksTool
|
5 |
-
from .tools.GetTask import GetTaskTool
|
6 |
-
from .tools.CreateTask import CreateTaskTool
|
7 |
-
from .tools.UpdateTask import UpdateTaskTool
|
8 |
-
from .tools.DeleteTask import DeleteTaskTool
|
9 |
-
|
10 |
-
|
11 |
-
class NotionProjectAgent(Agent):
|
12 |
-
def __init__(self):
|
13 |
-
# Retrieve the Notion integration secret from the environment
|
14 |
-
try:
|
15 |
-
integration_secret = os.environ["NOTION_INTEGRATION_SECRET"]
|
16 |
-
except KeyError:
|
17 |
-
raise EnvironmentError(
|
18 |
-
"NOTION_INTEGRATION_SECRET environment variable is not set."
|
19 |
-
)
|
20 |
-
|
21 |
-
# Initialize the parent Agent class with updated parameters
|
22 |
-
super().__init__(
|
23 |
-
name="NotionProjectAgent",
|
24 |
-
description="Project Management Assistant who tracks and updates project progress on Notion",
|
25 |
-
instructions="./instructions.md",
|
26 |
-
files_folder="./files",
|
27 |
-
schemas_folder="./schemas",
|
28 |
-
tools=[
|
29 |
-
GetTasksTool,
|
30 |
-
GetTaskTool,
|
31 |
-
CreateTaskTool,
|
32 |
-
UpdateTaskTool,
|
33 |
-
DeleteTaskTool,
|
34 |
-
],
|
35 |
-
tools_folder="./tools",
|
36 |
-
model="gpt-4o",
|
37 |
-
temperature=0.3,
|
38 |
-
max_prompt_tokens=25000,
|
39 |
-
# api_headers={
|
40 |
-
# "notion_tasks.json": {
|
41 |
-
# "Authorization": f"Bearer {integration_secret}",
|
42 |
-
# "Notion-Version": "2022-06-28",
|
43 |
-
# }
|
44 |
-
# },
|
45 |
-
)
|
46 |
-
|
47 |
-
def response_validator(self, message):
|
48 |
-
return message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/agents/NotionProjectAgent/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
from .NotionProjectAgent import NotionProjectAgent
|
|
|
|
app/agents/NotionProjectAgent/instructions.md
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
# NotionProjectAgent Instructions
|
2 |
-
|
3 |
-
Your team uses Notion to manage projects and tasks.
|
4 |
-
Users often refer to tasks by name, and sometimes there may not be an exact match, so you should look for the closest ones. If in doubt, provide the user with valid options to choose from or ask for more information if necessary.
|
5 |
-
|
6 |
-
## NOTION_STRUCTURE
|
7 |
-
|
8 |
-
-> Database: The highest level of organization in Notion. Contains all your tasks.
|
9 |
-
--> Page: A task in a Notion database, assignable with due dates.
|
10 |
-
--> Subpage: A child page of a parent Page, assignable to different people.
|
11 |
-
|
12 |
-
## DEFAULT_NOTION_IDS
|
13 |
-
|
14 |
-
Use these IDs unless specified otherwise
|
15 |
-
|
16 |
-
- Database ID: 1a88235ee2ff801e8f93d8ab2e14de1d
|
17 |
-
|
18 |
-
## DATABASE PROPERTIES
|
19 |
-
|
20 |
-
- Task Name
|
21 |
-
- Status
|
22 |
-
- Priority
|
23 |
-
- Due Date
|
24 |
-
|
25 |
-
## WORKFLOWS
|
26 |
-
|
27 |
-
### Create a high level WBS
|
28 |
-
|
29 |
-
When required to create a WBS, you may be prompted with information about the project scope and requirements and/or you may provided with information in a task (page) in the Notion database. Understand the project and create a high level WBS containing 5 to 10 tasks, which cover the project scope for start to end. Each high level task should be a page in the Notion database.
|
30 |
-
|
31 |
-
You may ask the ResearchAndReportAgent to help you with the creation of the WBS or to provide you with supporting information for one or more tasks, in a way that helps whoever started the task to better understand how to complete it.
|
32 |
-
|
33 |
-
### Task Status Reporting
|
34 |
-
|
35 |
-
When asked for a project status report, retrieve all tasks from the Notion database using the GetTasksTool. Group tasks by status (Backlog, In Progress, Testing, Completed) and provide a summary of each group. For tasks that are overdue (due date is in the past and status is not Completed), highlight them and suggest using UpdateTaskTool to adjust either the due date or status. If there are high priority tasks that haven't been started, emphasize these in your report.
|
36 |
-
|
37 |
-
### Project Timeline Management
|
38 |
-
|
39 |
-
When managing project timelines, first retrieve all tasks using GetTasksTool with sorting by due date. Analyze the distribution of tasks over time and identify potential bottlenecks (multiple high-priority tasks due on the same day). For each bottleneck period, retrieve detailed information about each task using GetTaskTool to understand their scope and requirements. Suggest timeline adjustments by using UpdateTaskTool to redistribute workload more evenly. When new tasks are added with CreateTaskTool, automatically check if they affect the existing timeline and provide recommendations for due date assignments that maintain a balanced workload.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/agents/ResearchAndReportAgent/ResearchAndReportAgent.py
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
from agency_swarm.agents import Agent
|
2 |
-
|
3 |
-
|
4 |
-
class ResearchAndReportAgent(Agent):
|
5 |
-
def __init__(self):
|
6 |
-
super().__init__(
|
7 |
-
name="ResearchAndReportAgent",
|
8 |
-
description="Project Management Assistant who supports project planning and execution by performing researching, writing reports and sending them via e-mail or text",
|
9 |
-
instructions="./instructions.md",
|
10 |
-
files_folder="./files",
|
11 |
-
schemas_folder="./schemas",
|
12 |
-
tools=[],
|
13 |
-
tools_folder="./tools",
|
14 |
-
model="gpt-4o",
|
15 |
-
temperature=0.3,
|
16 |
-
max_prompt_tokens=25000,
|
17 |
-
)
|
18 |
-
|
19 |
-
def response_validator(self, message):
|
20 |
-
return message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/agents/ResearchAndReportAgent/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
from .ResearchAndReportAgent import ResearchAndReportAgent
|
|
|
|
app/agents/ResearchAndReportAgent/instructions.md
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
# ResearchAndReportAgent Instructions
|
2 |
-
|
|
|
|
|
|
app/agents/ResearchAndReportAgent/tools/ExampleTool.py
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
from agency_swarm.tools import BaseTool
|
2 |
-
from pydantic import Field
|
3 |
-
import os
|
4 |
-
|
5 |
-
account_id = "MY_ACCOUNT_ID"
|
6 |
-
api_key = os.getenv("MY_API_KEY") # or access_token = os.getenv("MY_ACCESS_TOKEN")
|
7 |
-
|
8 |
-
class ExampleTool(BaseTool):
|
9 |
-
"""
|
10 |
-
A brief description of what the custom tool does.
|
11 |
-
The docstring should clearly explain the tool's purpose and functionality.
|
12 |
-
It will be used by the agent to determine when to use this tool.
|
13 |
-
"""
|
14 |
-
|
15 |
-
# Define the fields with descriptions using Pydantic Field
|
16 |
-
example_field: str = Field(
|
17 |
-
..., description="Description of the example field, explaining its purpose and usage for the Agent."
|
18 |
-
)
|
19 |
-
|
20 |
-
def run(self):
|
21 |
-
"""
|
22 |
-
The implementation of the run method, where the tool's main functionality is executed.
|
23 |
-
This method should utilize the fields defined above to perform the task.
|
24 |
-
Docstring is not required for this method and will not be used by the agent.
|
25 |
-
"""
|
26 |
-
# Your custom tool logic goes here
|
27 |
-
# do_something(self.example_field, api_key, account_id)
|
28 |
-
|
29 |
-
# Return the result of the tool's operation as a string
|
30 |
-
return "Result of ExampleTool operation"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/agents/TechnicalProjectManager/TechnicalProjectManager.py
CHANGED
@@ -1,16 +1,28 @@
|
|
1 |
from agency_swarm.agents import Agent
|
2 |
from .tools.SendWhatsAppText import SendWhatsAppText
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
|
5 |
class TechnicalProjectManager(Agent):
|
6 |
def __init__(self):
|
7 |
super().__init__(
|
8 |
name="TechnicalProjectManager",
|
9 |
-
description="João Morossini's AI proxy, working as a Technical Project Manager at
|
10 |
instructions="./instructions.md",
|
11 |
files_folder="./files",
|
12 |
schemas_folder="./schemas",
|
13 |
-
tools=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
tools_folder="./tools",
|
15 |
model="gpt-4o",
|
16 |
temperature=0.3,
|
|
|
1 |
from agency_swarm.agents import Agent
|
2 |
from .tools.SendWhatsAppText import SendWhatsAppText
|
3 |
+
from .tools.CreateTask import CreateTask
|
4 |
+
from .tools.UpdateTask import UpdateTask
|
5 |
+
from .tools.DeleteTask import DeleteTask
|
6 |
+
from .tools.GetTask import GetTask
|
7 |
+
from .tools.GetTasks import GetTasks
|
8 |
|
9 |
|
10 |
class TechnicalProjectManager(Agent):
|
11 |
def __init__(self):
|
12 |
super().__init__(
|
13 |
name="TechnicalProjectManager",
|
14 |
+
description="João Morossini's AI proxy, working as a Technical Project Manager at VRSEN AI",
|
15 |
instructions="./instructions.md",
|
16 |
files_folder="./files",
|
17 |
schemas_folder="./schemas",
|
18 |
+
tools=[
|
19 |
+
SendWhatsAppText,
|
20 |
+
CreateTask,
|
21 |
+
UpdateTask,
|
22 |
+
DeleteTask,
|
23 |
+
GetTask,
|
24 |
+
GetTasks,
|
25 |
+
],
|
26 |
tools_folder="./tools",
|
27 |
model="gpt-4o",
|
28 |
temperature=0.3,
|
app/agents/TechnicalProjectManager/instructions.md
CHANGED
@@ -2,7 +2,48 @@
|
|
2 |
|
3 |
You are a Technical Project Manager working at VRSEN AI.
|
4 |
|
5 |
-
|
6 |
|
7 |
-
-
|
8 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
You are a Technical Project Manager working at VRSEN AI.
|
4 |
|
5 |
+
Your Team consists of:
|
6 |
|
7 |
+
- Devid (Code writing and execution)
|
8 |
+
- BrowsingAgent (Online research and broswer actions)
|
9 |
+
|
10 |
+
## NOTION INSTRUCTIONS
|
11 |
+
|
12 |
+
Your team uses Notion to manage projects and tasks.
|
13 |
+
Users often refer to tasks by name, and sometimes there may not be an exact match, so you should look for the closest ones. If in doubt, provide the user with valid options to choose from or ask for more information if necessary.
|
14 |
+
|
15 |
+
Remember to track your team's tasks in Notion.
|
16 |
+
|
17 |
+
Always use this database ID unless specified otherwise
|
18 |
+
|
19 |
+
- Database ID: 1a88235ee2ff801e8f93d8ab2e14de1d
|
20 |
+
|
21 |
+
### NOTION_STRUCTURE
|
22 |
+
|
23 |
+
-> Database: The highest level of organization in Notion. Contains all your tasks.
|
24 |
+
--> Page: A task in a Notion database, assignable with due dates.
|
25 |
+
--> Subpage: A child page of a parent Page, assignable to different people.
|
26 |
+
|
27 |
+
## DATABASE PROPERTIES
|
28 |
+
|
29 |
+
- Task Name
|
30 |
+
- Task Description
|
31 |
+
- Status
|
32 |
+
- Priority
|
33 |
+
- Due Date
|
34 |
+
|
35 |
+
## WORKFLOWS
|
36 |
+
|
37 |
+
### Create a high level WBS
|
38 |
+
|
39 |
+
When required to create a WBS, you may be prompted with information about the project scope and requirements and/or you may provided with information in a task (page) in the Notion database. Understand the project and create a high level WBS containing 5 to 10 tasks, which cover the project scope for start to end. Each high level task should be a page in the Notion database.
|
40 |
+
|
41 |
+
You may ask the ResearchAndReportAgent to help you with the creation of the WBS or to provide you with supporting information for one or more tasks, in a way that helps whoever started the task to better understand how to complete it.
|
42 |
+
|
43 |
+
### Task Status Reporting
|
44 |
+
|
45 |
+
When asked for a project status report, retrieve all tasks from the Notion database using the GetTasksTool. Group tasks by status (Backlog, In Progress, Testing, Completed) and provide a summary of each group. For tasks that are overdue (due date is in the past and status is not Completed), highlight them and suggest using UpdateTaskTool to adjust either the due date or status. If there are high priority tasks that haven't been started, emphasize these in your report.
|
46 |
+
|
47 |
+
### Project Timeline Management
|
48 |
+
|
49 |
+
When managing project timelines, first retrieve all tasks using GetTasksTool with sorting by due date. Analyze the distribution of tasks over time and identify potential bottlenecks (multiple high-priority tasks due on the same day). For each bottleneck period, retrieve detailed information about each task using GetTaskTool to understand their scope and requirements. Suggest timeline adjustments by using UpdateTaskTool to redistribute workload more evenly. When new tasks are added with CreateTaskTool, automatically check if they affect the existing timeline and provide recommendations for due date assignments that maintain a balanced workload.
|
app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/CreateTask.py
RENAMED
File without changes
|
app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/DeleteTask.py
RENAMED
File without changes
|
app/agents/TechnicalProjectManager/tools/ExampleTool.py
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
from agency_swarm.tools import BaseTool
|
2 |
-
from pydantic import Field
|
3 |
-
import os
|
4 |
-
|
5 |
-
account_id = "MY_ACCOUNT_ID"
|
6 |
-
api_key = os.getenv("MY_API_KEY") # or access_token = os.getenv("MY_ACCESS_TOKEN")
|
7 |
-
|
8 |
-
class ExampleTool(BaseTool):
|
9 |
-
"""
|
10 |
-
A brief description of what the custom tool does.
|
11 |
-
The docstring should clearly explain the tool's purpose and functionality.
|
12 |
-
It will be used by the agent to determine when to use this tool.
|
13 |
-
"""
|
14 |
-
|
15 |
-
# Define the fields with descriptions using Pydantic Field
|
16 |
-
example_field: str = Field(
|
17 |
-
..., description="Description of the example field, explaining its purpose and usage for the Agent."
|
18 |
-
)
|
19 |
-
|
20 |
-
def run(self):
|
21 |
-
"""
|
22 |
-
The implementation of the run method, where the tool's main functionality is executed.
|
23 |
-
This method should utilize the fields defined above to perform the task.
|
24 |
-
Docstring is not required for this method and will not be used by the agent.
|
25 |
-
"""
|
26 |
-
# Your custom tool logic goes here
|
27 |
-
# do_something(self.example_field, api_key, account_id)
|
28 |
-
|
29 |
-
# Return the result of the tool's operation as a string
|
30 |
-
return "Result of ExampleTool operation"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/GetTask.py
RENAMED
File without changes
|
app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/GetTasks.py
RENAMED
File without changes
|
app/agents/{NotionProjectAgent → TechnicalProjectManager}/tools/UpdateTask.py
RENAMED
File without changes
|