Merge pull request #58 from Fosowl/dev
Web navigation improvement, error section in readme, role updated for router, fix bug
This commit is contained in:
@@ -44,6 +44,10 @@
|
|||||||
|
|
||||||
## **Installation**
|
## **Installation**
|
||||||
|
|
||||||
|
Make sure you have chrome driver and docker installed.
|
||||||
|
|
||||||
|
For issues related to chrome driver, see the **Chromedriver** section.
|
||||||
|
|
||||||
### 1️⃣ **Clone the repository and setup**
|
### 1️⃣ **Clone the repository and setup**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -219,6 +223,29 @@ provider_server_address = 127.0.0.1:5000
|
|||||||
|
|
||||||
`provider_server_address`: can be set to anything if you are not using the server provider.
|
`provider_server_address`: can be set to anything if you are not using the server provider.
|
||||||
|
|
||||||
|
# Known issues
|
||||||
|
|
||||||
|
## Chromedriver Issues
|
||||||
|
|
||||||
|
**Known error #1:** *chromedriver mismatch*
|
||||||
|
|
||||||
|
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||||
|
Current browser version is 134.0.6998.89 with binary path`
|
||||||
|
|
||||||
|
This happen if there is a mismatch between your browser and chromedriver version.
|
||||||
|
|
||||||
|
You need to navigate to download the latest version:
|
||||||
|
|
||||||
|
https://developer.chrome.com/docs/chromedriver/downloads
|
||||||
|
|
||||||
|
If you're using Chrome version 115 or newer go to:
|
||||||
|
|
||||||
|
https://googlechromelabs.github.io/chrome-for-testing/
|
||||||
|
|
||||||
|
And download the chromedriver version matching your OS.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
**Q: What hardware do I need?**
|
**Q: What hardware do I need?**
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,6 @@ def main():
|
|||||||
name="File Agent",
|
name="File Agent",
|
||||||
prompt_path="prompts/file_agent.txt",
|
prompt_path="prompts/file_agent.txt",
|
||||||
provider=provider),
|
provider=provider),
|
||||||
PlannerAgent(model=config["MAIN"]["provider_model"],
|
|
||||||
name="Planner",
|
|
||||||
prompt_path="prompts/planner_agent.txt",
|
|
||||||
provider=provider),
|
|
||||||
BrowserAgent(model=config["MAIN"]["provider_model"],
|
BrowserAgent(model=config["MAIN"]["provider_model"],
|
||||||
name="Browser",
|
name="Browser",
|
||||||
prompt_path="prompts/browser_agent.txt",
|
prompt_path="prompts/browser_agent.txt",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
@@ -44,7 +44,7 @@ User: "I need to find the file config.txt and read its contents."
|
|||||||
|
|
||||||
Assistant: I’ll use file_finder to locate the file:
|
Assistant: I’ll use file_finder to locate the file:
|
||||||
|
|
||||||
```file_finder
|
```file_finder:read
|
||||||
config.txt
|
config.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ class BrowserAgent(Agent):
|
|||||||
self.tools = {
|
self.tools = {
|
||||||
"web_search": searxSearch(),
|
"web_search": searxSearch(),
|
||||||
}
|
}
|
||||||
self.role = "deep research and web search"
|
self.role = "web search, internet, google"
|
||||||
self.browser = Browser()
|
self.browser = Browser()
|
||||||
self.browser.go_to("https://github.com/")
|
|
||||||
self.search_history = []
|
self.search_history = []
|
||||||
self.navigable_links = []
|
self.navigable_links = []
|
||||||
self.notes = []
|
self.notes = []
|
||||||
@@ -50,7 +49,7 @@ class BrowserAgent(Agent):
|
|||||||
{search_choice}
|
{search_choice}
|
||||||
Your goal is to find accurate and complete information to satisfy the user’s request.
|
Your goal is to find accurate and complete information to satisfy the user’s request.
|
||||||
User request: {user_prompt}
|
User request: {user_prompt}
|
||||||
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I want to navigate to <link>."
|
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I want to navigate to <link>"
|
||||||
Do not explain your choice.
|
Do not explain your choice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -58,27 +57,44 @@ class BrowserAgent(Agent):
|
|||||||
remaining_links = self.get_unvisited_links()
|
remaining_links = self.get_unvisited_links()
|
||||||
remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, proceed with a new search."
|
remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, proceed with a new search."
|
||||||
return f"""
|
return f"""
|
||||||
\nYou are currently browsing the web. Not the user, you are the browser.
|
You are a web browser.
|
||||||
|
You are currently on this webpage:
|
||||||
Page content:
|
|
||||||
{page_text}
|
{page_text}
|
||||||
|
|
||||||
You can navigate to these links:
|
You can navigate to these navigation links:
|
||||||
{remaining_links}
|
{remaining_links}
|
||||||
|
|
||||||
You must choose a link (write it down) to navigate to, or go back.
|
Your task:
|
||||||
For exemple you can say: i want to go to www.wikipedia.org/cats
|
1. Decide if the current page answers the user’s query: {user_prompt}
|
||||||
|
- If it does, take notes of the useful information, write down source, link or reference, then move to a new page.
|
||||||
|
- If it does and you are 100% certain that it provide a definive answer, say REQUEST_EXIT
|
||||||
|
- If it doesn’t, say: Error: This page does not answer the user’s query then go back or navigate to another link.
|
||||||
|
2. Navigate by either:
|
||||||
|
- Navigate to a navigation links (write the full URL, e.g., www.example.com/cats).
|
||||||
|
- If no link seems helpful, say: GO_BACK.
|
||||||
|
|
||||||
|
Recap of note taking:
|
||||||
|
If useful -> Note: [Briefly summarize the key information that answers the user’s query.]
|
||||||
|
Do not write "The page talk about ...", write your finding on the page and how they contribute to an answer.
|
||||||
|
If not useful -> Error: [Explain why the page doesn’t help.]
|
||||||
|
|
||||||
|
Example 1 (useful page, no need of going futher):
|
||||||
|
Note: According to karpathy site (https://karpathy.github.io/) LeCun net is the earliest real-world application of a neural net"
|
||||||
|
No link seem useful to provide futher information. GO_BACK
|
||||||
|
|
||||||
Follow up with a summary of the page content (of the current page, not of the link), for example:
|
Example 2 (not useful, but related link):
|
||||||
Summary: According to https://karpathy.github.io/ LeCun net is the earliest real-world application of a neural net"
|
Error: This forum reddit.com/welcome does not discuss anything related to the user’s query.
|
||||||
The summary should include any useful finding that are useful in answering user query.
|
There is a link that could lead to the information, I want to navigate to http://reddit.com/r/locallama
|
||||||
If a website does not have usefull information say Error, for exemple:
|
|
||||||
Error: This forum does not discus anything that can answer the user query
|
|
||||||
Be short, concise, direct.
|
|
||||||
|
|
||||||
If no link seem appropriate, please say "GO_BACK".
|
Example 3 (not useful, no related links):
|
||||||
Remember, you seek the information the user want.
|
Error: x.com does not discuss anything related to the user’s query and no navigation link are usefull
|
||||||
The user query was : {user_prompt}
|
GO_BACK
|
||||||
|
|
||||||
|
Example 3 (not useful, no related links):
|
||||||
|
Note: I found on github.com that the creator of agenticSeek is fosowl.
|
||||||
|
Given this information, given this I should exit the web browser. REQUEST_EXIT
|
||||||
|
|
||||||
|
Remember, the user asked: {user_prompt}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def llm_decide(self, prompt):
|
def llm_decide(self, prompt):
|
||||||
@@ -122,11 +138,11 @@ class BrowserAgent(Agent):
|
|||||||
def save_notes(self, text):
|
def save_notes(self, text):
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if "summary" in line.lower():
|
if "note" in line.lower():
|
||||||
self.notes.append(line)
|
self.notes.append(line)
|
||||||
|
|
||||||
def conclude_prompt(self, user_query):
|
def conclude_prompt(self, user_query):
|
||||||
annotated_notes = [f"{i+1}: {note.lower().replace('summary:', '')}" for i, note in enumerate(self.notes)]
|
annotated_notes = [f"{i+1}: {note.lower().replace('note:', '')}" for i, note in enumerate(self.notes)]
|
||||||
search_note = '\n'.join(annotated_notes)
|
search_note = '\n'.join(annotated_notes)
|
||||||
print("AI research notes:\n", search_note)
|
print("AI research notes:\n", search_note)
|
||||||
return f"""
|
return f"""
|
||||||
@@ -174,7 +190,6 @@ class BrowserAgent(Agent):
|
|||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
answer, reasoning = self.llm_request(prompt)
|
answer, reasoning = self.llm_request(prompt)
|
||||||
pretty_print(answer, color="output")
|
pretty_print(answer, color="output")
|
||||||
speech_module.speak(answer)
|
|
||||||
return answer, reasoning
|
return answer, reasoning
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class FileAgent(Agent):
|
|||||||
"file_finder": FileFinder(),
|
"file_finder": FileFinder(),
|
||||||
"bash": BashInterpreter()
|
"bash": BashInterpreter()
|
||||||
}
|
}
|
||||||
self.role = "files operations"
|
self.role = "find, read, write, edit files"
|
||||||
|
|
||||||
def process(self, prompt, speech_module) -> str:
|
def process(self, prompt, speech_module) -> str:
|
||||||
complete = False
|
complete = False
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from sources.utility import pretty_print, animate_thinking
|
|||||||
from sources.agents.agent import Agent
|
from sources.agents.agent import Agent
|
||||||
from sources.agents.code_agent import CoderAgent
|
from sources.agents.code_agent import CoderAgent
|
||||||
from sources.agents.file_agent import FileAgent
|
from sources.agents.file_agent import FileAgent
|
||||||
from sources.agents.casual_agent import CasualAgent
|
from sources.agents.browser_agent import BrowserAgent
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class PlannerAgent(Agent):
|
class PlannerAgent(Agent):
|
||||||
@@ -19,9 +19,9 @@ class PlannerAgent(Agent):
|
|||||||
self.agents = {
|
self.agents = {
|
||||||
"coder": CoderAgent(model, name, prompt_path, provider),
|
"coder": CoderAgent(model, name, prompt_path, provider),
|
||||||
"file": FileAgent(model, name, prompt_path, provider),
|
"file": FileAgent(model, name, prompt_path, provider),
|
||||||
"web": CasualAgent(model, name, prompt_path, provider)
|
"web": BrowserAgent(model, name, prompt_path, provider)
|
||||||
}
|
}
|
||||||
self.role = "complex programming tasks and web research"
|
self.role = "Manage complex tasks"
|
||||||
self.tag = "json"
|
self.tag = "json"
|
||||||
|
|
||||||
def parse_agent_tasks(self, text):
|
def parse_agent_tasks(self, text):
|
||||||
|
|||||||
+29
-11
@@ -12,6 +12,8 @@ from bs4 import BeautifulSoup
|
|||||||
import markdownify
|
import markdownify
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
class Browser:
|
class Browser:
|
||||||
def __init__(self, headless=False, anticaptcha_install=False):
|
def __init__(self, headless=False, anticaptcha_install=False):
|
||||||
@@ -57,7 +59,8 @@ class Browser:
|
|||||||
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google\\Chrome\\Application\\chrome.exe") # User install
|
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google\\Chrome\\Application\\chrome.exe") # User install
|
||||||
]
|
]
|
||||||
elif sys.platform.startswith("darwin"): # macOS
|
elif sys.platform.startswith("darwin"): # macOS
|
||||||
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"]
|
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
|
||||||
else: # Linux
|
else: # Linux
|
||||||
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"]
|
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"]
|
||||||
|
|
||||||
@@ -81,15 +84,15 @@ class Browser:
|
|||||||
def is_sentence(self, text):
|
def is_sentence(self, text):
|
||||||
"""Check if the text qualifies as a meaningful sentence or contains important error codes."""
|
"""Check if the text qualifies as a meaningful sentence or contains important error codes."""
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
|
|
||||||
error_codes = ["404", "403", "500", "502", "503"]
|
error_codes = ["404", "403", "500", "502", "503"]
|
||||||
if any(code in text for code in error_codes):
|
if any(code in text for code in error_codes):
|
||||||
return True
|
return True
|
||||||
words = text.split()
|
words = re.findall(r'\w+', text, re.UNICODE)
|
||||||
word_count = len(words)
|
word_count = len(words)
|
||||||
has_punctuation = text.endswith(('.', '!', '?'))
|
has_punctuation = any(text.endswith(p) for p in ['.', ',', ',', '!', '?', '。', '!', '?', '।', '۔'])
|
||||||
is_long_enough = word_count > 5
|
is_long_enough = word_count > 5
|
||||||
has_letters = any(word.isalpha() for word in words)
|
return (word_count >= 5 and (has_punctuation or is_long_enough))
|
||||||
return (word_count >= 5 and (has_punctuation or is_long_enough) and has_letters)
|
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
"""Get page text and convert it to README (Markdown) format."""
|
"""Get page text and convert it to README (Markdown) format."""
|
||||||
@@ -104,15 +107,14 @@ class Browser:
|
|||||||
lines = (line.strip() for line in text.splitlines())
|
lines = (line.strip() for line in text.splitlines())
|
||||||
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
||||||
text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk))
|
text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk))
|
||||||
|
#markdown_text = markdownify.markdownify(text, heading_style="ATX")
|
||||||
markdown_text = markdownify.markdownify(text, heading_style="ATX")
|
return "[Start of page]\n" + text + "\n[End of page]"
|
||||||
|
|
||||||
return markdown_text
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error getting text: {str(e)}")
|
self.logger.error(f"Error getting text: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def clean_url(self, url):
|
def clean_url(self, url):
|
||||||
|
"""Clean URL to keep only the part needed for navigation to the page"""
|
||||||
clean = url.split('#')[0]
|
clean = url.split('#')[0]
|
||||||
parts = clean.split('?', 1)
|
parts = clean.split('?', 1)
|
||||||
base_url = parts[0]
|
base_url = parts[0]
|
||||||
@@ -127,6 +129,22 @@ class Browser:
|
|||||||
if essential_params:
|
if essential_params:
|
||||||
return f"{base_url}?{'&'.join(essential_params)}"
|
return f"{base_url}?{'&'.join(essential_params)}"
|
||||||
return base_url
|
return base_url
|
||||||
|
|
||||||
|
def is_link_valid(self, url):
|
||||||
|
"""Check if a URL is a valid link (page, not related to icon or metadata)."""
|
||||||
|
if len(url) > 64:
|
||||||
|
return False
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
if not parsed_url.scheme or not parsed_url.netloc:
|
||||||
|
return False
|
||||||
|
if re.search(r'/\d+$', parsed_url.path):
|
||||||
|
return False
|
||||||
|
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']
|
||||||
|
metadata_extensions = ['.ico', '.xml', '.json', '.rss', '.atom']
|
||||||
|
for ext in image_extensions + metadata_extensions:
|
||||||
|
if url.lower().endswith(ext):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def get_navigable(self):
|
def get_navigable(self):
|
||||||
"""Get all navigable links on the current page."""
|
"""Get all navigable links on the current page."""
|
||||||
@@ -144,7 +162,7 @@ class Browser:
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.logger.info(f"Found {len(links)} navigable links")
|
self.logger.info(f"Found {len(links)} navigable links")
|
||||||
return [self.clean_url(link['url']) for link in links if link['is_displayed'] == True and len(link) < 256]
|
return [self.clean_url(link['url']) for link in links if (link['is_displayed'] == True and self.is_link_valid(link['url']))]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error getting navigable links: {str(e)}")
|
self.logger.error(f"Error getting navigable links: {str(e)}")
|
||||||
return []
|
return []
|
||||||
@@ -210,7 +228,7 @@ if __name__ == "__main__":
|
|||||||
browser = Browser(headless=False)
|
browser = Browser(headless=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
browser.go_to("https://karpathy.github.io/")
|
browser.go_to("https://github.com/Fosowl/agenticSeek")
|
||||||
text = browser.get_text()
|
text = browser.get_text()
|
||||||
print("Page Text in Markdown:")
|
print("Page Text in Markdown:")
|
||||||
print(text)
|
print(text)
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class Interaction:
|
|||||||
self.current_agent = agent
|
self.current_agent = agent
|
||||||
# get history from previous agent, good ?
|
# get history from previous agent, good ?
|
||||||
self.current_agent.memory.push('user', self.last_query)
|
self.current_agent.memory.push('user', self.last_query)
|
||||||
|
self.current_agent.memory.push('assistant', self.last_answer)
|
||||||
self.last_answer, _ = agent.process(self.last_query, self.speech)
|
self.last_answer, _ = agent.process(self.last_query, self.speech)
|
||||||
|
|
||||||
def show_answer(self) -> None:
|
def show_answer(self) -> None:
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ class searxSearch(Tools):
|
|||||||
description = article.find('p', class_='content').text.strip() if article.find('p', class_='content') else "No Description"
|
description = article.find('p', class_='content').text.strip() if article.find('p', class_='content') else "No Description"
|
||||||
results.append(f"Title:{title}\nSnippet:{description}\nLink:{url}")
|
results.append(f"Title:{title}\nSnippet:{description}\nLink:{url}")
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
raise Exception("Searx search failed. did you run start_services.sh? Did docker die?")
|
return "No search results, web search failed."
|
||||||
return "\n\n".join(results) # Return results as a single string, separated by newlines
|
return "\n\n".join(results) # Return results as a single string, separated by newlines
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
return f"Error during search: {str(e)}"
|
raise Exception("\nSearxng search failed. did you run start_services.sh? is docker still running?") from e
|
||||||
|
|
||||||
def execution_failure_check(self, output: str) -> bool:
|
def execution_failure_check(self, output: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user