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:
Martin
2025-03-18 18:25:29 +01:00
committed by GitHub
10 changed files with 100 additions and 43 deletions
+27
View File
@@ -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.
![alt text](./media/chromedriver_readme.png)
## FAQ ## FAQ
**Q: What hardware do I need?** **Q: What hardware do I need?**
-4
View File
@@ -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

+1 -1
View File
@@ -44,7 +44,7 @@ User: "I need to find the file config.txt and read its contents."
Assistant: Ill use file_finder to locate the file: Assistant: Ill use file_finder to locate the file:
```file_finder ```file_finder:read
config.txt config.txt
``` ```
+36 -21
View File
@@ -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 users request. Your goal is to find accurate and complete information to satisfy the users 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 users 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 doesnt, say: Error: This page does not answer the users 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.
Follow up with a summary of the page content (of the current page, not of the link), for example: Recap of note taking:
Summary: According to https://karpathy.github.io/ LeCun net is the earliest real-world application of a neural net" If useful -> Note: [Briefly summarize the key information that answers the users query.]
The summary should include any useful finding that are useful in answering user query. Do not write "The page talk about ...", write your finding on the page and how they contribute to an answer.
If a website does not have usefull information say Error, for exemple: If not useful -> Error: [Explain why the page doesnt help.]
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 1 (useful page, no need of going futher):
Remember, you seek the information the user want. Note: According to karpathy site (https://karpathy.github.io/) LeCun net is the earliest real-world application of a neural net"
The user query was : {user_prompt} No link seem useful to provide futher information. GO_BACK
Example 2 (not useful, but related link):
Error: This forum reddit.com/welcome does not discuss anything related to the users query.
There is a link that could lead to the information, I want to navigate to http://reddit.com/r/locallama
Example 3 (not useful, no related links):
Error: x.com does not discuss anything related to the users query and no navigation link are usefull
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__":
+1 -1
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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]
@@ -128,6 +130,22 @@ class Browser:
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."""
try: try:
@@ -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)
+1
View File
@@ -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:
+2 -2
View File
@@ -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:
""" """